From db996c0e8ee30ce3fec24ef2d30154bf912d3a8c Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 16:39:40 +0100 Subject: [PATCH 01/15] Expose Transaction.Context.Request and Transaction.Context.Response #124 --- sample/ApiSamples/Program.cs | 2 + src/Elastic.Apm.AspNetCore/ApmMiddleware.cs | 19 ++-- src/Elastic.Apm/Api/Context.cs | 28 ++++++ src/Elastic.Apm/Api/ITransaction.cs | 6 ++ .../{Model/Payload => Api}/Request.cs | 16 +++- src/Elastic.Apm/Api/Response.cs | 19 ++++ src/Elastic.Apm/Elastic.Apm.csproj | 6 +- src/Elastic.Apm/Model/Payload/Context.cs | 15 ---- src/Elastic.Apm/Model/Payload/Response.cs | 12 --- .../ApiTests/ConvenientApiTransactionTests.cs | 86 +++++++++++++++++++ 10 files changed, 165 insertions(+), 44 deletions(-) create mode 100755 src/Elastic.Apm/Api/Context.cs rename src/Elastic.Apm/{Model/Payload => Api}/Request.cs (52%) create mode 100755 src/Elastic.Apm/Api/Response.cs delete mode 100755 src/Elastic.Apm/Model/Payload/Context.cs delete mode 100755 src/Elastic.Apm/Model/Payload/Response.cs diff --git a/sample/ApiSamples/Program.cs b/sample/ApiSamples/Program.cs index 062546818..e22477fae 100644 --- a/sample/ApiSamples/Program.cs +++ b/sample/ApiSamples/Program.cs @@ -70,6 +70,8 @@ public static void SampleError() public static void SampleCustomTransactionWithConvenientApi() => Agent.Tracer.CaptureTransaction("TestTransaction", "TestType", t => { + t.Context.Response = new Response() { Finished = true, StatusCode = 200 }; + t.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); t.Tags["fooTransaction"] = "barTransaction"; Thread.Sleep(10); t.CaptureSpan("TestSpan", "TestSpanName", s => diff --git a/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs b/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs index 9a8fa9f7b..02a697e10 100755 --- a/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs +++ b/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Elastic.Apm.Api; using Elastic.Apm.Helpers; -using Elastic.Apm.Model.Payload; using Microsoft.AspNetCore.Http; [assembly: @@ -32,21 +31,21 @@ public async Task InvokeAsync(HttpContext context) var transaction = _tracer.StartTransactionInternal($"{context.Request.Method} {context.Request.Path}", ApiConstants.TypeRequest); - transaction.Context.Request = new Request + var url = new Url + { + Full = context.Request?.Path.Value, + HostName = context.Request.Host.Host, + Protocol = GetProtocolName(context.Request.Protocol), + Raw = context.Request?.Path.Value //TODO + }; + + transaction.Context.Request = new Request( context.Request.Method, url) { - Method = context.Request.Method, Socket = new Socket { Encrypted = context.Request.IsHttps, RemoteAddress = context.Connection?.RemoteIpAddress?.ToString() }, - Url = new Url - { - Full = context.Request?.Path.Value, - HostName = context.Request.Host.Host, - Protocol = GetProtocolName(context.Request.Protocol), - Raw = context.Request?.Path.Value //TODO - }, HttpVersion = GetHttpVersion(context.Request.Protocol) }; diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs new file mode 100755 index 000000000..59f4bb931 --- /dev/null +++ b/src/Elastic.Apm/Api/Context.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Elastic.Apm.Api +{ + public class Context + { + private readonly Lazy> tags = new Lazy>(); + + /// + /// If a log record was generated as a result of a http request, the http interface can be used to collect this + /// information. + /// This property is by default null! You have to assign a instance to this property in order to use + /// it. + /// + public Request Request { get; set; } + + /// + /// If a log record was generated as a result of a http request, the http interface can be used to collect this + /// information. + /// This property is by default null! You have to assign a instance to this property in order to use + /// it. + /// + public Response Response { get; set; } + + public Dictionary Tags => tags.Value; + } +} diff --git a/src/Elastic.Apm/Api/ITransaction.cs b/src/Elastic.Apm/Api/ITransaction.cs index 96975e002..ee405bc57 100644 --- a/src/Elastic.Apm/Api/ITransaction.cs +++ b/src/Elastic.Apm/Api/ITransaction.cs @@ -7,6 +7,12 @@ namespace Elastic.Apm.Api { public interface ITransaction { + /// + /// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user. + /// This field is lazily initialized, you don't have to assign a value to it and you don't have to null check it either. + /// + Context Context { get; } + /// /// The duration of the transaction. /// If it's not set (its HasValue property is false) then the value diff --git a/src/Elastic.Apm/Model/Payload/Request.cs b/src/Elastic.Apm/Api/Request.cs similarity index 52% rename from src/Elastic.Apm/Model/Payload/Request.cs rename to src/Elastic.Apm/Api/Request.cs index 54d25e641..eeda4f24f 100755 --- a/src/Elastic.Apm/Model/Payload/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -1,17 +1,25 @@ using Newtonsoft.Json; -namespace Elastic.Apm.Model.Payload +namespace Elastic.Apm.Api { - internal class Request + /// + /// Encapsulates Request related information that can be attached to an through + /// See + /// + public class Request { + public Request(string method, Url url) => (Method, Url) = (method, url); + public string HttpVersion { get; set; } public string Method { get; set; } public Socket Socket { get; set; } public Url Url { get; set; } + + public object Body { get; set; } } - internal class Socket + public class Socket { public bool Encrypted { get; set; } @@ -19,7 +27,7 @@ internal class Socket public string RemoteAddress { get; set; } } - internal class Url + public class Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs new file mode 100755 index 000000000..b8d074995 --- /dev/null +++ b/src/Elastic.Apm/Api/Response.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Elastic.Apm.Api +{ + /// + /// Encapsulates Response related information that can be attached to an through + /// See + /// + public class Response + { + public bool Finished { get; set; } + + /// + /// The HTTP status code of the response. + /// + [JsonProperty("Status_code")] + public int StatusCode { get; set; } + } +} diff --git a/src/Elastic.Apm/Elastic.Apm.csproj b/src/Elastic.Apm/Elastic.Apm.csproj index e534436f4..0a78c949b 100644 --- a/src/Elastic.Apm/Elastic.Apm.csproj +++ b/src/Elastic.Apm/Elastic.Apm.csproj @@ -22,9 +22,9 @@ latest - + - - + + diff --git a/src/Elastic.Apm/Model/Payload/Context.cs b/src/Elastic.Apm/Model/Payload/Context.cs deleted file mode 100755 index 149d5d2e3..000000000 --- a/src/Elastic.Apm/Model/Payload/Context.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Elastic.Apm.Model.Payload -{ - internal class Context - { - private readonly Lazy> tags = new Lazy>(); - public Request Request { get; set; } - - public Response Response { get; set; } - - public Dictionary Tags => tags.Value; - } -} diff --git a/src/Elastic.Apm/Model/Payload/Response.cs b/src/Elastic.Apm/Model/Payload/Response.cs deleted file mode 100755 index a40e5d6b4..000000000 --- a/src/Elastic.Apm/Model/Payload/Response.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace Elastic.Apm.Model.Payload -{ - internal class Response - { - public bool Finished { get; set; } - - [JsonProperty("Status_code")] - public int StatusCode { get; set; } - } -} diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs index 48034ed37..edc64e3b9 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs @@ -467,6 +467,92 @@ await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transa Assert.Equal("bar", payloadSender.Payloads[0].Transactions[0].Tags["foo"]); } + /// + /// Creates a transaction and attaches a Request to this transaction. + /// Makes sure that the transaction details are captured. + /// + [Fact] + public void TransactionWithRequest() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); + }); + }); + + Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); + Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + } + + /// + /// Creates a transaction and attaches a Request to this transaction. It fills all the fields on the Request. + /// Makes sure that all the transaction details are captured. + /// + [Fact] + public void TransactionWithRequestDetailed() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Request = new Request("GET", new Url() + { + Full = "https://elastic.co", + Raw = "https://elastic.co", + HostName = "elastic", + Protocol = "HTTP" + }) + { + HttpVersion = "2.0", + Socket = new Socket() + { + Encrypted = true, + RemoteAddress = "127.0.0.1", + }, + Body = "123" + }; + }); + }); + + Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); + Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + + Assert.Equal("2.0", payloadSender.FirstTransaction.Context.Request.HttpVersion); + Assert.True(payloadSender.FirstTransaction.Context.Request.Socket.Encrypted); + Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Full); + Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Raw); + Assert.Equal("127.0.0.1", payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress); + Assert.Equal("elastic", payloadSender.FirstTransaction.Context.Request.Url.HostName); + Assert.Equal("123", payloadSender.FirstTransaction.Context.Request.Body); + } + + /// + /// Creates a transaction and attaches a Response to this transaction. + /// Makes sure that the transaction details are captured. + /// + [Fact] + public void TransactionWithResponse() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Response = new Response() { Finished = true, StatusCode = 200 }; + }); + }); + + Assert.True(payloadSender.FirstTransaction.Context.Response.Finished); + Assert.Equal(200, payloadSender.FirstTransaction.Context.Response.StatusCode); + } + /// /// Asserts on 1 transaction with async code /// From c38aba17103a6a512832e561722a242d665a4959 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 16:43:56 +0100 Subject: [PATCH 02/15] Update src/Elastic.Apm/Elastic.Apm.csproj --- src/Elastic.Apm/Elastic.Apm.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Elastic.Apm/Elastic.Apm.csproj b/src/Elastic.Apm/Elastic.Apm.csproj index 0a78c949b..e534436f4 100644 --- a/src/Elastic.Apm/Elastic.Apm.csproj +++ b/src/Elastic.Apm/Elastic.Apm.csproj @@ -22,9 +22,9 @@ latest - + - - + + From 842eda01ed5b54739749750f19638a5b1b2f0c91 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 19:08:14 +0100 Subject: [PATCH 03/15] Make Request, Socket, Url, Response to struct. --- src/Elastic.Apm/Api/Request.cs | 8 ++++---- src/Elastic.Apm/Api/Response.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Apm/Api/Request.cs b/src/Elastic.Apm/Api/Request.cs index eeda4f24f..ade123a40 100755 --- a/src/Elastic.Apm/Api/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -6,9 +6,9 @@ namespace Elastic.Apm.Api /// Encapsulates Request related information that can be attached to an through /// See /// - public class Request + public struct Request { - public Request(string method, Url url) => (Method, Url) = (method, url); + public Request(string method, Url url) => (Method, Url, Body, HttpVersion, Socket) = (method, url, null, null, new Socket()); public string HttpVersion { get; set; } @@ -19,7 +19,7 @@ public class Request public object Body { get; set; } } - public class Socket + public struct Socket { public bool Encrypted { get; set; } @@ -27,7 +27,7 @@ public class Socket public string RemoteAddress { get; set; } } - public class Url + public struct Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs index b8d074995..100ca1634 100755 --- a/src/Elastic.Apm/Api/Response.cs +++ b/src/Elastic.Apm/Api/Response.cs @@ -6,7 +6,7 @@ namespace Elastic.Apm.Api /// Encapsulates Response related information that can be attached to an through /// See /// - public class Response + public struct Response { public bool Finished { get; set; } From 5ba803e0b380a8706fba0de95163396067873252 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 00:18:10 +0100 Subject: [PATCH 04/15] Adjust comments --- src/Elastic.Apm/Api/Context.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs index 59f4bb931..9e1fde087 100755 --- a/src/Elastic.Apm/Api/Context.cs +++ b/src/Elastic.Apm/Api/Context.cs @@ -10,7 +10,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property is by default null! You have to assign a instance to this property in order to use + /// This property by default contains empty values! You have to assign a instance to this property in order to use /// it. /// public Request Request { get; set; } @@ -18,7 +18,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property is by default null! You have to assign a instance to this property in order to use + /// This property by default contains empty values! You have to assign a instance to this property in order to use /// it. /// public Response Response { get; set; } From 7ddcb0d688434f937b698ee1baa003974e5eb26d Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 09:55:55 +0100 Subject: [PATCH 05/15] Revert "Adjust comments" This reverts commit 5ba803e0b380a8706fba0de95163396067873252. --- src/Elastic.Apm/Api/Context.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs index 9e1fde087..59f4bb931 100755 --- a/src/Elastic.Apm/Api/Context.cs +++ b/src/Elastic.Apm/Api/Context.cs @@ -10,7 +10,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property by default contains empty values! You have to assign a instance to this property in order to use + /// This property is by default null! You have to assign a instance to this property in order to use /// it. /// public Request Request { get; set; } @@ -18,7 +18,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property by default contains empty values! You have to assign a instance to this property in order to use + /// This property is by default null! You have to assign a instance to this property in order to use /// it. /// public Response Response { get; set; } From cbf51ffe4f79d34c0963f5f3dbdff660b6fec1f0 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 09:55:58 +0100 Subject: [PATCH 06/15] Revert "Make Request, Socket, Url, Response to struct." This reverts commit 842eda01ed5b54739749750f19638a5b1b2f0c91. --- src/Elastic.Apm/Api/Request.cs | 8 ++++---- src/Elastic.Apm/Api/Response.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Apm/Api/Request.cs b/src/Elastic.Apm/Api/Request.cs index ade123a40..eeda4f24f 100755 --- a/src/Elastic.Apm/Api/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -6,9 +6,9 @@ namespace Elastic.Apm.Api /// Encapsulates Request related information that can be attached to an through /// See /// - public struct Request + public class Request { - public Request(string method, Url url) => (Method, Url, Body, HttpVersion, Socket) = (method, url, null, null, new Socket()); + public Request(string method, Url url) => (Method, Url) = (method, url); public string HttpVersion { get; set; } @@ -19,7 +19,7 @@ public struct Request public object Body { get; set; } } - public struct Socket + public class Socket { public bool Encrypted { get; set; } @@ -27,7 +27,7 @@ public struct Socket public string RemoteAddress { get; set; } } - public struct Url + public class Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs index 100ca1634..b8d074995 100755 --- a/src/Elastic.Apm/Api/Response.cs +++ b/src/Elastic.Apm/Api/Response.cs @@ -6,7 +6,7 @@ namespace Elastic.Apm.Api /// Encapsulates Response related information that can be attached to an through /// See /// - public struct Response + public class Response { public bool Finished { get; set; } From 2363dd89952076688c691e300ed896a004e73ccb Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 4 Mar 2019 14:27:25 +0100 Subject: [PATCH 07/15] move assertions over to fluent assertions (#133) * move assertions over to fluent assertions * Add extensions for should duration assertions * move new Asserts over after rebase --- .../AspNetCoreDiagnosticListenerTest.cs | 26 +- .../AspNetCoreMiddlewareTests.cs | 76 +- .../DiagnosticListenerTests.cs | 31 +- .../Elastic.Apm.AspNetCore.Tests.csproj | 7 +- .../MicrosoftExtensionsConfigTests.cs | 61 +- test/Elastic.Apm.Tests/ApiTests/ApiTests.cs | 109 +- .../ApiTests/ConvenientApiSpanTests.cs | 211 ++-- .../ApiTests/ConvenientApiTransactionTests.cs | 176 +-- test/Elastic.Apm.Tests/BasicAgentTests.cs | 19 +- test/Elastic.Apm.Tests/ConfigTest.cs | 112 +- .../Elastic.Apm.Tests.csproj | 3 + .../ShouldWaitDurationExtensions.cs | 19 + .../{ => Extensions}/WaitExtensions.cs | 15 +- .../HttpDiagnosticListenerTest.cs | 1097 +++++++++-------- test/Elastic.Apm.Tests/LoggerTest.cs | 29 +- test/Elastic.Apm.Tests/StackTraceTests.cs | 6 +- 16 files changed, 1045 insertions(+), 952 deletions(-) create mode 100644 test/Elastic.Apm.Tests/Extensions/ShouldWaitDurationExtensions.cs rename test/Elastic.Apm.Tests/{ => Extensions}/WaitExtensions.cs (69%) diff --git a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreDiagnosticListenerTest.cs b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreDiagnosticListenerTest.cs index 114156ff5..52577af06 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreDiagnosticListenerTest.cs +++ b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreDiagnosticListenerTest.cs @@ -1,8 +1,10 @@ using System; using System.Net.Http; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Elastic.Apm.Model.Payload; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SampleAspNetCoreApp; using Xunit; @@ -34,18 +36,24 @@ public async Task TestErrorInAspNetCore() var response = await client.GetAsync("/Home/TriggerError"); - Assert.Single(capturedPayload.Payloads); - Assert.Single(capturedPayload.Payloads[0].Transactions); + capturedPayload.Should().NotBeNull(); + capturedPayload.Payloads.Should().ContainSingle(); + capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); - Assert.Single(capturedPayload.Errors); - Assert.Single(capturedPayload.Errors[0].Errors); + capturedPayload.Errors.Should().ContainSingle(); + capturedPayload.Errors[0].Errors.Should().ContainSingle(); - Assert.Equal("This is a test exception!", capturedPayload.Errors[0].Errors[0].Exception.Message); - Assert.Equal(typeof(Exception).FullName, capturedPayload.Errors[0].Errors[0].Exception.Type); + var errorException = capturedPayload.Errors[0].Errors[0].Exception; + errorException.Message.Should().Be("This is a test exception!"); + errorException.Type.Should().Be(typeof(Exception).FullName); - Assert.Equal("/Home/TriggerError", capturedPayload.FirstErrorDetail.Context.Request.Url.Full); - Assert.Equal(HttpMethod.Get.Method, capturedPayload.FirstErrorDetail.Context.Request.Method); - Assert.False((capturedPayload.FirstErrorDetail.Exception as CapturedException)?.Handled); + var context = capturedPayload.FirstErrorDetail.Context; + context.Request.Url.Full.Should().Be("/Home/TriggerError"); + context.Request.Method.Should().Be(HttpMethod.Get.Method); + + var capturedException = errorException as CapturedException; + capturedException.Should().NotBeNull(); + capturedException.Handled.Should().BeFalse(); } } } diff --git a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreMiddlewareTests.cs b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreMiddlewareTests.cs index 7625948c9..97761dde3 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreMiddlewareTests.cs +++ b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreMiddlewareTests.cs @@ -4,9 +4,11 @@ using System.Threading.Tasks; using Elastic.Apm.Model.Payload; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SampleAspNetCoreApp; using Xunit; +using Xunit.Sdk; namespace Elastic.Apm.AspNetCore.Tests { @@ -43,41 +45,48 @@ public async Task HomeSimplePageTransactionTest() { var response = await _client.GetAsync("/Home/SimplePage"); - Assert.Single(_capturedPayload.Payloads); - Assert.Single(_capturedPayload.Payloads[0].Transactions); + _capturedPayload.Payloads.Should().ContainSingle(); + _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); var payload = _capturedPayload.Payloads[0]; - //test payload - Assert.Equal(Assembly.GetEntryAssembly()?.GetName()?.Name, payload.Service.Name); - Assert.Equal(Consts.AgentName, payload.Service.Agent.Name); - Assert.Equal(Assembly.Load("Elastic.Apm").GetName().Version.ToString(), payload.Service.Agent.Version); - Assembly.CreateQualifiedName("ASP.NET Core", payload.Service.Framework.Name); - Assembly.CreateQualifiedName(Assembly.Load("Microsoft.AspNetCore").GetName().Version.ToString(), payload.Service.Framework.Version); + payload.Service.Name.Should().NotBeNullOrWhiteSpace() + .And.Be(Assembly.GetEntryAssembly()?.GetName()?.Name); + + payload.Service.Agent.Name.Should().Be(Consts.AgentName); + var apmVersion = Assembly.Load("Elastic.Apm").GetName().Version.ToString(); + payload.Service.Agent.Version.Should().Be(apmVersion); + + payload.Service.Framework.Name.Should().Be("ASP.NET Core"); + + var aspNetCoreVersion = Assembly.Load("Microsoft.AspNetCore").GetName().Version.ToString(); + payload.Service.Framework.Version.Should().Be(aspNetCoreVersion); var transaction = _capturedPayload.FirstTransaction; //test transaction - Assert.Equal($"{response.RequestMessage.Method} {response.RequestMessage.RequestUri.AbsolutePath}", transaction.Name); - Assert.Equal("HTTP 2xx", transaction.Result); - Assert.True(transaction.Duration > 0); - Assert.Equal("request", transaction.Type); - Assert.True(transaction.Id != Guid.Empty); + var transactionName = $"{response.RequestMessage.Method} {response.RequestMessage.RequestUri.AbsolutePath}"; + transaction.Name.Should().Be(transactionName); + transaction.Result.Should().Be("HTTP 2xx"); + transaction.Duration.Should().BeGreaterThan(0); + + transaction.Type.Should().Be("request"); + transaction.Id.Should().NotBeEmpty(); //test transaction.context.response - Assert.Equal(200, transaction.Context.Response.StatusCode); + transaction.Context.Response.StatusCode.Should().Be(200); //test transaction.context.request - Assert.Equal("2.0", transaction.Context.Request.HttpVersion); - Assert.Equal("GET", transaction.Context.Request.Method); + transaction.Context.Request.HttpVersion.Should().Be("2.0"); + transaction.Context.Request.Method.Should().Be("GET"); //test transaction.context.request.url - Assert.Equal(response.RequestMessage.RequestUri.AbsolutePath, transaction.Context.Request.Url.Full); - Assert.Equal("localhost", transaction.Context.Request.Url.HostName); - Assert.Equal("HTTP", transaction.Context.Request.Url.Protocol); + transaction.Context.Request.Url.Full.Should().Be(response.RequestMessage.RequestUri.AbsolutePath); + transaction.Context.Request.Url.HostName.Should().Be("localhost"); + transaction.Context.Request.Url.Protocol.Should().Be("HTTP"); //test transaction.context.request.encrypted - Assert.False(transaction.Context.Request.Socket.Encrypted); + transaction.Context.Request.Socket.Encrypted.Should().BeFalse(); } /// @@ -88,13 +97,9 @@ public async Task HomeSimplePageTransactionTest() public async Task HomeIndexSpanTest() { var response = await _client.GetAsync("/Home/Index"); - Assert.True(response.IsSuccessStatusCode); - - var transaction = _capturedPayload.Payloads[0].Transactions[0]; - Assert.NotEmpty(_capturedPayload.SpansOnFirstTransaction); + response.IsSuccessStatusCode.Should().BeTrue(); - //one of the spans is a DB call: - Assert.Contains(_capturedPayload.SpansOnFirstTransaction, n => n.Context.Db != null); + _capturedPayload.SpansOnFirstTransaction.Should().NotBeEmpty().And.Contain(n => n.Context.Db != null); } /// @@ -106,16 +111,23 @@ public async Task HomeIndexSpanTest() public async Task FailingRequestWithoutConfiguredExceptionPage() { _client = Helper.GetClientWithoutExceptionPage(_agent, _factory); - await Assert.ThrowsAsync(async () => { await _client.GetAsync("Home/TriggerError"); }); - Assert.Single(_capturedPayload.Payloads); - Assert.Single(_capturedPayload.Payloads[0].Transactions); + Func act = async () => await _client.GetAsync("Home/TriggerError"); + await act.Should().ThrowAsync(); + + _capturedPayload.Payloads.Should().ContainSingle(); + _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); - Assert.NotEmpty(_capturedPayload.Errors); - Assert.Single(_capturedPayload.Errors[0].Errors); + _capturedPayload.Errors.Should().NotBeEmpty(); + _capturedPayload.Errors[0].Errors.Should().ContainSingle(); //also make sure the tag is captured - Assert.Equal(((_capturedPayload.Errors[0] as Error)?.Errors[0] as Error.ErrorDetail)?.Context.Tags["foo"], "bar"); + var error = _capturedPayload.Errors[0] as Error; + error.Should().NotBeNull(); + var errorDetail = error.Errors[0] as Error.ErrorDetail; + errorDetail.Should().NotBeNull(); + var tags = errorDetail.Context.Tags; + tags.Should().NotBeEmpty().And.ContainKey("foo").And.Contain("foo", "bar"); } public void Dispose() diff --git a/test/Elastic.Apm.AspNetCore.Tests/DiagnosticListenerTests.cs b/test/Elastic.Apm.AspNetCore.Tests/DiagnosticListenerTests.cs index ba5aa19cd..2ee4ddb8c 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/DiagnosticListenerTests.cs +++ b/test/Elastic.Apm.AspNetCore.Tests/DiagnosticListenerTests.cs @@ -4,6 +4,7 @@ using Elastic.Apm.AspNetCore.DiagnosticListener; using Elastic.Apm.EntityFrameworkCore; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SampleAspNetCoreApp; using Xunit; @@ -47,12 +48,12 @@ public async Task SubscribeAndUnsubscribeAspNetCoreDiagnosticListener() // { // await _client.GetAsync("/Home/TriggerError"); // -// Assert.Single(_capturedPayload.Payloads); -// Assert.Single(_capturedPayload.Payloads[0].Transactions); +// _capturedPayload.Payloads.Should().ContainSingle(); +// _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); // -// Assert.NotEmpty(_capturedPayload.Errors); -// Assert.Single(_capturedPayload.Errors[0].Errors); -// Assert.Equal(typeof(Exception).FullName, _capturedPayload.Errors[0].Errors[0].Exception.Type); +// _capturedPayload.Errors.Should().NotBeEmpty(); +// _capturedPayload.Errors[0].Errors.Should().ContainSingle(); +// _capturedPayload.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(Exception).FullName); // } //here we unsubsribe, so no errors should be captured after this line. _agent.Dispose(); @@ -62,9 +63,9 @@ public async Task SubscribeAndUnsubscribeAspNetCoreDiagnosticListener() await _client.GetAsync("/Home/TriggerError"); - Assert.Single(_capturedPayload.Payloads); - Assert.Single(_capturedPayload.Payloads[0].Transactions); - Assert.Empty(_capturedPayload.Errors); + _capturedPayload.Payloads.Should().ContainSingle(); + _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); + _capturedPayload.Errors.Should().BeEmpty(); } /// @@ -81,11 +82,11 @@ public async Task SubscribeAndUnsubscribeEfCoreDiagnosticListener() { await _client.GetAsync("/Home/Index"); - Assert.Single(_capturedPayload.Payloads); - Assert.Single(_capturedPayload.Payloads[0].Transactions); + _capturedPayload.Payloads.Should().ContainSingle(); + _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); - Assert.NotEmpty(_capturedPayload.SpansOnFirstTransaction); - Assert.Contains(_capturedPayload.SpansOnFirstTransaction, n => n.Context.Db != null); + _capturedPayload.SpansOnFirstTransaction.Should().NotBeEmpty() + .And.Contain(n => n.Context.Db != null); } //here we unsubsribe, so no errors should be captured after this line. _capturedPayload.Payloads.Clear(); @@ -93,10 +94,10 @@ public async Task SubscribeAndUnsubscribeEfCoreDiagnosticListener() await _client.GetAsync("/Home/Index"); - Assert.Single(_capturedPayload.Payloads); - Assert.Single(_capturedPayload.Payloads[0].Transactions); + _capturedPayload.Payloads.Should().ContainSingle(); + _capturedPayload.Payloads[0].Transactions.Should().ContainSingle(); - Assert.Empty(_capturedPayload.SpansOnFirstTransaction); + _capturedPayload.SpansOnFirstTransaction.Should().BeEmpty(); } public void Dispose() diff --git a/test/Elastic.Apm.AspNetCore.Tests/Elastic.Apm.AspNetCore.Tests.csproj b/test/Elastic.Apm.AspNetCore.Tests/Elastic.Apm.AspNetCore.Tests.csproj index 217bfd9db..ef41140ab 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/Elastic.Apm.AspNetCore.Tests.csproj +++ b/test/Elastic.Apm.AspNetCore.Tests/Elastic.Apm.AspNetCore.Tests.csproj @@ -6,8 +6,12 @@ Elastic.Apm.AspNetCore.Tests true ..\..\build\elasticapm.snk + + True + + @@ -20,9 +24,6 @@ - - - Always diff --git a/test/Elastic.Apm.AspNetCore.Tests/MicrosoftExtensionsConfigTests.cs b/test/Elastic.Apm.AspNetCore.Tests/MicrosoftExtensionsConfigTests.cs index fb465b4f2..30831ec3d 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/MicrosoftExtensionsConfigTests.cs +++ b/test/Elastic.Apm.AspNetCore.Tests/MicrosoftExtensionsConfigTests.cs @@ -7,6 +7,7 @@ using Elastic.Apm.Config; using Elastic.Apm.Logging; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using SampleAspNetCoreApp; @@ -27,10 +28,11 @@ public class MicrosoftExtensionsConfigTests [Fact] public void ReadValidConfigsFromAppSettingsJson() { - var config = new MicrosoftExtensionsConfig(GetConfig($"TestConfigs{Path.DirectorySeparatorChar}appsettings_valid.json"), new TestLogger()); - Assert.Equal(LogLevel.Debug, config.LogLevel); - Assert.Equal(new Uri("http://myServerFromTheConfigFile:8080"), config.ServerUrls[0]); - Assert.Equal("My_Test_Application", config.ServiceName); + var config = new MicrosoftExtensionsConfig(GetConfig($"TestConfigs{Path.DirectorySeparatorChar}appsettings_valid.json"), + new TestLogger()); + config.LogLevel.Should().Be(LogLevel.Debug); + config.ServerUrls[0].Should().Be(new Uri("http://myServerFromTheConfigFile:8080")); + config.ServiceName.Should().Be("My_Test_Application"); } /// @@ -42,11 +44,17 @@ public void ReadInvalidLogLevelConfigFromAppsettingsJson() { var logger = new TestLogger(); var config = new MicrosoftExtensionsConfig(GetConfig($"TestConfigs{Path.DirectorySeparatorChar}appsettings_invalid.json"), logger); - Assert.Equal(LogLevel.Error, config.LogLevel); - - Assert.Equal( - $"{{MicrosoftExtensionsConfig}} Failed parsing log level from {MicrosoftExtensionsConfig.Origin}: {MicrosoftExtensionsConfig.Keys.Level}, value: DbeugMisspelled. Defaulting to log level 'Error'", - logger.Lines[0]); + config.LogLevel.Should().Be(LogLevel.Error); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0].Should() + .StartWith($"{{{nameof(MicrosoftExtensionsConfig)}}}") + .And.ContainAll( + "Failed parsing log level from", + MicrosoftExtensionsConfig.Origin, + MicrosoftExtensionsConfig.Keys.Level, + "Defaulting to " + ); } /// @@ -58,11 +66,18 @@ public void ReadInvalidServerUrlsConfigFromAppsettingsJson() { var logger = new TestLogger(); var config = new MicrosoftExtensionsConfig(GetConfig($"TestConfigs{Path.DirectorySeparatorChar}appsettings_invalid.json"), logger); - Assert.Equal(LogLevel.Error, config.LogLevel); - - Assert.Equal( - $"{{MicrosoftExtensionsConfig}} Failed parsing log level from {MicrosoftExtensionsConfig.Origin}: {MicrosoftExtensionsConfig.Keys.Level}, value: DbeugMisspelled. Defaulting to log level 'Error'", - logger.Lines[0]); + config.LogLevel.Should().Be(LogLevel.Error); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0].Should() + .StartWith($"{{{nameof(MicrosoftExtensionsConfig)}}}") + .And.ContainAll( + "Failed parsing log level from", + MicrosoftExtensionsConfig.Origin, + MicrosoftExtensionsConfig.Keys.Level, + "Defaulting to ", + "DbeugMisspelled" + ); } /// @@ -84,10 +99,10 @@ public void ReadConfingsFromEnvVarsViaIConfig() .Build(); var config = new MicrosoftExtensionsConfig(configBuilder, new TestLogger()); - Assert.Equal(LogLevel.Debug, config.LogLevel); - Assert.Equal(new Uri(serverUrl), config.ServerUrls[0]); - Assert.Equal(serviceName, config.ServiceName); - Assert.Equal(secretToken, config.SecretToken); + config.LogLevel.Should().Be(LogLevel.Debug); + config.ServerUrls[0].Should().Be(new Uri(serverUrl)); + config.ServiceName.Should().Be(serviceName); + config.SecretToken.Should().Be(secretToken); } /// @@ -100,8 +115,8 @@ public void LoggerNotNull() var testLogger = new TestLogger(); var config = new MicrosoftExtensionsConfig(GetConfig($"TestConfigs{Path.DirectorySeparatorChar}appsettings_invalid.json"), testLogger); var serverUrl = config.ServerUrls.FirstOrDefault(); - Assert.NotNull(serverUrl); - Assert.NotEmpty(testLogger.Lines); + serverUrl.Should().NotBeNull(); + testLogger.Lines.Should().NotBeEmpty(); } internal static IConfiguration GetConfig(string path) @@ -149,10 +164,10 @@ public MicrosoftExtensionsConfigIntegrationTests(WebApplicationFactory public async Task InvalidUrlTest() { var response = await _client.GetAsync("/Home/Index"); - Assert.True(response.IsSuccessStatusCode); + response.IsSuccessStatusCode.Should().BeTrue(); - Assert.NotEmpty(_logger.Lines); - Assert.Contains(_logger.Lines, n => n.Contains("Failed parsing server URL from")); + _logger.Lines.Should().NotBeEmpty() + .And.Contain(n => n.Contains("Failed parsing server URL from")); } public void Dispose() diff --git a/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs b/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs index cfb6d8e4c..58495869c 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs @@ -4,6 +4,7 @@ using Elastic.Apm.Api; using Elastic.Apm.Model.Payload; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests.ApiTests @@ -32,13 +33,14 @@ public void StartEndTransaction() Thread.Sleep(5); //Make sure we have duration > 0 transaction.End(); - Assert.Single(payloadSender.Payloads); - Assert.Equal(transactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(transactionType, payloadSender.Payloads[0].Transactions[0].Type); - Assert.True(payloadSender.Payloads[0].Transactions[0].Duration >= 5); - Assert.True(payloadSender.Payloads[0].Transactions[0].Id != Guid.Empty); - - Assert.NotNull(payloadSender.Payloads[0].Service); + payloadSender.Payloads.Should().ContainSingle(); + var capturedTransaction = payloadSender.Payloads[0].Transactions[0]; + capturedTransaction.Name.Should().Be(transactionName); + capturedTransaction.Type.Should().Be(transactionType); + capturedTransaction.Duration.Should().BeGreaterOrEqualTo(5); + capturedTransaction.Id.Should().NotBeEmpty(); + + payloadSender.Payloads[0].Service.Should().NotBeNull(); } /// @@ -54,7 +56,7 @@ public void StartNoEndTransaction() var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); var unused = agent.Tracer.StartTransaction(transactionName, transactionType); - Assert.Empty(payloadSender.Payloads); + payloadSender.Payloads.Should().BeEmpty(); } /// @@ -74,7 +76,7 @@ public void TransactionResultTest() transaction.Result = result; transaction.End(); - Assert.Equal(result, payloadSender.Payloads[0].Transactions[0].Result); + payloadSender.Payloads[0].Transactions[0].Result.Should().Be(result); } /// @@ -86,7 +88,7 @@ public void GetCurrentTransactionWithNoTransaction() { var agent = new ApmAgent(new TestAgentComponents()); var currentTransaction = agent.Tracer.CurrentTransaction; - Assert.Null(currentTransaction); + currentTransaction.Should().BeNull(); } /// @@ -104,9 +106,9 @@ public async Task GetCurrentTransactionWithNotNull() var currentTransaction = agent.Tracer.CurrentTransaction; //Get transaction in the current task - Assert.NotNull(currentTransaction); - Assert.Equal(transactionName, currentTransaction.Name); - Assert.Equal(ApiConstants.TypeRequest, currentTransaction.Type); + currentTransaction.Should().NotBeNull(); + currentTransaction.Name.Should().Be(transactionName); + currentTransaction.Type.Should().Be(ApiConstants.TypeRequest); void StartTransaction() { @@ -117,16 +119,16 @@ void StartTransaction() async Task DoAsyncWork() { //Make sure we have a transaction in the subtask before the async work - Assert.NotNull(agent.Tracer.CurrentTransaction); - Assert.Equal(transactionName, agent.Tracer.CurrentTransaction.Name); - Assert.Equal(ApiConstants.TypeRequest, agent.Tracer.CurrentTransaction.Type); + agent.Tracer.CurrentTransaction.Should().NotBeNull(); + agent.Tracer.CurrentTransaction.Name.Should().Be(transactionName); + agent.Tracer.CurrentTransaction.Type.Should().Be(ApiConstants.TypeRequest); await Task.Delay(50); //and after the async work - Assert.NotNull(agent.Tracer.CurrentTransaction); - Assert.Equal(transactionName, agent.Tracer.CurrentTransaction.Name); - Assert.Equal(ApiConstants.TypeRequest, agent.Tracer.CurrentTransaction.Type); + agent.Tracer.CurrentTransaction.Should().NotBeNull(); + agent.Tracer.CurrentTransaction.Name.Should().Be(transactionName); + agent.Tracer.CurrentTransaction.Type.Should().Be(ApiConstants.TypeRequest); } } @@ -151,13 +153,12 @@ public void TransactionWithSpan() span.End(); transaction.End(); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(spanName, payloadSender.SpansOnFirstTransaction[0].Name); - Assert.True(payloadSender.SpansOnFirstTransaction[0].Duration >= 5); - Assert.True(payloadSender.SpansOnFirstTransaction[0].Id >= 5); - Assert.NotNull(payloadSender.Payloads[0].Service); + payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(spanName); + payloadSender.SpansOnFirstTransaction[0].Duration.Should().BeGreaterOrEqualTo(5); + payloadSender.Payloads[0].Service.Should().NotBeNull(); } /// @@ -180,10 +181,10 @@ public void TransactionWithSpanWithoutEnd() Thread.Sleep(5); //Make sure we have duration > 0 transaction.End(); //Ends transaction, but doesn't end span. - Assert.NotEmpty(payloadSender.Payloads); - Assert.Empty(payloadSender.SpansOnFirstTransaction); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.SpansOnFirstTransaction.Should().BeEmpty(); - Assert.NotNull(payloadSender.Payloads[0].Service); + payloadSender.Payloads[0].Service.Should().NotBeNull(); } /// @@ -204,14 +205,14 @@ public void TransactionWithSpanWithSubTypeAndAction() span.End(); transaction.End(); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(ApiConstants.TypeDb, payloadSender.SpansOnFirstTransaction[0].Type); - Assert.Equal(ApiConstants.SubtypeMssql, payloadSender.SpansOnFirstTransaction[0].Subtype); - Assert.Equal(ApiConstants.ActionQuery, payloadSender.SpansOnFirstTransaction[0].Action); + payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(ApiConstants.TypeDb); + payloadSender.SpansOnFirstTransaction[0].Subtype.Should().Be(ApiConstants.SubtypeMssql); + payloadSender.SpansOnFirstTransaction[0].Action.Should().Be(ApiConstants.ActionQuery); - Assert.NotNull(payloadSender.Payloads[0].Service); + payloadSender.Payloads[0].Service.Should().NotBeNull(); } /// @@ -256,12 +257,12 @@ private static void ErrorOnTransactionCommon(string culprit = null) transaction.End(); - Assert.Single(payloadSender.Payloads); - Assert.Single(payloadSender.Errors); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Payloads.Should().ContainSingle(); + payloadSender.Errors.Should().ContainSingle(); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(exceptionMessage); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(exceptionMessage); - Assert.Equal(!string.IsNullOrEmpty(culprit) ? culprit : "PublicAPI-CaptureException", payloadSender.Errors[0].Errors[0].Culprit); + payloadSender.Errors[0].Errors[0].Culprit.Should().Be(!string.IsNullOrEmpty(culprit) ? culprit : "PublicAPI-CaptureException"); } /// @@ -296,10 +297,9 @@ public void ErrorOnSpan() span.End(); transaction.End(); - Assert.Single(payloadSender.Payloads); - Assert.Single(payloadSender.Errors); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Payloads.Should().ContainSingle(); + payloadSender.Errors.Should().ContainSingle(); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(exceptionMessage); } /// @@ -338,22 +338,21 @@ public void TagsOnTransactionAndSpan() span.End(); transaction.End(); - Assert.Single(payloadSender.Payloads); - Assert.Single(payloadSender.Errors); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); - Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Payloads.Should().ContainSingle(); + payloadSender.Errors.Should().ContainSingle(); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(exceptionMessage); - Assert.Equal("barTransaction1", payloadSender.Payloads[0].Transactions[0].Tags["fooTransaction1"]); - Assert.Equal("barTransaction1", payloadSender.FirstTransaction.Context.Tags["fooTransaction1"]); + payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("fooTransaction1", "barTransaction1"); + payloadSender.FirstTransaction.Context.Tags.Should().Contain("fooTransaction1", "barTransaction1"); - Assert.Equal("barTransaction2", payloadSender.Payloads[0].Transactions[0].Tags["fooTransaction2"]); - Assert.Equal("barTransaction2", payloadSender.FirstTransaction.Context.Tags["fooTransaction2"]); + payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("fooTransaction2", "barTransaction2"); + payloadSender.FirstTransaction.Context.Tags.Should().Contain("fooTransaction2", "barTransaction2"); - Assert.Equal("barSpan1", payloadSender.SpansOnFirstTransaction[0].Tags["fooSpan1"]); - Assert.Equal("barSpan1", payloadSender.SpansOnFirstTransaction[0].Context.Tags["fooSpan1"]); + payloadSender.SpansOnFirstTransaction[0].Tags.Should().Contain("fooSpan1", "barSpan1"); + payloadSender.SpansOnFirstTransaction[0].Context.Tags.Should().Contain("fooSpan1", "barSpan1"); - Assert.Equal("barSpan2", payloadSender.SpansOnFirstTransaction[0].Tags["fooSpan2"]); - Assert.Equal("barSpan2", payloadSender.SpansOnFirstTransaction[0].Context.Tags["fooSpan2"]); + payloadSender.SpansOnFirstTransaction[0].Tags.Should().Contain("fooSpan2", "barSpan2"); + payloadSender.SpansOnFirstTransaction[0].Context.Tags.Should().Contain("fooSpan2", "barSpan2"); } } } diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs index 6953641fc..a954db5c0 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs @@ -3,7 +3,9 @@ using System.Threading.Tasks; using Elastic.Apm.Api; using Elastic.Apm.Model.Payload; +using Elastic.Apm.Tests.Extensions; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests.ApiTests @@ -46,14 +48,15 @@ public void SimpleAction() public void SimpleActionWithException() => AssertWith1TransactionAnd1SpanAnd1Error(t => { - Assert.Throws(() => + Action act = () => { t.CaptureSpan(SpanName, SpanType, new Action(() => { WaitHelpers.Sleep2XMinimum(); throw new InvalidOperationException(ExceptionMessage); })); - }); + }; + act.Should().Throw(); }); /// @@ -68,7 +71,7 @@ public void SimpleActionWithParameter() t.CaptureSpan(SpanName, SpanType, s => { - Assert.NotNull(s); + s.Should().NotBeNull(); WaitHelpers.Sleep2XMinimum(); }); }); @@ -85,15 +88,16 @@ public void SimpleActionWithParameter() public void SimpleActionWithExceptionAndParameter() => AssertWith1TransactionAnd1SpanAnd1Error(t => { - Assert.Throws(() => + Action act = () => { t.CaptureSpan(SpanName, SpanType, new Action(s => { - Assert.NotNull(s); + s.Should().NotBeNull(); WaitHelpers.Sleep2XMinimum(); throw new InvalidOperationException(ExceptionMessage); })); - }); + }; + act.Should().Throw().WithMessage(ExceptionMessage); }); /// @@ -111,7 +115,7 @@ public void SimpleActionWithReturnType() return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -127,12 +131,12 @@ public void SimpleActionWithReturnTypeAndParameter() { var res = t.CaptureSpan(SpanName, SpanType, s => { - Assert.NotNull(t); + t.Should().NotBeNull(); WaitHelpers.Sleep2XMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -147,11 +151,11 @@ public void SimpleActionWithReturnTypeAndParameter() public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1TransactionAnd1SpanAnd1Error(t => { - Assert.Throws(() => + Action act = () => { var result = t.CaptureSpan(SpanName, SpanType, s => { - Assert.NotNull(s); + s.Should().NotBeNull(); WaitHelpers.Sleep2XMinimum(); if (new Random().Next(1) == 0) //avoid unreachable code warning. @@ -159,10 +163,9 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + throw new Exception("CaptureSpan should not eat exception and continue"); + }; + act.Should().Throw().WithMessage(ExceptionMessage); }); /// @@ -177,21 +180,17 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() public void SimpleActionWithReturnTypeAndException() => AssertWith1TransactionAnd1SpanAnd1Error(t => { - Assert.Throws(() => + var alwaysThrow = new Random().Next(1) == 0; + Func act = () => t.CaptureSpan(SpanName, SpanType, () => { - var result = t.CaptureSpan(SpanName, SpanType, () => - { - WaitHelpers.Sleep2XMinimum(); - - if (new Random().Next(1) == 0) //avoid unreachable code warning. - throw new InvalidOperationException(ExceptionMessage); + WaitHelpers.Sleep2XMinimum(); - return 42; - }); + if (alwaysThrow) //avoid unreachable code warning. + throw new InvalidOperationException(ExceptionMessage); - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. + return 42; }); + act.Should().Throw().WithMessage(ExceptionMessage); }); /// @@ -216,14 +215,16 @@ public async Task AsyncTask() public async Task AsyncTaskWithException() => await AssertWith1TransactionAnd1ErrorAnd1SpanAsync(async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await t.CaptureSpan(SpanName, SpanType, async () => { await WaitHelpers.Delay2XMinimum(); throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + var should = await act.Should().ThrowAsync(); + should.WithMessage(ExceptionMessage); }); /// @@ -238,7 +239,7 @@ public async Task AsyncTaskWithParameter() await t.CaptureSpan(SpanName, SpanType, async s => { - Assert.NotNull(s); + s.Should().NotBeNull(); await WaitHelpers.Delay2XMinimum(); }); }); @@ -254,15 +255,16 @@ await t.CaptureSpan(SpanName, SpanType, public async Task AsyncTaskWithExceptionAndParameter() => await AssertWith1TransactionAnd1ErrorAnd1SpanAsync(async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await t.CaptureSpan(SpanName, SpanType, async s => { - Assert.NotNull(s); + s.Should().NotBeNull(); await WaitHelpers.Delay2XMinimum(); throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -279,7 +281,7 @@ public async Task AsyncTaskWithReturnType() await WaitHelpers.Delay2XMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -296,12 +298,12 @@ public async Task AsyncTaskWithReturnTypeAndParameter() var res = await t.CaptureSpan(SpanName, SpanType, async s => { - Assert.NotNull(s); + s.Should().NotBeNull(); await WaitHelpers.Delay2XMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -316,11 +318,11 @@ public async Task AsyncTaskWithReturnTypeAndParameter() public async Task AsyncTaskWithReturnTypeAndExceptionAndParameter() => await AssertWith1TransactionAnd1ErrorAnd1SpanAsync(async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { var result = await t.CaptureSpan(SpanName, SpanType, async s => { - Assert.NotNull(s); + s.Should().NotBeNull(); await WaitHelpers.Delay2XMinimum(); if (new Random().Next(1) == 0) //avoid unreachable code warning. @@ -328,10 +330,8 @@ await Assert.ThrowsAsync(async () => return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -344,7 +344,7 @@ await Assert.ThrowsAsync(async () => public async Task AsyncTaskWithReturnTypeAndException() => await AssertWith1TransactionAnd1ErrorAnd1SpanAsync(async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { var result = await t.CaptureSpan(SpanName, SpanType, async () => { @@ -355,10 +355,8 @@ await Assert.ThrowsAsync(async () => return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -376,7 +374,7 @@ public async Task CancelledAsyncTask() await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await t.CaptureSpan(SpanName, SpanType, async () => { @@ -384,7 +382,8 @@ await t.CaptureSpan(SpanName, SpanType, async () => await WaitHelpers.Delay2XMinimum(); token.ThrowIfCancellationRequested(); }); - }); + }; + act.Should().Throw(); }); } @@ -406,10 +405,10 @@ public void TagsOnSpan() }); //According to the Intake API tags are stored on the Context (and not on Spans.Tags directly). - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Context.Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Context.Tags.Should().Contain("foo","bar"); //Also make sure the tag is visible directly on Span.Tags. - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Tags.Should().Contain("foo","bar"); } /// @@ -430,10 +429,10 @@ await t.CaptureSpan(SpanName, SpanType, async span => }); //According to the Intake API tags are stored on the Context (and not on Spans.Tags directly). - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Context.Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Context.Tags.Should().Contain("foo","bar"); //Also make sure the tag is visible directly on Span.Tags. - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Tags.Should().Contain("foo","bar"); } /// @@ -443,27 +442,27 @@ await t.CaptureSpan(SpanName, SpanType, async span => [Fact] public async Task TagsOnSpanAsyncError() { - var payloadSender = await AssertWith1TransactionAnd1ErrorAnd1SpanAsync( - async t => + var payloadSender = await AssertWith1TransactionAnd1ErrorAnd1SpanAsync(async t => + { + Func act = async () => { - await Assert.ThrowsAsync(async () => + await t.CaptureSpan(SpanName, SpanType, async span => { - await t.CaptureSpan(SpanName, SpanType, async span => - { - await WaitHelpers.Delay2XMinimum(); - span.Tags["foo"] = "bar"; - - if (new Random().Next(1) == 0) //avoid unreachable code warning. - throw new InvalidOperationException(ExceptionMessage); - }); + await WaitHelpers.Delay2XMinimum(); + span.Tags["foo"] = "bar"; + + if (new Random().Next(1) == 0) //avoid unreachable code warning. + throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + act.Should().Throw(); + }); //According to the Intake API tags are stored on the Context (and not on Spans.Tags directly). - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Context.Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Context.Tags.Should().Contain("foo","bar"); //Also make sure the tag is visible directly on Span.Tags. - Assert.Equal("bar", payloadSender.SpansOnFirstTransaction[0].Tags["foo"]); + payloadSender.SpansOnFirstTransaction[0].Tags.Should().Contain("foo","bar"); } /// @@ -480,26 +479,26 @@ await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t await func(t); }); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.Assert3XMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(numberOfSleeps: 3); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(SpanName, payloadSender.SpansOnFirstTransaction[0].Name); - Assert.Equal(SpanType, payloadSender.SpansOnFirstTransaction[0].Type); + payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(SpanName); + payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(SpanType); - Assert.NotEmpty(payloadSender.Errors); - Assert.NotEmpty(payloadSender.Errors[0].Errors); + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors.Should().NotBeEmpty(); - Assert.Equal(typeof(InvalidOperationException).FullName, payloadSender.Errors[0].Errors[0].Exception.Type); - Assert.Equal(ExceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(InvalidOperationException).FullName); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(ExceptionMessage); return payloadSender; } @@ -519,19 +518,19 @@ await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t await func(t); }); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.Assert3XMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(numberOfSleeps: 3); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(SpanName, payloadSender.SpansOnFirstTransaction[0].Name); - Assert.Equal(SpanType, payloadSender.SpansOnFirstTransaction[0].Type); + payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(SpanName); + payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(SpanType); return payloadSender; } @@ -550,19 +549,19 @@ private MockPayloadSender AssertWith1TransactionAnd1Span(Action ac action(t); }); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(SpanName, payloadSender.SpansOnFirstTransaction[0].Name); - Assert.Equal(SpanType, payloadSender.SpansOnFirstTransaction[0].Type); + payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(SpanName); + payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(SpanType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.Assert3XMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(numberOfSleeps: 3); return payloadSender; } @@ -581,25 +580,25 @@ private void AssertWith1TransactionAnd1SpanAnd1Error(Action action action(t); }); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.Assert3XMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(numberOfSleeps: 3); - Assert.NotEmpty(payloadSender.SpansOnFirstTransaction); + payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); - Assert.Equal(SpanName, payloadSender.SpansOnFirstTransaction[0].Name); - Assert.Equal(SpanType, payloadSender.SpansOnFirstTransaction[0].Type); + payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(SpanName); + payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(SpanType); - Assert.NotEmpty(payloadSender.Errors); - Assert.NotEmpty(payloadSender.Errors[0].Errors); + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors.Should().NotBeEmpty(); - Assert.Equal(typeof(InvalidOperationException).FullName, payloadSender.Errors[0].Errors[0].Exception.Type); - Assert.Equal(ExceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(InvalidOperationException).FullName); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(ExceptionMessage); } } } diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs index 48034ed37..0480e4cc2 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs @@ -3,7 +3,9 @@ using System.Threading.Tasks; using Elastic.Apm.Api; using Elastic.Apm.Model.Payload; +using Elastic.Apm.Tests.Extensions; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests.ApiTests @@ -45,14 +47,15 @@ public void SimpleAction() => AssertWith1Transaction(agent => [Fact] public void SimpleActionWithException() => AssertWith1TransactionAnd1Error(agent => { - Assert.Throws(() => + Action act = () => { agent.Tracer.CaptureTransaction(TransactionName, TransactionType, new Action(() => { WaitHelpers.SleepMinimum(); throw new InvalidOperationException(ExceptionMessage); })); - }); + }; + act.Should().Throw(); }); /// @@ -68,7 +71,7 @@ public void SimpleActionWithParameter() => AssertWith1Transaction(agent => agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t => { - Assert.NotNull(t); + t.Should().NotBeNull(); WaitHelpers.SleepMinimum(); }); }); @@ -83,15 +86,16 @@ public void SimpleActionWithParameter() => AssertWith1Transaction(agent => [Fact] public void SimpleActionWithExceptionAndParameter() => AssertWith1TransactionAnd1Error(agent => { - Assert.Throws(() => + Action act = () => { agent.Tracer.CaptureTransaction(TransactionName, TransactionType, new Action(t => { - Assert.NotNull(t); + t.Should().NotBeNull(); WaitHelpers.SleepMinimum(); throw new InvalidOperationException(ExceptionMessage); })); - }); + }; + act.Should().Throw(); }); /// @@ -108,7 +112,7 @@ public void SimpleActionWithReturnType() => AssertWith1Transaction(agent => return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -124,12 +128,12 @@ public void SimpleActionWithReturnTypeAndParameter() => AssertWith1Transaction(a var res = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t => { - Assert.NotNull(t); + t.Should().NotBeNull(); WaitHelpers.SleepMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -143,11 +147,11 @@ public void SimpleActionWithReturnTypeAndParameter() => AssertWith1Transaction(a [Fact] public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1TransactionAnd1Error(agent => { - Assert.Throws(() => + Action act = () => { var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t => { - Assert.NotNull(t); + t.Should().NotBeNull(); WaitHelpers.SleepMinimum(); if (new Random().Next(1) == 0) //avoid unreachable code warning. @@ -155,10 +159,9 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1T return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + throw new Exception("CaptureTransaction should not eat exception and continue"); + }; + act.Should().Throw(); }); /// @@ -170,7 +173,7 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1T [Fact] public void SimpleActionWithReturnTypeAndException() => AssertWith1TransactionAnd1Error(agent => { - Assert.Throws(() => + Action act = () => { var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, () => { @@ -181,10 +184,9 @@ public void SimpleActionWithReturnTypeAndException() => AssertWith1TransactionAn return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + throw new Exception("CaptureTransaction should not eat exception and continue"); + }; + act.Should().Throw(); }); /// @@ -207,14 +209,15 @@ await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, [Fact] public async Task AsyncTaskWithException() => await AssertWith1TransactionAnd1ErrorAsync(async agent => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () => { await WaitHelpers.DelayMinimum(); throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -229,7 +232,7 @@ public async Task AsyncTaskWithParameter() => await AssertWith1TransactionAsync( await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { - Assert.NotNull(t); + t.Should().NotBeNull(); await WaitHelpers.DelayMinimum(); }); }); @@ -245,15 +248,16 @@ await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, [Fact] public async Task AsyncTaskWithExceptionAndParameter() => await AssertWith1TransactionAnd1ErrorAsync(async agent => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { - Assert.NotNull(t); + t.Should().NotBeNull(); await WaitHelpers.DelayMinimum(); throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -269,7 +273,7 @@ public async Task AsyncTaskWithReturnType() => await AssertWith1TransactionAsync await WaitHelpers.DelayMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -285,12 +289,12 @@ public async Task AsyncTaskWithReturnTypeAndParameter() => await AssertWith1Tran var res = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { - Assert.NotNull(t); + t.Should().NotBeNull(); await WaitHelpers.DelayMinimum(); return 42; }); - Assert.Equal(42, res); + res.Should().Be(42); }); /// @@ -304,11 +308,11 @@ public async Task AsyncTaskWithReturnTypeAndParameter() => await AssertWith1Tran [Fact] public async Task AsyncTaskWithReturnTypeAndExceptionAndParameter() => await AssertWith1TransactionAnd1ErrorAsync(async agent => { - await Assert.ThrowsAsync(async () => + Func act = async () => { var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { - Assert.NotNull(t); + t.Should().NotBeNull(); await WaitHelpers.DelayMinimum(); if (new Random().Next(1) == 0) //avoid unreachable code warning. @@ -316,10 +320,8 @@ await Assert.ThrowsAsync(async () => return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -331,7 +333,7 @@ await Assert.ThrowsAsync(async () => [Fact] public async Task AsyncTaskWithReturnTypeAndException() => await AssertWith1TransactionAnd1ErrorAsync(async agent => { - await Assert.ThrowsAsync(async () => + Func act = async () => { var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () => { @@ -342,10 +344,8 @@ await Assert.ThrowsAsync(async () => return 42; }); - - Assert.True(false); //Should not be executed because the agent isn't allowed to catch an exception. - Assert.Equal(42, result); //But if it'd not throw it'd be 42. - }); + }; + await act.Should().ThrowAsync(); }); /// @@ -362,7 +362,7 @@ public async Task CancelledAsyncTask() var token = cancellationTokenSource.Token; cancellationTokenSource.Cancel(); - await Assert.ThrowsAsync(async () => + Func act = async () => { await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () => @@ -371,22 +371,23 @@ await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, await WaitHelpers.DelayMinimum(); token.ThrowIfCancellationRequested(); }); - }); + }; + await act.Should().ThrowAsync(); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - Assert.True(duration >= WaitHelpers.SleepLength, $"Expected {duration} to be greater or equal to: {WaitHelpers.SleepLength}"); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(); - Assert.NotEmpty(payloadSender.Errors); - Assert.NotEmpty(payloadSender.Errors[0].Errors); + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors.Should().NotBeEmpty(); - Assert.Equal("A task was canceled", payloadSender.Errors[0].Errors[0].Culprit); - Assert.Equal("Task canceled", payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Errors[0].Errors[0].Culprit.Should().Be("A task was canceled"); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be("Task canceled"); } /// @@ -407,10 +408,10 @@ public void TagsOnTransaction() }); //According to the Intake API tags are stored on the Context (and not on Transaction.Tags directly). - Assert.Equal("bar", payloadSender.FirstTransaction.Context.Tags["foo"]); + payloadSender.FirstTransaction.Context.Tags.Should().Contain("foo", "bar"); //Also make sure the tag is visible directly on Transaction.Tags. - Assert.Equal("bar", payloadSender.Payloads[0].Transactions[0].Tags["foo"]); + payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("foo", "bar"); } /// @@ -431,10 +432,10 @@ await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transa }); //According to the Intake API tags are stored on the Context (and not on Transaction.Tags directly). - Assert.Equal("bar", payloadSender.FirstTransaction.Context.Tags["foo"]); + payloadSender.FirstTransaction.Context.Tags.Should().Contain("foo", "bar"); //Also make sure the tag is visible directly on Transaction.Tags. - Assert.Equal("bar", payloadSender.Payloads[0].Transactions[0].Tags["foo"]); + payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("foo", "bar"); } /// @@ -447,7 +448,7 @@ public async Task TagsOnTransactionAsyncError() var payloadSender = await AssertWith1TransactionAnd1ErrorAsync( async t => { - await Assert.ThrowsAsync(async () => + Func act = async () => { await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transaction => { @@ -457,14 +458,15 @@ await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transa if (new Random().Next(1) == 0) //avoid unreachable code warning. throw new InvalidOperationException(ExceptionMessage); }); - }); + }; + await act.Should().ThrowAsync(); }); //According to the Intake API tags are stored on the Context (and not on Transaction.Tags directly). - Assert.Equal("bar", payloadSender.FirstTransaction.Context.Tags["foo"]); + payloadSender.FirstTransaction.Context.Tags.Should().Contain("foo", "bar"); //Also make sure the tag is visible directly on Transaction.Tags. - Assert.Equal("bar", payloadSender.Payloads[0].Transactions[0].Tags["foo"]); + payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("foo", "bar"); } /// @@ -477,14 +479,14 @@ private async Task AssertWith1TransactionAsync(Func AssertWith1TransactionAnd1ErrorAsync(Func< await func(agent); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.AssertMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(); - Assert.NotEmpty(payloadSender.Errors); - Assert.NotEmpty(payloadSender.Errors[0].Errors); + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors.Should().NotBeEmpty(); - Assert.Equal(typeof(InvalidOperationException).FullName, payloadSender.Errors[0].Errors[0].Exception.Type); - Assert.Equal(ExceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(InvalidOperationException).FullName); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(ExceptionMessage); return payloadSender; } @@ -526,14 +528,14 @@ private MockPayloadSender AssertWith1Transaction(Action action) action(agent); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.AssertMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(); return payloadSender; } @@ -548,20 +550,20 @@ private void AssertWith1TransactionAnd1Error(Action action) action(agent); - Assert.NotEmpty(payloadSender.Payloads); - Assert.NotEmpty(payloadSender.Payloads[0].Transactions); + payloadSender.Payloads.Should().NotBeEmpty(); + payloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); - Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name); - Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type); + payloadSender.Payloads[0].Transactions[0].Name.Should().Be(TransactionName); + payloadSender.Payloads[0].Transactions[0].Type.Should().Be(TransactionType); var duration = payloadSender.Payloads[0].Transactions[0].Duration; - WaitHelpers.AssertMinimumSleepLength(duration); + duration.Should().BeGreaterOrEqualToMinimumSleepLength(); - Assert.NotEmpty(payloadSender.Errors); - Assert.NotEmpty(payloadSender.Errors[0].Errors); + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors.Should().NotBeEmpty(); - Assert.Equal(typeof(InvalidOperationException).FullName, payloadSender.Errors[0].Errors[0].Exception.Type); - Assert.Equal(ExceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message); + payloadSender.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(InvalidOperationException).FullName); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(ExceptionMessage); } } } diff --git a/test/Elastic.Apm.Tests/BasicAgentTests.cs b/test/Elastic.Apm.Tests/BasicAgentTests.cs index cd52fad08..ac3b15886 100644 --- a/test/Elastic.Apm.Tests/BasicAgentTests.cs +++ b/test/Elastic.Apm.Tests/BasicAgentTests.cs @@ -11,6 +11,7 @@ using Elastic.Apm.Model.Payload; using Elastic.Apm.Report; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; [assembly: @@ -37,7 +38,7 @@ public void AgentVersion() using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender))) agent.Tracer.CaptureTransaction("TestName", "TestType", () => { Thread.Sleep(5); }); - Assert.Equal(Assembly.Load("Elastic.Apm").GetName().Version.ToString(), payloadSender.Payloads[0].Service.Agent.Version); + payloadSender.Payloads[0].Service.Agent.Version.Should().Be(Assembly.Load("Elastic.Apm").GetName().Version.ToString()); } /// @@ -57,12 +58,12 @@ public void SpanNameLengthTest() agent.Tracer.CaptureTransaction("TestTransaction", "Test", (t) => { t.CaptureSpan(spanName.ToString(), "test", () => { }); }); - Assert.NotNull(payloadSender.FirstSpan); - Assert.Equal(1024, payloadSender.FirstSpan.Name.Length); - Assert.Equal(spanName.ToString(0, 1021), payloadSender.FirstSpan.Name.Substring(0, 1021)); - Assert.Equal("...", payloadSender.FirstSpan.Name.Substring(1021, 3)); + payloadSender.FirstSpan.Should().NotBeNull(); + payloadSender.FirstSpan.Name.Length.Should().Be(1024); + spanName.ToString(0, 1021).Should().Be(payloadSender.FirstSpan.Name.Substring(0, 1021)); + payloadSender.FirstSpan.Name.Substring(1021, 3).Should().Be("..."); } - + [Fact] public void PayloadSentWithBearerToken() { @@ -83,9 +84,9 @@ public void PayloadSentWithBearerToken() // ideally, introduce a mechanism to flush payloads Thread.Sleep(TimeSpan.FromSeconds(2)); - Assert.NotNull(authHeader); - Assert.Equal("Bearer", authHeader.Scheme); - Assert.Equal(secretToken, authHeader.Parameter); + authHeader.Should().NotBeNull(); + authHeader.Scheme.Should().Be("Bearer"); + authHeader.Parameter.Should().Be(secretToken); } } } diff --git a/test/Elastic.Apm.Tests/ConfigTest.cs b/test/Elastic.Apm.Tests/ConfigTest.cs index 51c51837f..5534dc2e3 100644 --- a/test/Elastic.Apm.Tests/ConfigTest.cs +++ b/test/Elastic.Apm.Tests/ConfigTest.cs @@ -4,6 +4,7 @@ using Elastic.Apm.Config; using Elastic.Apm.Logging; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests @@ -18,8 +19,9 @@ public void ServerUrlsSimpleTest() { var serverUrl = "http://myServer.com:1234"; var agent = new ApmAgent(new TestAgentComponents(serverUrls: serverUrl)); - Assert.Equal(serverUrl, agent.ConfigurationReader.ServerUrls[0].OriginalString); - Assert.Equal(serverUrl.ToLower() + "/", agent.ConfigurationReader.ServerUrls[0].ToString().ToLower()); + agent.ConfigurationReader.ServerUrls[0].OriginalString.Should().Be(serverUrl); + var rootedUrl = serverUrl + "/"; + rootedUrl.Should().BeEquivalentTo(agent.ConfigurationReader.ServerUrls[0].AbsoluteUri); } [Fact] @@ -27,7 +29,7 @@ public void ServerUrlsInvalidUrlTest() { var serverUrl = "InvalidUrl"; var agent = new ApmAgent(new TestAgentComponents(serverUrls: serverUrl)); - Assert.Equal(ConfigConsts.DefaultServerUri.ToString(), agent.ConfigurationReader.ServerUrls[0].ToString()); + agent.ConfigurationReader.ServerUrls[0].Should().Be(ConfigConsts.DefaultServerUri); } [Fact] @@ -36,11 +38,17 @@ public void ServerUrlInvalidUrlLogTest() var serverUrl = "InvalidUrl"; var logger = new TestLogger(); var agent = new ApmAgent(new TestAgentComponents(logger, serverUrl)); - Assert.Equal(ConfigConsts.DefaultServerUri.ToString(), agent.ConfigurationReader.ServerUrls[0].ToString()); - - Assert.Equal( - $"{{{nameof(TestAgentConfigurationReader)}}} Failed parsing server URL from {TestAgentConfigurationReader.Origin}: {ConfigConsts.ConfigKeys.Urls}, value: {serverUrl}", - logger.Lines[0]); + agent.ConfigurationReader.ServerUrls[0].Should().Be(ConfigConsts.DefaultServerUri); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0].Should() + .StartWith($"{{{nameof(TestAgentConfigurationReader)}}}") + .And.ContainAll( + "Failed parsing server URL from", + TestAgentConfigurationReader.Origin, + ConfigConsts.ConfigKeys.Urls, + serverUrl + ); } /// @@ -56,11 +64,12 @@ public void ServerUrlsMultipleUrlsTest() var logger = new TestLogger(); var agent = new ApmAgent(new TestAgentComponents(logger, serverUrls)); - Assert.Equal(serverUrl1, agent.ConfigurationReader.ServerUrls[0].OriginalString); - Assert.Equal(serverUrl1.ToLower() + "/", agent.ConfigurationReader.ServerUrls[0].ToString().ToLower()); + var parsedUrls = agent.ConfigurationReader.ServerUrls; + parsedUrls[0].OriginalString.Should().Be(serverUrl1); + parsedUrls[0].AbsoluteUri.Should().BeEquivalentTo($"{serverUrl1}/"); - Assert.Equal(serverUrl2, agent.ConfigurationReader.ServerUrls[1].OriginalString); - Assert.Equal(serverUrl2.ToLower() + "/", agent.ConfigurationReader.ServerUrls[1].ToString().ToLower()); + parsedUrls[1].OriginalString.Should().Be(serverUrl2); + parsedUrls[1].AbsoluteUri.Should().BeEquivalentTo($"{serverUrl2}/"); } /// @@ -77,15 +86,23 @@ public void ServerUrlsMultipleUrlsWith1InvalidUrlTest() var logger = new TestLogger(); var agent = new ApmAgent(new TestAgentComponents(logger, serverUrls)); - Assert.Equal(serverUrl1, agent.ConfigurationReader.ServerUrls[0].OriginalString); - Assert.Equal(serverUrl1.ToLower() + "/", agent.ConfigurationReader.ServerUrls[0].ToString().ToLower()); - - Assert.Equal(serverUrl3, agent.ConfigurationReader.ServerUrls[1].OriginalString); - Assert.Equal(serverUrl3.ToLower() + "/", agent.ConfigurationReader.ServerUrls[1].ToString().ToLower()); - - Assert.Equal( - $"{{{nameof(TestAgentConfigurationReader)}}} Failed parsing server URL from {TestAgentConfigurationReader.Origin}: {ConfigConsts.ConfigKeys.Urls}, value: {serverUrl2}", - logger.Lines[0]); + var parsedUrls = agent.ConfigurationReader.ServerUrls; + parsedUrls.Should().NotBeEmpty().And.HaveCount(2, "seeded 3 but one was invalid"); + parsedUrls[0].OriginalString.Should().Be(serverUrl1); + parsedUrls[0].AbsoluteUri.Should().BeEquivalentTo($"{serverUrl1}/"); + + parsedUrls[1].OriginalString.Should().Be(serverUrl3); + parsedUrls[1].AbsoluteUri.Should().BeEquivalentTo($"{serverUrl3}/"); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0].Should() + .StartWith($"{{{nameof(TestAgentConfigurationReader)}}}") + .And.ContainAll( + "Failed parsing server URL from", + TestAgentConfigurationReader.Origin, + ConfigConsts.ConfigKeys.Urls, + serverUrl2 + ); } [Fact] @@ -93,38 +110,38 @@ public void SecretTokenSimpleTest() { var secretToken = "secretToken"; var agent = new ApmAgent(new TestAgentComponents(secretToken: secretToken)); - Assert.Equal(secretToken, agent.ConfigurationReader.SecretToken); + agent.ConfigurationReader.SecretToken.Should().Be(secretToken); } [Fact] - public void DefaultLogLevelTest() => Assert.Equal(LogLevel.Error, Agent.Config.LogLevel); + public void DefaultLogLevelTest() => Agent.Config.LogLevel.Should().Be(LogLevel.Error); [Fact] public void SetDebugLogLevelTest() { var agent = new ApmAgent(new TestAgentComponents("Debug")); - Assert.Equal(LogLevel.Debug, agent.ConfigurationReader.LogLevel); + agent.ConfigurationReader.LogLevel.Should().Be(LogLevel.Debug); } [Fact] public void SetErrorLogLevelTest() { var agent = new ApmAgent(new TestAgentComponents("Error")); - Assert.Equal(LogLevel.Error, agent.ConfigurationReader.LogLevel); + agent.ConfigurationReader.LogLevel.Should().Be(LogLevel.Error); } [Fact] public void SetInfoLogLevelTest() { var agent = new ApmAgent(new TestAgentComponents("Information")); - Assert.Equal(LogLevel.Information, agent.ConfigurationReader.LogLevel); + agent.ConfigurationReader.LogLevel.Should().Be(LogLevel.Information); } [Fact] public void SetWarningLogLevelTest() { var agent = new ApmAgent(new TestAgentComponents("Warning")); - Assert.Equal(LogLevel.Warning, agent.ConfigurationReader.LogLevel); + agent.ConfigurationReader.LogLevel.Should().Be(LogLevel.Warning); } [Fact] @@ -134,10 +151,16 @@ public void SetInvalidLogLevelTest() var agent = new ApmAgent(new TestAgentComponents(logLevelValue)); var logger = agent.Logger as TestLogger; - Assert.Equal(LogLevel.Error, agent.ConfigurationReader.LogLevel); - Assert.Equal( - $"{{{nameof(TestAgentConfigurationReader)}}} Failed parsing log level from {TestAgentConfigurationReader.Origin}: {ConfigConsts.ConfigKeys.Level}, value: {logLevelValue}. Defaulting to log level 'Error'", - logger.Lines[0]); + agent.ConfigurationReader.LogLevel.Should().Be(LogLevel.Error); + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0].Should() + .StartWith($"{{{nameof(TestAgentConfigurationReader)}}}") + .And.ContainAll( + "Failed parsing log level from", + TestAgentConfigurationReader.Origin, + ConfigConsts.ConfigKeys.Level, + "Defaulting to " + ); } /// @@ -153,8 +176,9 @@ public void DefaultServiceNameTest() //By default XUnit uses 'testhost' as the entry assembly, and that is what the //agent reports if we don't set it to anything: - Assert.False(string.IsNullOrEmpty(payloadSender.Payloads[0].Service.Name)); - Assert.False(payloadSender.Payloads[0].Service.Name.Contains('.')); + var serviceName = payloadSender.Payloads[0].Service.Name; + serviceName.Should().NotBeNullOrWhiteSpace(); + serviceName.Should().NotContain("."); } /// @@ -171,7 +195,7 @@ public void ReadServiceNameViaEnvironmentVariable() var agent = new ApmAgent(new AgentComponents(payloadSender: payloadSender)); agent.Tracer.CaptureTransaction("TestTransactionName", "TestTransactionType", t => { Thread.Sleep(2); }); - Assert.Equal(serviceName, payloadSender.Payloads[0].Service.Name); + payloadSender.Payloads[0].Service.Name.Should().Be(serviceName); } /// @@ -189,8 +213,9 @@ public void ReadServiceNameWithDotViaEnvironmentVariable() var agent = new ApmAgent(new AgentComponents(payloadSender: payloadSender)); agent.Tracer.CaptureTransaction("TestTransactionName", "TestTransactionType", t => { Thread.Sleep(2); }); - Assert.Equal(serviceName.Replace('.', '_'), payloadSender.Payloads[0].Service.Name); - Assert.False(payloadSender.Payloads[0].Service.Name.Contains('.')); + + payloadSender.Payloads[0].Service.Name.Should().Be(serviceName.Replace('.', '_')); + payloadSender.Payloads[0].Service.Name.Should().NotContain("."); } /// @@ -204,18 +229,17 @@ public void TestAbstractConfigurationReaderIsMsOrElastic() var elasticToken = new byte[] { 174, 116, 0, 210, 193, 137, 207, 34 }; var mscorlibToken = new byte[] { 183, 122, 92, 86, 25, 52, 224, 137 }; - Assert.True(AbstractConfigurationReader.IsMsOrElastic(elasticToken)); - Assert.True(AbstractConfigurationReader.IsMsOrElastic(elasticToken)); + AbstractConfigurationReader.IsMsOrElastic(elasticToken).Should().BeTrue(); - Assert.False(AbstractConfigurationReader.IsMsOrElastic(new byte[] { 0 })); - Assert.False(AbstractConfigurationReader.IsMsOrElastic(new byte[] { })); + AbstractConfigurationReader.IsMsOrElastic(new byte[] { 0 }).Should().BeFalse(); + AbstractConfigurationReader.IsMsOrElastic(new byte[] { }).Should().BeFalse(); - Assert.False(AbstractConfigurationReader + AbstractConfigurationReader .IsMsOrElastic(new[] { elasticToken[0], mscorlibToken[1], elasticToken[2], mscorlibToken[3], elasticToken[4], mscorlibToken[5], elasticToken[6], mscorlibToken[7] - })); + }).Should().BeFalse(); } /// @@ -230,8 +254,8 @@ public void LoggerNotNull() var config = new EnvironmentConfigurationReader(testLogger); var serverUrl = config.ServerUrls.FirstOrDefault(); - Assert.NotNull(serverUrl); - Assert.NotEmpty(testLogger.Lines); + serverUrl.Should().NotBeNull(); + testLogger.Lines.Should().NotBeEmpty(); } public void Dispose() => Environment.SetEnvironmentVariable(ConfigConsts.ConfigKeys.Urls, null); diff --git a/test/Elastic.Apm.Tests/Elastic.Apm.Tests.csproj b/test/Elastic.Apm.Tests/Elastic.Apm.Tests.csproj index 4b367c277..de1be3bb9 100644 --- a/test/Elastic.Apm.Tests/Elastic.Apm.Tests.csproj +++ b/test/Elastic.Apm.Tests/Elastic.Apm.Tests.csproj @@ -8,9 +8,12 @@ true ..\..\build\elasticapm.snk false + + True + diff --git a/test/Elastic.Apm.Tests/Extensions/ShouldWaitDurationExtensions.cs b/test/Elastic.Apm.Tests/Extensions/ShouldWaitDurationExtensions.cs new file mode 100644 index 000000000..4caef672c --- /dev/null +++ b/test/Elastic.Apm.Tests/Extensions/ShouldWaitDurationExtensions.cs @@ -0,0 +1,19 @@ +using System.Threading; +using Elastic.Apm.Tests.Extensions; +using FluentAssertions; +using FluentAssertions.Numeric; + +namespace Elastic.Apm.Tests +{ + public static class ShouldWaitDurationExtensions + { + public static AndConstraint> BeGreaterOrEqualToMinimumSleepLength(this NullableNumericAssertions duration) => + duration.NotBeNull().And.BeGreaterOrEqualTo(WaitHelpers.SleepLength); + + public static AndConstraint> BeGreaterOrEqualToMinimumSleepLength(this NullableNumericAssertions duration, int numberOfSleeps) + { + var expectedTransactionLength = numberOfSleeps * WaitHelpers.SleepLength; + return duration.NotBeNull().And.BeGreaterOrEqualTo(expectedTransactionLength, $"we expected {numberOfSleeps} to influence the total duration"); + } + } +} diff --git a/test/Elastic.Apm.Tests/WaitExtensions.cs b/test/Elastic.Apm.Tests/Extensions/WaitExtensions.cs similarity index 69% rename from test/Elastic.Apm.Tests/WaitExtensions.cs rename to test/Elastic.Apm.Tests/Extensions/WaitExtensions.cs index 65c94690b..1fcf8c845 100644 --- a/test/Elastic.Apm.Tests/WaitExtensions.cs +++ b/test/Elastic.Apm.Tests/Extensions/WaitExtensions.cs @@ -1,9 +1,10 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using FluentAssertions; using Xunit; -namespace Elastic.Apm.Tests +namespace Elastic.Apm.Tests.Extensions { /// /// Helper class that runs Task.Delay and Thread.Sleep @@ -32,15 +33,7 @@ public static int SleepLength public static void SleepMinimum() => Thread.Sleep(SleepLength); - public static int A(this string str) => 24; - - public static void AssertMinimumSleepLength(double? duration) - => Assert.True(duration >= SleepLength, $"Expected {duration} to be greater or equal to: {SleepLength}"); - - public static void Assert3XMinimumSleepLength(double? duration) - { - var expectedTransactionLength = SleepLength + 2 * SleepLength; - Assert.True(duration >= expectedTransactionLength, $"Expected {duration} to be greater or equal to: {expectedTransactionLength}"); - } + public static void Assert3XMinimumSleepLength(double? duration) => duration.Should().BeGreaterOrEqualToMinimumSleepLength(numberOfSleeps: 3); } + } diff --git a/test/Elastic.Apm.Tests/HttpDiagnosticListenerTest.cs b/test/Elastic.Apm.Tests/HttpDiagnosticListenerTest.cs index 6803a3bf0..4791a0ccb 100644 --- a/test/Elastic.Apm.Tests/HttpDiagnosticListenerTest.cs +++ b/test/Elastic.Apm.Tests/HttpDiagnosticListenerTest.cs @@ -1,541 +1,556 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Elastic.Apm.Api; -using Elastic.Apm.DiagnosticListeners; -using Elastic.Apm.DiagnosticSource; -using Elastic.Apm.Logging; -using Elastic.Apm.Model.Payload; -using Elastic.Apm.Tests.Mocks; -using Xunit; - -namespace Elastic.Apm.Tests -{ - public class HttpDiagnosticListenerTest - { - /// - /// Calls the OnError method on the HttpDiagnosticListener and makes sure that the correct error message is logged. - /// - [Fact] - public void OnErrorLog() - { - var logger = new TestLogger(); - var agent = new ApmAgent(new TestAgentComponents(logger)); - var listener = new HttpDiagnosticListener(agent); - - var exceptionMessage = "Ooops, this went wrong"; - var fakeException = new Exception(exceptionMessage); - listener.OnError(fakeException); - - Assert.Equal($"{{{nameof(HttpDiagnosticListener)}}} {nameof(Exception)} in OnError ({nameof(HttpDiagnosticListener)}.cs:38): {exceptionMessage}", - logger.Lines?.FirstOrDefault()); - } - - /// - /// Builds an HttpRequestMessage and calls HttpDiagnosticListener.OnNext directly with it. - /// Makes sure that the processingRequests dictionary captures the ongoing transaction. - /// - [Fact] - public void OnNextWithStart() - { - var logger = new TestLogger(); - var agent = new ApmAgent(new TestAgentComponents(logger)); - StartTransaction(agent); - var listener = new HttpDiagnosticListener(agent); - - var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); - - //Simulate Start - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); - Assert.Single(listener.ProcessingRequests); - Assert.Equal(request.RequestUri.ToString(), listener.ProcessingRequests[request].Context.Http.Url); - Assert.Equal(HttpMethod.Get.ToString(), listener.ProcessingRequests[request].Context.Http.Method); - } - - /// - /// Simulates the complete lifecycle of an HTTP request. - /// It builds an HttpRequestMessage and an HttpResponseMessage - /// and passes them to the OnNext method. - /// Makes sure that a Span with an Http context is captured. - /// - [Fact] - public void OnNextWithStartAndStop() - { - var logger = new TestLogger(); - var payloadSender = new MockPayloadSender(); - var agent = new ApmAgent(new TestAgentComponents(logger, payloadSender: payloadSender)); - StartTransaction(agent); - var listener = new HttpDiagnosticListener(agent); - - var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); - var response = new HttpResponseMessage(HttpStatusCode.OK); - - //Simulate Start - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); - //Simulate Stop - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); - Assert.Empty(listener.ProcessingRequests); - - Assert.Equal(request.RequestUri.ToString(), (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Url); - Assert.Equal(HttpMethod.Get.ToString(), (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Method); - } - - /// - /// Calls OnNext with System.Net.Http.HttpRequestOut.Stop twice. - /// Makes sure that the transaction is only captured once and the span is also only captured once. - /// Also make sure that there is an error log. - /// - [Fact] - public void OnNextWithStartAndStopTwice() - { - var logger = new TestLogger(LogLevel.Warning); - var agent = new ApmAgent(new TestAgentComponents(logger)); - StartTransaction(agent); - var listener = new HttpDiagnosticListener(agent); - - var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); - var response = new HttpResponseMessage(HttpStatusCode.OK); - - //Simulate Start - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); - //Simulate Stop - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); - //Simulate Stop again. This should not happen, still we test for this. - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); - - Assert.Equal( - $"{{{nameof(HttpDiagnosticListener)}}} Failed capturing request '{HttpMethod.Get} {request.RequestUri}' in System.Net.Http.HttpRequestOut.Stop. This Span will be skipped in case it wasn't captured before.", - logger.Lines[0]); - Assert.NotNull(Agent.TransactionContainer.Transactions.Value); - Assert.Single(Agent.TransactionContainer.Transactions.Value.Spans); - } - - /// - /// Calls HttpDiagnosticListener.OnNext with types that are unknown. - /// The test makes sure that in this case still no exception is thrown from the OnNext method. - /// - [Fact] - public void UnknownObjectToOnNext() - { - var logger = new TestLogger(); - var agent = new ApmAgent(new TestAgentComponents(logger)); - var listener = new HttpDiagnosticListener(agent); - var myFake = new StringBuilder(); //just a random type that is not planned to be passed into OnNext - - var exception = - Record.Exception(() => { listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", myFake)); }); - - Assert.Null(exception); - } - - /// - /// Passes null instead of a valid HttpRequestMessage into HttpDiagnosticListener.OnNext - /// and makes sure that still no exception is thrown. - /// - [Fact] - public void NullValueToOnNext() - { - var logger = new TestLogger(); - var agent = new ApmAgent(new TestAgentComponents(logger)); - var listener = new HttpDiagnosticListener(agent); - - var exception = - Record.Exception(() => { listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", null)); }); - - Assert.Null(exception); - } - - /// - /// Passes a null key with null value instead of a valid HttpRequestMessage into HttpDiagnosticListener.OnNext - /// and makes sure that still no exception is thrown. - /// - [Fact] - public void NullKeyValueToOnNext() - { - var logger = new TestLogger(); - var agent = new ApmAgent(new TestAgentComponents(logger)); - var listener = new HttpDiagnosticListener(agent); - - var exception = - Record.Exception(() => { listener.OnNext(new KeyValuePair(null, null)); }); - - Assert.Null(exception); - } - - /// - /// Sends a simple real HTTP GET message and makes sure that - /// HttpDiagnosticListener captures it. - /// - [Fact] - public async Task TestSimpleOutgoingHttpRequest() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer()) - { - var httpClient = new HttpClient(); - var res = await httpClient.GetAsync(localServer.Uri); - - Assert.True(res.IsSuccessStatusCode); - Assert.Equal(localServer.Uri, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Url); - } - - Assert.Equal(200, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.StatusCode); - Assert.Equal(HttpMethod.Get.ToString(), (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Method); - } - - /// - /// Sends a simple real HTTP POST message and the server responds with 500 - /// The test makes sure HttpDiagnosticListener captures the POST method and - /// the response code correctly - /// - [Fact] - public async Task TestNotSuccessfulOutgoingHttpPostRequest() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer(ctx => { ctx.Response.StatusCode = 500; })) - { - var httpClient = new HttpClient(); - var res = await httpClient.PostAsync(localServer.Uri, new StringContent("foo")); - - Assert.False(res.IsSuccessStatusCode); - Assert.Equal(localServer.Uri, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Url); - } - - Assert.Equal(500, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.StatusCode); - Assert.Equal(HttpMethod.Post.ToString(), (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Method); - } - - /// - /// Starts an HTTP call to a non existing URL and makes sure that an error is captured. - /// This uses an HttpClient instance directly - /// - [Fact] - public async Task CaptureErrorOnFailingHttpCall_HttpClient() - { - var (listener, payloadSender, _) = RegisterListenerAndStartTransaction(); - - using (listener) - { - var httpClient = new HttpClient(); - try - { - await httpClient.GetAsync("http://nonexistenturl_dsfdsf.ghkdehfn"); - Assert.True(false); //Make it fail if no exception is thrown - } - catch (Exception e) - { - Assert.NotNull(e); - } - finally - { - listener.Dispose(); - } - } - - Assert.NotEmpty(payloadSender.Errors); - } - - /// - /// Passes an exception to and makes sure that the exception is captured - /// Unlike the method this does not use HttpClient, instead here we - /// call the OnNext method directly. - /// - [Fact] - public void CaptureErrorOnFailingHttpCall_DirectCall() - { - var (disposableListener, payloadSender, agent) = RegisterListenerAndStartTransaction(); - - using (disposableListener) - { - var listener = new HttpDiagnosticListener(agent); - - var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); - - //Simulate Start - listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); - - var exceptionMsg = "Sample exception msg"; - var exception = new Exception(exceptionMsg); - //Simulate Exception - listener.OnNext(new KeyValuePair("System.Net.Http.Exception", new { Request = request, Exception = exception })); - - Assert.NotEmpty(payloadSender.Errors); - Assert.Equal(exceptionMsg, payloadSender.Errors[0].Errors[0].Exception.Message); - Assert.Equal(typeof(Exception).FullName, payloadSender.Errors[0].Errors[0].Exception.Type); - } - } - - /// - /// Makes sure we set the correct type and subtype for external, http spans - /// - [Fact] - public async Task SpanTypeAndSubtype() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer()) - { - var httpClient = new HttpClient(); - var res = await httpClient.GetAsync(localServer.Uri); - - Assert.True(res.IsSuccessStatusCode); - } - - Assert.Equal(ApiConstants.TypeExternal, Agent.TransactionContainer.Transactions.Value.Spans[0].Type); - Assert.Equal(ApiConstants.SubtypeHttp, Agent.TransactionContainer.Transactions.Value.Spans[0].Subtype); - Assert.Null(Agent.TransactionContainer.Transactions.Value.Spans[0].Action); //we don't set Action for HTTP calls - } - - /// - /// Makes sure we generate the correct span name - /// - [Fact] - public async Task SpanName() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer()) - { - var httpClient = new HttpClient(); - var res = await httpClient.GetAsync(localServer.Uri); - - Assert.True(res.IsSuccessStatusCode); - } - - Assert.Equal("GET localhost", Agent.TransactionContainer.Transactions.Value.Spans[0].Name); - } - - /// - /// Makes sure that the duration of an HTTP Request is captured by the agent - /// - /// The request duration. - [Fact] - public async Task HttpRequestDuration() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer(ctx => - { - ctx.Response.StatusCode = 200; - Thread.Sleep(5); //Make sure duration is really > 0 - })) - { - var httpClient = new HttpClient(); - var res = await httpClient.GetAsync(localServer.Uri); - - Assert.True(res.IsSuccessStatusCode); - Assert.Equal(localServer.Uri, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Url); - } - - Assert.True(Agent.TransactionContainer.Transactions.Value.Spans[0].Duration > 0); - } - - /// - /// Makes sure spans have an Id - /// - [Fact] - public async Task HttpRequestSpanGuid() - { - var (listener, _, _) = RegisterListenerAndStartTransaction(); - - using (listener) - using (var localServer = new LocalServer()) - { - var httpClient = new HttpClient(); - var res = await httpClient.GetAsync(localServer.Uri); - - Assert.True(res.IsSuccessStatusCode); - Assert.Equal(localServer.Uri, (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.Context.Http.Url); - } - - Assert.True(Agent.TransactionContainer.Transactions.Value.Spans[0].Id > 0); - } - - /// - /// Creates an HTTP call without registering the . - /// This is something like having a console application and just referencing the agent library. - /// By default the agent does not subscribe in that scenario to any diagnostic source. - /// Makes sure that no HTTP call is captured. - /// - [Fact] - public async Task HttpCallWithoutRegisteredListener() - { - var mockPayloadSender = new MockPayloadSender(); - var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); - - using (var localServer = new LocalServer()) - { - await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => - { - Thread.Sleep(5); - - var httpClient = new HttpClient(); - try - { - await httpClient.GetAsync(localServer.Uri); - } - catch (Exception e) - { - t.CaptureException(e); - } - }); - - Assert.NotEmpty(mockPayloadSender.Payloads[0].Transactions); - Assert.Empty(mockPayloadSender.SpansOnFirstTransaction); - - } - } - - /// - /// Make sure HttpDiagnosticSubscriber does not report spans after its disposed - /// - [Fact] - public async Task SubscriptionOnlyRegistersSpansDuringItsLifeTime() - { - var agent = new ApmAgent(new TestAgentComponents()); - StartTransaction(agent); - - var spans = Agent.TransactionContainer.Transactions.Value.Spans; - - using (var localServer = new LocalServer()) - using (var httpClient = new HttpClient()) - { - Assert.True(spans.Length == 0, $"Expected 0 spans has count: {spans.Length}"); - using (agent.Subscribe(new HttpDiagnosticsSubscriber())) - { - var res = await httpClient.GetAsync(localServer.Uri); - Assert.True(res.IsSuccessStatusCode); - res = await httpClient.GetAsync(localServer.Uri); - Assert.True(res.IsSuccessStatusCode); - } - spans = Agent.TransactionContainer.Transactions.Value.Spans; - Assert.True(spans.Length == 2, $"Expected 2 but spans has count: {spans.Length}"); - foreach (var _ in Enumerable.Range(0, 10)) - await httpClient.GetAsync(localServer.Uri); - - Assert.True(localServer.SeenRequests > 10, "Make sure we actually performed more than 1 request to our local server"); - } - Assert.True(spans.Length == 2, $"Expected 1 span because the listener is disposed but spans has count: {spans.Length}"); - } - - /// - /// Same as but this one registers - /// . - /// Makes sure that the outgoing web request is captured. - /// - [Fact] - public async Task HttpCallWithRegisteredListener() - { - var mockPayloadSender = new MockPayloadSender(); - var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); - var subscriber = new HttpDiagnosticsSubscriber(); - - using (var localServer = new LocalServer()) - using (agent.Subscribe(subscriber)) - { - var url = localServer.Uri; - await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => - { - Thread.Sleep(5); - - var httpClient = new HttpClient(); - try - { - await httpClient.GetAsync(url); - } - catch (Exception e) - { - t.CaptureException(e); - } - }); - - Assert.NotEmpty(mockPayloadSender.Payloads[0].Transactions); - Assert.NotEmpty(mockPayloadSender.SpansOnFirstTransaction); - - Assert.NotNull(mockPayloadSender.SpansOnFirstTransaction[0].Context.Http); - Assert.Equal(url, mockPayloadSender.SpansOnFirstTransaction[0].Context.Http.Url); - } - } - - /// - /// Subscribes to diagnostic events then unsubscribes. - /// Makes sure unsubscribing worked. - /// - [Fact] - public async Task SubscribeUnsubscribe() - { - var mockPayloadSender = new MockPayloadSender(); - var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); - var subscriber = new HttpDiagnosticsSubscriber(); - - using (var localServer = new LocalServer()) - { - var url = localServer.Uri; - using (agent.Subscribe(subscriber)) //subscribe - { - await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => - { - Thread.Sleep(5); - - var httpClient = new HttpClient(); - try - { - await httpClient.GetAsync(url); - } - catch (Exception e) - { - t.CaptureException(e); - } - }); - } //and then unsubscribe - - mockPayloadSender.Payloads.Clear(); - - await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => - { - Thread.Sleep(5); - - var httpClient = new HttpClient(); - try - { - await httpClient.GetAsync(url); - } - catch (Exception e) - { - t.CaptureException(e); - } - }); - - Assert.NotNull(mockPayloadSender.Payloads[0].Transactions[0]); - Assert.Empty(mockPayloadSender.SpansOnFirstTransaction); - } - } - - internal static (IDisposable, MockPayloadSender, ApmAgent) RegisterListenerAndStartTransaction() - { - var payloadSender = new MockPayloadSender(); - var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); - var sub = agent.Subscribe(new HttpDiagnosticsSubscriber()); - StartTransaction(agent); - - return (sub, payloadSender, agent); - } - - private static void StartTransaction(ApmAgent agent) - // => agent.TransactionContainer.Transactions.Value = - // new Transaction(agent, $"{nameof(TestSimpleOutgoingHttpRequest)}", ApiConstants.TypeRequest); - => agent.Tracer.StartTransaction($"{nameof(TestSimpleOutgoingHttpRequest)}", ApiConstants.TypeRequest); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Elastic.Apm.Api; +using Elastic.Apm.DiagnosticListeners; +using Elastic.Apm.DiagnosticSource; +using Elastic.Apm.Logging; +using Elastic.Apm.Model.Payload; +using Elastic.Apm.Tests.Mocks; +using FluentAssertions; +using Xunit; + +namespace Elastic.Apm.Tests +{ + public class HttpDiagnosticListenerTest + { + /// + /// Calls the OnError method on the HttpDiagnosticListener and makes sure that the correct error message is logged. + /// + [Fact] + public void OnErrorLog() + { + var logger = new TestLogger(); + var agent = new ApmAgent(new TestAgentComponents(logger)); + var listener = new HttpDiagnosticListener(agent); + + var exceptionMessage = "Ooops, this went wrong"; + var fakeException = new Exception(exceptionMessage); + listener.OnError(fakeException); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0] + .Should() + .StartWith($"{{{nameof(HttpDiagnosticListener)}}}") + .And.ContainAll( + "in OnError", + ".cs:", + exceptionMessage + ); + } + + /// + /// Builds an HttpRequestMessage and calls HttpDiagnosticListener.OnNext directly with it. + /// Makes sure that the processingRequests dictionary captures the ongoing transaction. + /// + [Fact] + public void OnNextWithStart() + { + var logger = new TestLogger(); + var agent = new ApmAgent(new TestAgentComponents(logger)); + StartTransaction(agent); + var listener = new HttpDiagnosticListener(agent); + + var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); + + //Simulate Start + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); + listener.ProcessingRequests.Should().NotBeEmpty().And.HaveCount(1); + listener.ProcessingRequests[request].Context.Http.Url.Should().Be(request.RequestUri.ToString()); + listener.ProcessingRequests[request].Context.Http.Method.Should().Be(HttpMethod.Get.ToString()); + } + + /// + /// Simulates the complete lifecycle of an HTTP request. + /// It builds an HttpRequestMessage and an HttpResponseMessage + /// and passes them to the OnNext method. + /// Makes sure that a Span with an Http context is captured. + /// + [Fact] + public void OnNextWithStartAndStop() + { + var logger = new TestLogger(); + var payloadSender = new MockPayloadSender(); + var agent = new ApmAgent(new TestAgentComponents(logger, payloadSender: payloadSender)); + StartTransaction(agent); + var listener = new HttpDiagnosticListener(agent); + + var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); + var response = new HttpResponseMessage(HttpStatusCode.OK); + + //Simulate Start + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); + //Simulate Stop + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); + listener.ProcessingRequests.Should().BeEmpty(); + + var firstSpan = Agent.TransactionContainer.Transactions.Value.Spans[0] as Span; + firstSpan.Should().NotBeNull(); + firstSpan.Context.Http.Url.Should().BeEquivalentTo(request.RequestUri.AbsoluteUri); + firstSpan.Context.Http.Method.Should().Be(HttpMethod.Get.Method); + } + + /// + /// Calls OnNext with System.Net.Http.HttpRequestOut.Stop twice. + /// Makes sure that the transaction is only captured once and the span is also only captured once. + /// Also make sure that there is an error log. + /// + [Fact] + public void OnNextWithStartAndStopTwice() + { + var logger = new TestLogger(LogLevel.Warning); + var agent = new ApmAgent(new TestAgentComponents(logger)); + StartTransaction(agent); + var listener = new HttpDiagnosticListener(agent); + + var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); + var response = new HttpResponseMessage(HttpStatusCode.OK); + + //Simulate Start + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); + //Simulate Stop + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); + //Simulate Stop again. This should not happen, still we test for this. + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Stop", new { Request = request, Response = response })); + + logger.Lines.Should().NotBeEmpty(); + logger.Lines[0] + .Should() + .StartWith($"{{{nameof(HttpDiagnosticListener)}}}") + .And.ContainAll( + "Failed capturing request", + HttpMethod.Get.Method, + request.RequestUri.AbsoluteUri + ); + Agent.TransactionContainer.Transactions.Value.Should().NotBeNull(); + Agent.TransactionContainer.Transactions.Value.Spans.Should().ContainSingle(); + } + + /// + /// Calls HttpDiagnosticListener.OnNext with types that are unknown. + /// The test makes sure that in this case still no exception is thrown from the OnNext method. + /// + [Fact] + public void UnknownObjectToOnNext() + { + var logger = new TestLogger(); + var agent = new ApmAgent(new TestAgentComponents(logger)); + var listener = new HttpDiagnosticListener(agent); + var myFake = new StringBuilder(); //just a random type that is not planned to be passed into OnNext + + var exception = + Record.Exception(() => { listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", myFake)); }); + + exception.Should().BeNull(); + } + + /// + /// Passes null instead of a valid HttpRequestMessage into HttpDiagnosticListener.OnNext + /// and makes sure that still no exception is thrown. + /// + [Fact] + public void NullValueToOnNext() + { + var logger = new TestLogger(); + var agent = new ApmAgent(new TestAgentComponents(logger)); + var listener = new HttpDiagnosticListener(agent); + + var exception = + Record.Exception(() => { listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", null)); }); + + exception.Should().BeNull(); + } + + /// + /// Passes a null key with null value instead of a valid HttpRequestMessage into HttpDiagnosticListener.OnNext + /// and makes sure that still no exception is thrown. + /// + [Fact] + public void NullKeyValueToOnNext() + { + var logger = new TestLogger(); + var agent = new ApmAgent(new TestAgentComponents(logger)); + var listener = new HttpDiagnosticListener(agent); + + var exception = + Record.Exception(() => { listener.OnNext(new KeyValuePair(null, null)); }); + + exception.Should().BeNull(); + } + + /// + /// Sends a simple real HTTP GET message and makes sure that + /// HttpDiagnosticListener captures it. + /// + [Fact] + public async Task TestSimpleOutgoingHttpRequest() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer()) + { + var httpClient = new HttpClient(); + var res = await httpClient.GetAsync(localServer.Uri); + + res.IsSuccessStatusCode.Should().BeTrue(); + var firstSpan = Agent.TransactionContainer.Transactions.Value.Spans[0] as Span; + firstSpan.Should().NotBeNull(); + firstSpan.Context.Http.Url.Should().Be(localServer.Uri); + firstSpan.Context.Http.StatusCode.Should().Be(200); + firstSpan.Context.Http.Method.Should().Be(HttpMethod.Get.Method); + } + } + + /// + /// Sends a simple real HTTP POST message and the server responds with 500 + /// The test makes sure HttpDiagnosticListener captures the POST method and + /// the response code correctly + /// + [Fact] + public async Task TestNotSuccessfulOutgoingHttpPostRequest() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer(ctx => { ctx.Response.StatusCode = 500; })) + { + var httpClient = new HttpClient(); + var res = await httpClient.PostAsync(localServer.Uri, new StringContent("foo")); + + res.IsSuccessStatusCode.Should().BeFalse(); + var firstSpan = Agent.TransactionContainer.Transactions.Value.Spans[0] as Span; + firstSpan.Should().NotBeNull(); + firstSpan.Context.Http.Url.Should().Be(localServer.Uri); + firstSpan.Context.Http.StatusCode.Should().Be(500); + firstSpan.Context.Http.Method.Should().Be(HttpMethod.Post.Method); + } + } + + /// + /// Starts an HTTP call to a non existing URL and makes sure that an error is captured. + /// This uses an HttpClient instance directly + /// + [Fact] + public async Task CaptureErrorOnFailingHttpCall_HttpClient() + { + var (listener, payloadSender, _) = RegisterListenerAndStartTransaction(); + + using (listener) + { + var httpClient = new HttpClient(); + + Func act = async () => await httpClient.GetAsync("http://nonexistenturl_dsfdsf.ghkdehfn"); + act.Should().Throw(); + } + + payloadSender.Errors.Should().NotBeEmpty(); + } + + /// + /// Passes an exception to and makes sure that the exception is captured + /// Unlike the method this does not use HttpClient, instead here we + /// call the OnNext method directly. + /// + [Fact] + public void CaptureErrorOnFailingHttpCall_DirectCall() + { + var (disposableListener, payloadSender, agent) = RegisterListenerAndStartTransaction(); + + using (disposableListener) + { + var listener = new HttpDiagnosticListener(agent); + + var request = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co"); + + //Simulate Start + listener.OnNext(new KeyValuePair("System.Net.Http.HttpRequestOut.Start", new { Request = request })); + + var exceptionMsg = "Sample exception msg"; + var exception = new Exception(exceptionMsg); + //Simulate Exception + listener.OnNext(new KeyValuePair("System.Net.Http.Exception", new { Request = request, Exception = exception })); + + payloadSender.Errors.Should().NotBeEmpty(); + payloadSender.Errors[0].Errors[0].Exception.Message.Should().Be(exceptionMsg); + payloadSender.Errors[0].Errors[0].Exception.Type.Should().Be(typeof(Exception).FullName); + } + } + + /// + /// Makes sure we set the correct type and subtype for external, http spans + /// + [Fact] + public async Task SpanTypeAndSubtype() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer()) + { + var httpClient = new HttpClient(); + var res = await httpClient.GetAsync(localServer.Uri); + + res.IsSuccessStatusCode.Should().BeTrue(); + } + + Agent.TransactionContainer.Transactions.Value.Spans[0].Type.Should().Be(ApiConstants.TypeExternal); + Agent.TransactionContainer.Transactions.Value.Spans[0].Subtype.Should().Be(ApiConstants.SubtypeHttp); + Agent.TransactionContainer.Transactions.Value.Spans[0].Action.Should().BeNull(); //we don't set Action for HTTP calls + } + + /// + /// Makes sure we generate the correct span name + /// + [Fact] + public async Task SpanName() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer()) + { + var httpClient = new HttpClient(); + var res = await httpClient.GetAsync(localServer.Uri); + + res.IsSuccessStatusCode.Should().BeTrue(); + } + + Agent.TransactionContainer.Transactions.Value.Spans[0].Name.Should().Be("GET localhost"); + } + + /// + /// Makes sure that the duration of an HTTP Request is captured by the agent + /// + /// The request duration. + [Fact] + public async Task HttpRequestDuration() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer(ctx => + { + ctx.Response.StatusCode = 200; + Thread.Sleep(5); //Make sure duration is really > 0 + })) + { + var httpClient = new HttpClient(); + var res = await httpClient.GetAsync(localServer.Uri); + + res.IsSuccessStatusCode.Should().BeTrue(); + var firstSpan = Agent.TransactionContainer.Transactions.Value.Spans[0] as Span; + firstSpan.Should().NotBeNull(); + firstSpan.Context.Http.Url.Should().Be(localServer.Uri); + firstSpan.Context.Http.StatusCode.Should().Be(200); + firstSpan.Context.Http.Method.Should().Be(HttpMethod.Get.Method); + firstSpan.Duration.Should().BeGreaterThan(0); + } + } + + /// + /// Makes sure spans have an Id + /// + [Fact] + public async Task HttpRequestSpanGuid() + { + var (listener, _, _) = RegisterListenerAndStartTransaction(); + + using (listener) + using (var localServer = new LocalServer()) + { + var httpClient = new HttpClient(); + var res = await httpClient.GetAsync(localServer.Uri); + + res.IsSuccessStatusCode.Should().BeTrue(); + var firstSpan = Agent.TransactionContainer.Transactions.Value.Spans[0] as Span; + firstSpan.Should().NotBeNull(); + firstSpan.Context.Http.Url.Should().Be(localServer.Uri); + firstSpan.Context.Http.StatusCode.Should().Be(200); + firstSpan.Context.Http.Method.Should().Be(HttpMethod.Get.Method); + firstSpan.Duration.Should().BeGreaterThan(0); + } + } + + /// + /// Creates an HTTP call without registering the . + /// This is something like having a console application and just referencing the agent library. + /// By default the agent does not subscribe in that scenario to any diagnostic source. + /// Makes sure that no HTTP call is captured. + /// + [Fact] + public async Task HttpCallWithoutRegisteredListener() + { + var mockPayloadSender = new MockPayloadSender(); + var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); + + using (var localServer = new LocalServer()) + { + await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => + { + Thread.Sleep(5); + + var httpClient = new HttpClient(); + try + { + await httpClient.GetAsync(localServer.Uri); + } + catch (Exception e) + { + t.CaptureException(e); + } + }); + + mockPayloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); + mockPayloadSender.SpansOnFirstTransaction.Should().BeEmpty(); + } + } + + /// + /// Make sure HttpDiagnosticSubscriber does not report spans after its disposed + /// + [Fact] + public async Task SubscriptionOnlyRegistersSpansDuringItsLifeTime() + { + var agent = new ApmAgent(new TestAgentComponents()); + StartTransaction(agent); + + var spans = Agent.TransactionContainer.Transactions.Value.Spans; + + using (var localServer = new LocalServer()) + using (var httpClient = new HttpClient()) + { + spans.Should().BeEmpty(); + using (agent.Subscribe(new HttpDiagnosticsSubscriber())) + { + var res = await httpClient.GetAsync(localServer.Uri); + res.IsSuccessStatusCode.Should().BeTrue(); + res = await httpClient.GetAsync(localServer.Uri); + res.IsSuccessStatusCode.Should().BeTrue(); + } + spans = Agent.TransactionContainer.Transactions.Value.Spans; + spans.Should().NotBeEmpty().And.HaveCount(2); + foreach (var _ in Enumerable.Range(0, 10)) + await httpClient.GetAsync(localServer.Uri); + + localServer.SeenRequests.Should() + .BeGreaterOrEqualTo(10, + "Make sure we actually performed more than 1 request to our local server"); + } + spans.Should().HaveCount(2); + } + + /// + /// Same as but this one registers + /// . + /// Makes sure that the outgoing web request is captured. + /// + [Fact] + public async Task HttpCallWithRegisteredListener() + { + var mockPayloadSender = new MockPayloadSender(); + var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); + var subscriber = new HttpDiagnosticsSubscriber(); + + using (var localServer = new LocalServer()) + using (agent.Subscribe(subscriber)) + { + var url = localServer.Uri; + await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => + { + Thread.Sleep(5); + + var httpClient = new HttpClient(); + try + { + await httpClient.GetAsync(url); + } + catch (Exception e) + { + t.CaptureException(e); + } + }); + + mockPayloadSender.Payloads[0].Transactions.Should().NotBeEmpty(); + mockPayloadSender.SpansOnFirstTransaction.Should().NotBeEmpty(); + + mockPayloadSender.SpansOnFirstTransaction[0].Context.Http.Should().NotBeNull(); + mockPayloadSender.SpansOnFirstTransaction[0].Context.Http.Url.Should().Be(url); + } + } + + /// + /// Subscribes to diagnostic events then unsubscribes. + /// Makes sure unsubscribing worked. + /// + [Fact] + public async Task SubscribeUnsubscribe() + { + var mockPayloadSender = new MockPayloadSender(); + var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender)); + var subscriber = new HttpDiagnosticsSubscriber(); + + using (var localServer = new LocalServer()) + { + var url = localServer.Uri; + using (agent.Subscribe(subscriber)) //subscribe + { + await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => + { + Thread.Sleep(5); + + var httpClient = new HttpClient(); + try + { + await httpClient.GetAsync(url); + } + catch (Exception e) + { + t.CaptureException(e); + } + }); + } //and then unsubscribe + + mockPayloadSender.Payloads.Clear(); + + await agent.Tracer.CaptureTransaction("TestTransaction", "TestType", async t => + { + Thread.Sleep(5); + + var httpClient = new HttpClient(); + try + { + await httpClient.GetAsync(url); + } + catch (Exception e) + { + t.CaptureException(e); + } + }); + + mockPayloadSender.Payloads[0].Transactions[0].Should().NotBeNull(); + mockPayloadSender.SpansOnFirstTransaction.Should().BeEmpty(); + } + } + + internal static (IDisposable, MockPayloadSender, ApmAgent) RegisterListenerAndStartTransaction() + { + var payloadSender = new MockPayloadSender(); + var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); + var sub = agent.Subscribe(new HttpDiagnosticsSubscriber()); + StartTransaction(agent); + + return (sub, payloadSender, agent); + } + + private static void StartTransaction(ApmAgent agent) + // => agent.TransactionContainer.Transactions.Value = + // new Transaction(agent, $"{nameof(TestSimpleOutgoingHttpRequest)}", ApiConstants.TypeRequest); + => agent.Tracer.StartTransaction($"{nameof(TestSimpleOutgoingHttpRequest)}", ApiConstants.TypeRequest); + } +} diff --git a/test/Elastic.Apm.Tests/LoggerTest.cs b/test/Elastic.Apm.Tests/LoggerTest.cs index 2003abf8d..6313f4640 100644 --- a/test/Elastic.Apm.Tests/LoggerTest.cs +++ b/test/Elastic.Apm.Tests/LoggerTest.cs @@ -1,5 +1,6 @@ using Elastic.Apm.Logging; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests @@ -11,8 +12,8 @@ public void TestLogError() { var logger = LogWithLevel(LogLevel.Error); - Assert.Single(logger.Lines); - Assert.Equal("Error log", logger.Lines[0]); + logger.Lines.Should().ContainSingle(); + logger.Lines[0].Should().Be("Error log"); } [Fact] @@ -20,9 +21,9 @@ public void TestLogWarning() { var logger = LogWithLevel(LogLevel.Warning); - Assert.Equal(2, logger.Lines.Count); - Assert.Equal("Error log", logger.Lines[0]); - Assert.Equal("Warning log", logger.Lines[1]); + logger.Lines.Count.Should().Be(2); + logger.Lines[0].Should().Be("Error log"); + logger.Lines[1].Should().Be("Warning log"); } [Fact] @@ -30,10 +31,10 @@ public void TestLogInfo() { var logger = LogWithLevel(LogLevel.Information); - Assert.Equal(3, logger.Lines.Count); - Assert.Equal("Error log", logger.Lines[0]); - Assert.Equal("Warning log", logger.Lines[1]); - Assert.Equal("Info log", logger.Lines[2]); + logger.Lines.Count.Should().Be(3); + logger.Lines[0].Should().Be("Error log"); + logger.Lines[1].Should().Be("Warning log"); + logger.Lines[2].Should().Be("Info log"); } [Fact] @@ -41,11 +42,11 @@ public void TestLogDebug() { var logger = LogWithLevel(LogLevel.Debug); - Assert.Equal(4, logger.Lines.Count); - Assert.Equal("Error log", logger.Lines[0]); - Assert.Equal("Warning log", logger.Lines[1]); - Assert.Equal("Info log", logger.Lines[2]); - Assert.Equal("Debug log", logger.Lines[3]); + logger.Lines.Count.Should().Be(4); + logger.Lines[0].Should().Be("Error log"); + logger.Lines[1].Should().Be("Warning log"); + logger.Lines[2].Should().Be("Info log"); + logger.Lines[3].Should().Be("Debug log"); } private TestLogger LogWithLevel(LogLevel logLevel) diff --git a/test/Elastic.Apm.Tests/StackTraceTests.cs b/test/Elastic.Apm.Tests/StackTraceTests.cs index de7453d43..7bb27d972 100644 --- a/test/Elastic.Apm.Tests/StackTraceTests.cs +++ b/test/Elastic.Apm.Tests/StackTraceTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Elastic.Apm.Model.Payload; using Elastic.Apm.Tests.Mocks; +using FluentAssertions; using Xunit; namespace Elastic.Apm.Tests @@ -28,13 +29,12 @@ public async Task HttpClientStackTrace() var httpClient = new HttpClient(); var res = await httpClient.GetAsync(localServer.Uri); - Assert.True(res.IsSuccessStatusCode); + res.IsSuccessStatusCode.Should().BeTrue(); } var stackFrames = (Agent.TransactionContainer.Transactions.Value.Spans[0] as Span)?.StackTrace; - Assert.NotNull(stackFrames); - Assert.Contains(stackFrames, frame => frame.LineNo!=0); + stackFrames.Should().NotBeEmpty().And.Contain(frame => frame.LineNo != 0); } } } From e4aed8a86f0ca4eec5d4d1dfbeeb089c5f94b1f1 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 16:39:40 +0100 Subject: [PATCH 08/15] Expose Transaction.Context.Request and Transaction.Context.Response #124 --- sample/ApiSamples/Program.cs | 2 + src/Elastic.Apm.AspNetCore/ApmMiddleware.cs | 19 ++-- src/Elastic.Apm/Api/Context.cs | 28 ++++++ src/Elastic.Apm/Api/ITransaction.cs | 6 ++ .../{Model/Payload => Api}/Request.cs | 16 +++- src/Elastic.Apm/Api/Response.cs | 19 ++++ src/Elastic.Apm/Elastic.Apm.csproj | 6 +- src/Elastic.Apm/Model/Payload/Context.cs | 15 ---- src/Elastic.Apm/Model/Payload/Response.cs | 12 --- .../ApiTests/ConvenientApiTransactionTests.cs | 86 +++++++++++++++++++ 10 files changed, 165 insertions(+), 44 deletions(-) create mode 100755 src/Elastic.Apm/Api/Context.cs rename src/Elastic.Apm/{Model/Payload => Api}/Request.cs (52%) create mode 100755 src/Elastic.Apm/Api/Response.cs delete mode 100755 src/Elastic.Apm/Model/Payload/Context.cs delete mode 100755 src/Elastic.Apm/Model/Payload/Response.cs diff --git a/sample/ApiSamples/Program.cs b/sample/ApiSamples/Program.cs index 062546818..e22477fae 100644 --- a/sample/ApiSamples/Program.cs +++ b/sample/ApiSamples/Program.cs @@ -70,6 +70,8 @@ public static void SampleError() public static void SampleCustomTransactionWithConvenientApi() => Agent.Tracer.CaptureTransaction("TestTransaction", "TestType", t => { + t.Context.Response = new Response() { Finished = true, StatusCode = 200 }; + t.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); t.Tags["fooTransaction"] = "barTransaction"; Thread.Sleep(10); t.CaptureSpan("TestSpan", "TestSpanName", s => diff --git a/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs b/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs index 9a8fa9f7b..02a697e10 100755 --- a/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs +++ b/src/Elastic.Apm.AspNetCore/ApmMiddleware.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Elastic.Apm.Api; using Elastic.Apm.Helpers; -using Elastic.Apm.Model.Payload; using Microsoft.AspNetCore.Http; [assembly: @@ -32,21 +31,21 @@ public async Task InvokeAsync(HttpContext context) var transaction = _tracer.StartTransactionInternal($"{context.Request.Method} {context.Request.Path}", ApiConstants.TypeRequest); - transaction.Context.Request = new Request + var url = new Url + { + Full = context.Request?.Path.Value, + HostName = context.Request.Host.Host, + Protocol = GetProtocolName(context.Request.Protocol), + Raw = context.Request?.Path.Value //TODO + }; + + transaction.Context.Request = new Request( context.Request.Method, url) { - Method = context.Request.Method, Socket = new Socket { Encrypted = context.Request.IsHttps, RemoteAddress = context.Connection?.RemoteIpAddress?.ToString() }, - Url = new Url - { - Full = context.Request?.Path.Value, - HostName = context.Request.Host.Host, - Protocol = GetProtocolName(context.Request.Protocol), - Raw = context.Request?.Path.Value //TODO - }, HttpVersion = GetHttpVersion(context.Request.Protocol) }; diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs new file mode 100755 index 000000000..59f4bb931 --- /dev/null +++ b/src/Elastic.Apm/Api/Context.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Elastic.Apm.Api +{ + public class Context + { + private readonly Lazy> tags = new Lazy>(); + + /// + /// If a log record was generated as a result of a http request, the http interface can be used to collect this + /// information. + /// This property is by default null! You have to assign a instance to this property in order to use + /// it. + /// + public Request Request { get; set; } + + /// + /// If a log record was generated as a result of a http request, the http interface can be used to collect this + /// information. + /// This property is by default null! You have to assign a instance to this property in order to use + /// it. + /// + public Response Response { get; set; } + + public Dictionary Tags => tags.Value; + } +} diff --git a/src/Elastic.Apm/Api/ITransaction.cs b/src/Elastic.Apm/Api/ITransaction.cs index 96975e002..ee405bc57 100644 --- a/src/Elastic.Apm/Api/ITransaction.cs +++ b/src/Elastic.Apm/Api/ITransaction.cs @@ -7,6 +7,12 @@ namespace Elastic.Apm.Api { public interface ITransaction { + /// + /// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user. + /// This field is lazily initialized, you don't have to assign a value to it and you don't have to null check it either. + /// + Context Context { get; } + /// /// The duration of the transaction. /// If it's not set (its HasValue property is false) then the value diff --git a/src/Elastic.Apm/Model/Payload/Request.cs b/src/Elastic.Apm/Api/Request.cs similarity index 52% rename from src/Elastic.Apm/Model/Payload/Request.cs rename to src/Elastic.Apm/Api/Request.cs index 54d25e641..eeda4f24f 100755 --- a/src/Elastic.Apm/Model/Payload/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -1,17 +1,25 @@ using Newtonsoft.Json; -namespace Elastic.Apm.Model.Payload +namespace Elastic.Apm.Api { - internal class Request + /// + /// Encapsulates Request related information that can be attached to an through + /// See + /// + public class Request { + public Request(string method, Url url) => (Method, Url) = (method, url); + public string HttpVersion { get; set; } public string Method { get; set; } public Socket Socket { get; set; } public Url Url { get; set; } + + public object Body { get; set; } } - internal class Socket + public class Socket { public bool Encrypted { get; set; } @@ -19,7 +27,7 @@ internal class Socket public string RemoteAddress { get; set; } } - internal class Url + public class Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs new file mode 100755 index 000000000..b8d074995 --- /dev/null +++ b/src/Elastic.Apm/Api/Response.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Elastic.Apm.Api +{ + /// + /// Encapsulates Response related information that can be attached to an through + /// See + /// + public class Response + { + public bool Finished { get; set; } + + /// + /// The HTTP status code of the response. + /// + [JsonProperty("Status_code")] + public int StatusCode { get; set; } + } +} diff --git a/src/Elastic.Apm/Elastic.Apm.csproj b/src/Elastic.Apm/Elastic.Apm.csproj index e534436f4..0a78c949b 100644 --- a/src/Elastic.Apm/Elastic.Apm.csproj +++ b/src/Elastic.Apm/Elastic.Apm.csproj @@ -22,9 +22,9 @@ latest - + - - + + diff --git a/src/Elastic.Apm/Model/Payload/Context.cs b/src/Elastic.Apm/Model/Payload/Context.cs deleted file mode 100755 index 149d5d2e3..000000000 --- a/src/Elastic.Apm/Model/Payload/Context.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Elastic.Apm.Model.Payload -{ - internal class Context - { - private readonly Lazy> tags = new Lazy>(); - public Request Request { get; set; } - - public Response Response { get; set; } - - public Dictionary Tags => tags.Value; - } -} diff --git a/src/Elastic.Apm/Model/Payload/Response.cs b/src/Elastic.Apm/Model/Payload/Response.cs deleted file mode 100755 index a40e5d6b4..000000000 --- a/src/Elastic.Apm/Model/Payload/Response.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace Elastic.Apm.Model.Payload -{ - internal class Response - { - public bool Finished { get; set; } - - [JsonProperty("Status_code")] - public int StatusCode { get; set; } - } -} diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs index 0480e4cc2..9fe8b805d 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs @@ -469,6 +469,92 @@ await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transa payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("foo", "bar"); } + /// + /// Creates a transaction and attaches a Request to this transaction. + /// Makes sure that the transaction details are captured. + /// + [Fact] + public void TransactionWithRequest() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); + }); + }); + + Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); + Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + } + + /// + /// Creates a transaction and attaches a Request to this transaction. It fills all the fields on the Request. + /// Makes sure that all the transaction details are captured. + /// + [Fact] + public void TransactionWithRequestDetailed() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Request = new Request("GET", new Url() + { + Full = "https://elastic.co", + Raw = "https://elastic.co", + HostName = "elastic", + Protocol = "HTTP" + }) + { + HttpVersion = "2.0", + Socket = new Socket() + { + Encrypted = true, + RemoteAddress = "127.0.0.1", + }, + Body = "123" + }; + }); + }); + + Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); + Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + + Assert.Equal("2.0", payloadSender.FirstTransaction.Context.Request.HttpVersion); + Assert.True(payloadSender.FirstTransaction.Context.Request.Socket.Encrypted); + Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Full); + Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Raw); + Assert.Equal("127.0.0.1", payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress); + Assert.Equal("elastic", payloadSender.FirstTransaction.Context.Request.Url.HostName); + Assert.Equal("123", payloadSender.FirstTransaction.Context.Request.Body); + } + + /// + /// Creates a transaction and attaches a Response to this transaction. + /// Makes sure that the transaction details are captured. + /// + [Fact] + public void TransactionWithResponse() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Response = new Response() { Finished = true, StatusCode = 200 }; + }); + }); + + Assert.True(payloadSender.FirstTransaction.Context.Response.Finished); + Assert.Equal(200, payloadSender.FirstTransaction.Context.Response.StatusCode); + } + /// /// Asserts on 1 transaction with async code /// From 56b203858753c78e42fe80c6e974b5e69409f126 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 16:43:56 +0100 Subject: [PATCH 09/15] Update src/Elastic.Apm/Elastic.Apm.csproj --- src/Elastic.Apm/Elastic.Apm.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Elastic.Apm/Elastic.Apm.csproj b/src/Elastic.Apm/Elastic.Apm.csproj index 0a78c949b..e534436f4 100644 --- a/src/Elastic.Apm/Elastic.Apm.csproj +++ b/src/Elastic.Apm/Elastic.Apm.csproj @@ -22,9 +22,9 @@ latest - + - - + + From b2e126fe1fdb416dc34f1d8324999ed87b2254bf Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 27 Feb 2019 19:08:14 +0100 Subject: [PATCH 10/15] Make Request, Socket, Url, Response to struct. --- src/Elastic.Apm/Api/Request.cs | 8 ++++---- src/Elastic.Apm/Api/Response.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Apm/Api/Request.cs b/src/Elastic.Apm/Api/Request.cs index eeda4f24f..ade123a40 100755 --- a/src/Elastic.Apm/Api/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -6,9 +6,9 @@ namespace Elastic.Apm.Api /// Encapsulates Request related information that can be attached to an through /// See /// - public class Request + public struct Request { - public Request(string method, Url url) => (Method, Url) = (method, url); + public Request(string method, Url url) => (Method, Url, Body, HttpVersion, Socket) = (method, url, null, null, new Socket()); public string HttpVersion { get; set; } @@ -19,7 +19,7 @@ public class Request public object Body { get; set; } } - public class Socket + public struct Socket { public bool Encrypted { get; set; } @@ -27,7 +27,7 @@ public class Socket public string RemoteAddress { get; set; } } - public class Url + public struct Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs index b8d074995..100ca1634 100755 --- a/src/Elastic.Apm/Api/Response.cs +++ b/src/Elastic.Apm/Api/Response.cs @@ -6,7 +6,7 @@ namespace Elastic.Apm.Api /// Encapsulates Response related information that can be attached to an through /// See /// - public class Response + public struct Response { public bool Finished { get; set; } From c900b892e984ae4c2b5d76ffab8a95f063027fb9 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 00:18:10 +0100 Subject: [PATCH 11/15] Adjust comments --- src/Elastic.Apm/Api/Context.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs index 59f4bb931..9e1fde087 100755 --- a/src/Elastic.Apm/Api/Context.cs +++ b/src/Elastic.Apm/Api/Context.cs @@ -10,7 +10,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property is by default null! You have to assign a instance to this property in order to use + /// This property by default contains empty values! You have to assign a instance to this property in order to use /// it. /// public Request Request { get; set; } @@ -18,7 +18,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property is by default null! You have to assign a instance to this property in order to use + /// This property by default contains empty values! You have to assign a instance to this property in order to use /// it. /// public Response Response { get; set; } From e0f9eaf5b9bfc4d239e89649607b53fda7578e68 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 09:55:55 +0100 Subject: [PATCH 12/15] Revert "Adjust comments" This reverts commit 5ba803e0b380a8706fba0de95163396067873252. --- src/Elastic.Apm/Api/Context.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Apm/Api/Context.cs b/src/Elastic.Apm/Api/Context.cs index 9e1fde087..59f4bb931 100755 --- a/src/Elastic.Apm/Api/Context.cs +++ b/src/Elastic.Apm/Api/Context.cs @@ -10,7 +10,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property by default contains empty values! You have to assign a instance to this property in order to use + /// This property is by default null! You have to assign a instance to this property in order to use /// it. /// public Request Request { get; set; } @@ -18,7 +18,7 @@ public class Context /// /// If a log record was generated as a result of a http request, the http interface can be used to collect this /// information. - /// This property by default contains empty values! You have to assign a instance to this property in order to use + /// This property is by default null! You have to assign a instance to this property in order to use /// it. /// public Response Response { get; set; } From 173a5a13a79a113998c56a6de487461dd3aa6b1c Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Thu, 28 Feb 2019 09:55:58 +0100 Subject: [PATCH 13/15] Revert "Make Request, Socket, Url, Response to struct." This reverts commit 842eda01ed5b54739749750f19638a5b1b2f0c91. --- src/Elastic.Apm/Api/Request.cs | 8 ++++---- src/Elastic.Apm/Api/Response.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Apm/Api/Request.cs b/src/Elastic.Apm/Api/Request.cs index ade123a40..eeda4f24f 100755 --- a/src/Elastic.Apm/Api/Request.cs +++ b/src/Elastic.Apm/Api/Request.cs @@ -6,9 +6,9 @@ namespace Elastic.Apm.Api /// Encapsulates Request related information that can be attached to an through /// See /// - public struct Request + public class Request { - public Request(string method, Url url) => (Method, Url, Body, HttpVersion, Socket) = (method, url, null, null, new Socket()); + public Request(string method, Url url) => (Method, Url) = (method, url); public string HttpVersion { get; set; } @@ -19,7 +19,7 @@ public struct Request public object Body { get; set; } } - public struct Socket + public class Socket { public bool Encrypted { get; set; } @@ -27,7 +27,7 @@ public struct Socket public string RemoteAddress { get; set; } } - public struct Url + public class Url { public string Full { get; set; } public string HostName { get; set; } diff --git a/src/Elastic.Apm/Api/Response.cs b/src/Elastic.Apm/Api/Response.cs index 100ca1634..b8d074995 100755 --- a/src/Elastic.Apm/Api/Response.cs +++ b/src/Elastic.Apm/Api/Response.cs @@ -6,7 +6,7 @@ namespace Elastic.Apm.Api /// Encapsulates Response related information that can be attached to an through /// See /// - public struct Response + public class Response { public bool Finished { get; set; } From da16713b825a5905d81458552f24d55d309b971f Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 4 Mar 2019 20:56:03 +0100 Subject: [PATCH 14/15] Adapt tests to fluent API, Add SampleAspNetCoreApp Also remove some unused variables --- .../ApiTests/ConvenientApiTransactionTests.cs | 72 +++++++++++++------ 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs index 9fe8b805d..1135826f8 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs @@ -149,7 +149,7 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1T { Action act = () => { - var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t => + agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t => { t.Should().NotBeNull(); WaitHelpers.SleepMinimum(); @@ -175,7 +175,7 @@ public void SimpleActionWithReturnTypeAndException() => AssertWith1TransactionAn { Action act = () => { - var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, () => + agent.Tracer.CaptureTransaction(TransactionName, TransactionType, () => { WaitHelpers.SleepMinimum(); @@ -310,7 +310,7 @@ public async Task AsyncTaskWithReturnTypeAndExceptionAndParameter() => await Ass { Func act = async () => { - var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => + await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t => { t.Should().NotBeNull(); await WaitHelpers.DelayMinimum(); @@ -335,7 +335,7 @@ public async Task AsyncTaskWithReturnTypeAndException() => await AssertWith1Tran { Func act = async () => { - var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () => + await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () => { await WaitHelpers.DelayMinimum(); @@ -482,12 +482,12 @@ public void TransactionWithRequest() n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => { WaitHelpers.SleepMinimum(); - transaction.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); + transaction.Context.Request = new Request("GET", new Url { Protocol = "HTTP" }); }); }); - Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); - Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + payloadSender.FirstTransaction.Context.Request.Method.Should().Be("GET"); + payloadSender.FirstTransaction.Context.Request.Url.Protocol.Should().Be("HTTP"); } /// @@ -503,7 +503,7 @@ public void TransactionWithRequestDetailed() n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => { WaitHelpers.SleepMinimum(); - transaction.Context.Request = new Request("GET", new Url() + transaction.Context.Request = new Request("GET", new Url { Full = "https://elastic.co", Raw = "https://elastic.co", @@ -512,26 +512,26 @@ public void TransactionWithRequestDetailed() }) { HttpVersion = "2.0", - Socket = new Socket() + Socket = new Socket { Encrypted = true, - RemoteAddress = "127.0.0.1", + RemoteAddress = "127.0.0.1" }, Body = "123" }; }); }); - Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); - Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); + payloadSender.FirstTransaction.Context.Request.Method.Should().Be("GET"); + payloadSender.FirstTransaction.Context.Request.Url.Protocol.Should().Be("HTTP"); - Assert.Equal("2.0", payloadSender.FirstTransaction.Context.Request.HttpVersion); - Assert.True(payloadSender.FirstTransaction.Context.Request.Socket.Encrypted); - Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Full); - Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Raw); - Assert.Equal("127.0.0.1", payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress); - Assert.Equal("elastic", payloadSender.FirstTransaction.Context.Request.Url.HostName); - Assert.Equal("123", payloadSender.FirstTransaction.Context.Request.Body); + payloadSender.FirstTransaction.Context.Request.HttpVersion.Should().Be("2.0"); + payloadSender.FirstTransaction.Context.Request.Socket.Encrypted.Should().BeTrue(); + payloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("https://elastic.co"); + payloadSender.FirstTransaction.Context.Request.Url.Raw.Should().Be("https://elastic.co"); + payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress.Should().Be("127.0.0.1"); + payloadSender.FirstTransaction.Context.Request.Url.HostName.Should().Be("elastic"); + payloadSender.FirstTransaction.Context.Request.Body.Should().Be("123"); } /// @@ -547,12 +547,40 @@ public void TransactionWithResponse() n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => { WaitHelpers.SleepMinimum(); - transaction.Context.Response = new Response() { Finished = true, StatusCode = 200 }; + transaction.Context.Response = new Response + { Finished = true, StatusCode = 200 }; + }); + }); + + payloadSender.FirstTransaction.Context.Response.Finished.Should().BeTrue(); + payloadSender.FirstTransaction.Context.Response.StatusCode.Should().Be(200); + } + + /// + /// Creates a transaction and attaches a Response and a request to this transaction. It sets 1 property on each. + /// Makes sure that the transaction details are captured. + /// + [Fact] + public void TransactionWithResponseAndRequest() + { + var payloadSender = AssertWith1Transaction( + n => + { + n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => + { + WaitHelpers.SleepMinimum(); + transaction.Context.Response = new Response + { Finished = true}; + transaction.Context.Request = new Request("GET", new Url()); + + transaction.Context.Response.StatusCode = 200; + transaction.Context.Request.Url.Full = "https://elastic.co"; }); }); - Assert.True(payloadSender.FirstTransaction.Context.Response.Finished); - Assert.Equal(200, payloadSender.FirstTransaction.Context.Response.StatusCode); + payloadSender.FirstTransaction.Context.Response.Finished.Should().BeTrue(); + payloadSender.FirstTransaction.Context.Response.StatusCode.Should().Be(200); + payloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("https://elastic.co"); } /// From c3d4ece9bff6ed310447746b1c4d734f5e656951 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 4 Mar 2019 21:09:42 +0100 Subject: [PATCH 15/15] Update test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs --- .../ApiTests/ConvenientApiTransactionTests.cs | 86 ------------------- 1 file changed, 86 deletions(-) diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs index b2b9c082c..1135826f8 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs @@ -583,92 +583,6 @@ public void TransactionWithResponseAndRequest() payloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("https://elastic.co"); } - /// - /// Creates a transaction and attaches a Request to this transaction. - /// Makes sure that the transaction details are captured. - /// - [Fact] - public void TransactionWithRequest() - { - var payloadSender = AssertWith1Transaction( - n => - { - n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => - { - WaitHelpers.SleepMinimum(); - transaction.Context.Request = new Request("GET", new Url{Protocol = "HTTP"}); - }); - }); - - Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); - Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); - } - - /// - /// Creates a transaction and attaches a Request to this transaction. It fills all the fields on the Request. - /// Makes sure that all the transaction details are captured. - /// - [Fact] - public void TransactionWithRequestDetailed() - { - var payloadSender = AssertWith1Transaction( - n => - { - n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => - { - WaitHelpers.SleepMinimum(); - transaction.Context.Request = new Request("GET", new Url() - { - Full = "https://elastic.co", - Raw = "https://elastic.co", - HostName = "elastic", - Protocol = "HTTP" - }) - { - HttpVersion = "2.0", - Socket = new Socket() - { - Encrypted = true, - RemoteAddress = "127.0.0.1", - }, - Body = "123" - }; - }); - }); - - Assert.Equal("GET", payloadSender.FirstTransaction.Context.Request.Method); - Assert.Equal("HTTP", payloadSender.FirstTransaction.Context.Request.Url.Protocol); - - Assert.Equal("2.0", payloadSender.FirstTransaction.Context.Request.HttpVersion); - Assert.True(payloadSender.FirstTransaction.Context.Request.Socket.Encrypted); - Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Full); - Assert.Equal("https://elastic.co", payloadSender.FirstTransaction.Context.Request.Url.Raw); - Assert.Equal("127.0.0.1", payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress); - Assert.Equal("elastic", payloadSender.FirstTransaction.Context.Request.Url.HostName); - Assert.Equal("123", payloadSender.FirstTransaction.Context.Request.Body); - } - - /// - /// Creates a transaction and attaches a Response to this transaction. - /// Makes sure that the transaction details are captured. - /// - [Fact] - public void TransactionWithResponse() - { - var payloadSender = AssertWith1Transaction( - n => - { - n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction => - { - WaitHelpers.SleepMinimum(); - transaction.Context.Response = new Response() { Finished = true, StatusCode = 200 }; - }); - }); - - Assert.True(payloadSender.FirstTransaction.Context.Response.Finished); - Assert.Equal(200, payloadSender.FirstTransaction.Context.Response.StatusCode); - } - /// /// Asserts on 1 transaction with async code ///