From 0a06ef8b621443b6490c0c5793656f23fe02b44a Mon Sep 17 00:00:00 2001 From: Burak Akgerman Date: Sat, 4 Jun 2022 07:08:14 -0400 Subject: [PATCH] Added several layout renders for NLog.Web (#784) * AspNetRequestClientCertificateLayoutRenderer - aspnet-request-client-certificate * AspNetRequestIsWebSocketLayoutRenderer - aspnet-request-is-web-socket * AspNetRequestLocalIpLayoutRenderer - aspnet-request-local-ip * AspNetRequestLocalPortLayoutRenderer - aspnet-request-local-port * AspNetRequestRemotePortLayoutRenderer - aspnet-request-remote-port * AspNetRequestWebSocketRequestedProtocolsLayoutRenderer - aspnet-request-web-socket-requested-protocols * AspNetResponseContentTypeLayoutRenderer - aspnet-response-contenttype * AspNetResponseHeadersLayoutRenderer - aspnet-response-headers Co-authored-by: Burak Akgerman --- .../AspNetLayoutMultiValueRendererBase.cs | 63 ++++ ...tRequestClientCertificateLayoutRenderer.cs | 46 +++ .../AspNetRequestIsWebSocketLayoutRenderer.cs | 41 +++ .../AspNetRequestLocalIpLayoutRenderer.cs | 46 +++ .../AspNetRequestLocalPortLayoutRenderer.cs | 45 +++ .../AspNetRequestRemotePortLayoutRenderer.cs | 46 +++ ...bSocketRequestedProtocolsLayoutRenderer.cs | 72 ++++ ...AspNetResponseContentTypeLayoutRenderer.cs | 37 ++ .../AspNetResponseHeadersLayoutRenderer.cs | 113 ++++++ ...etRequestClientCertificateRendererTests.cs | 94 +++++ ...etRequestIsWebSocketLayoutRendererTests.cs | 95 +++++ .../AspNetRequestLocalIpRendererTests.cs | 34 ++ .../AspNetRequestLocalPortRendererTests.cs | 54 +++ .../AspNetRequestRemotePortRendererTests.cs | 48 +++ ...etRequestedProtocolsLayoutRendererTests.cs | 144 ++++++++ .../AspNetResponseContentTypeRendererTests.cs | 23 ++ ...spNetResponseHeadersLayoutRendererTests.cs | 332 ++++++++++++++++++ 17 files changed, 1333 insertions(+) create mode 100644 src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetResponseContentTypeLayoutRenderer.cs create mode 100644 src/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRenderer.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestLocalIpRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestLocalPortRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestRemotePortRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetResponseContentTypeRendererTests.cs create mode 100644 tests/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRendererTests.cs diff --git a/src/Shared/LayoutRenderers/AspNetLayoutMultiValueRendererBase.cs b/src/Shared/LayoutRenderers/AspNetLayoutMultiValueRendererBase.cs index 4acb99b8..ddacce62 100644 --- a/src/Shared/LayoutRenderers/AspNetLayoutMultiValueRendererBase.cs +++ b/src/Shared/LayoutRenderers/AspNetLayoutMultiValueRendererBase.cs @@ -167,6 +167,69 @@ private void SerializePairsFlat(IEnumerable> pairs, } } + /// + /// Serialize multiple values + /// + /// The values. + /// Add to this builder. + /// Log event for rendering separators. + protected void SerializeValues(IEnumerable values, StringBuilder builder, LogEventInfo logEvent) + { + switch (OutputFormat) + { + case AspNetRequestLayoutOutputFormat.Flat: + SerializeValuesFlat(values, builder, logEvent); + break; + case AspNetRequestLayoutOutputFormat.JsonArray: + case AspNetRequestLayoutOutputFormat.JsonDictionary: + SerializeValuesJson(values, builder); + break; + } + } + + private static void SerializeValuesJson(IEnumerable values, StringBuilder builder) + { + var firstItem = true; + foreach (var item in values) + { + if (firstItem) + { + builder.Append('['); + } + else + { + builder.Append(','); + } + SerializeValueJson(builder, item); + firstItem = false; + } + if (!firstItem) + { + builder.Append(']'); + } + } + + private static void SerializeValueJson(StringBuilder builder, string value) + { + // Quoted value + AppendQuoted(builder, value); + } + + private void SerializeValuesFlat(IEnumerable values, StringBuilder builder, LogEventInfo logEvent) + { + var itemSeparator = GetRenderedItemSeparator(logEvent); + var firstItem = true; + foreach (var value in values) + { + if (!firstItem) + { + builder.Append(itemSeparator); + } + firstItem = false; + builder.Append(value); + } + } + /// /// Get the rendered /// diff --git a/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs new file mode 100644 index 00000000..52566e38 --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs @@ -0,0 +1,46 @@ +using System.Text; +using NLog.LayoutRenderers; +#if ASP_NET_CORE +using Microsoft.AspNetCore.Http; +#else +using System.Security.Cryptography.X509Certificates; +using System.Web; +#endif + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Client Certificate of the Connection + /// + /// + /// ${aspnet-request-client certificate} + /// + [LayoutRenderer("aspnet-request-client-certificate")] + public class AspNetRequestClientCertificateLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Render Remote Port + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var httpContext = HttpContextAccessor.HttpContext; +#if ASP_NET_CORE + var connection = httpContext.Connection; + if (connection == null) + { + return; + } + builder.Append(connection.ClientCertificate); +#else + var certificate = httpContext.Request.ClientCertificate; + if (certificate == null) + { + return; + } + // Convert to an X509Certificate2, which does have the proper overridden ToString() method. + // HttpClientCertificate class only use object.ToString() which is useless. + builder.Append(new X509Certificate2(certificate.Certificate)); +#endif + } + } +} \ No newline at end of file diff --git a/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs new file mode 100644 index 00000000..6f3e106c --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs @@ -0,0 +1,41 @@ +using System.Text; +using NLog.LayoutRenderers; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Is Request Web Socket + /// + /// + /// ${aspnet-request-is-web-socket} + /// + [LayoutRenderer("aspnet-request-is-web-socket")] + public class AspNetRequestIsWebSocketLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Renders the specified ASP.NET Core HttpContext.WebSocketManager.IsWebSocketRequest variable and appends it to the specified . + /// + /// The to append the rendered data to. + /// Logging event. + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + // Not available on .NET 3.5 +#if ASP_NET_CORE + var websockets = HttpContextAccessor.HttpContext?.WebSockets; + if (websockets == null) + { + return; + } + + builder.Append(websockets.IsWebSocketRequest); +#elif NET46_OR_GREATER + var httpContext = HttpContextAccessor.HttpContext; + if (httpContext == null) + { + return; + } + builder.Append(httpContext.IsWebSocketRequest); +#endif + } + } +} diff --git a/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs new file mode 100644 index 00000000..645ccfae --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs @@ -0,0 +1,46 @@ +using System.Text; +using NLog.LayoutRenderers; +using NLog.Web.Internal; +#if ASP_NET_CORE +using Microsoft.AspNetCore.Http; +#else +using System.Web; +#endif + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Local IP of the Connection + /// + /// + /// ${aspnet-request-local-ip} + /// + [LayoutRenderer("aspnet-request-local-ip")] + public class AspNetRequestLocalIpLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Render Remote Port + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var httpContext = HttpContextAccessor.HttpContext; + +#if ASP_NET_CORE + var connection = httpContext.Connection; + if (connection == null) + { + return; + } + + builder.Append(connection.LocalIpAddress?.ToString()); +#else + var request = httpContext.TryGetRequest(); + if (request == null) + { + return; + } + builder.Append(request.ServerVariables?["LOCAL_ADDR"]); +#endif + } + } +} \ No newline at end of file diff --git a/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs new file mode 100644 index 00000000..e1103bf2 --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs @@ -0,0 +1,45 @@ +using System.Text; +using NLog.LayoutRenderers; +using NLog.Web.Internal; +#if ASP_NET_CORE +using Microsoft.AspNetCore.Http; +#else +using System.Web; +#endif + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Local Port of the Connection + /// + /// + /// ${aspnet-request-local-port} + /// + [LayoutRenderer("aspnet-request-local-port")] + public class AspNetRequestLocalPortLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Render Remote Port + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var httpContext = HttpContextAccessor.HttpContext; +#if ASP_NET_CORE + var connection = httpContext.Connection; + if (connection == null) + { + return; + } + + builder.Append(connection.LocalPort); +#else + var request = httpContext.TryGetRequest(); + if (request == null) + { + return; + } + builder.Append(request.ServerVariables?["LOCAL_PORT"]); +#endif + } + } +} \ No newline at end of file diff --git a/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs new file mode 100644 index 00000000..e2478a47 --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs @@ -0,0 +1,46 @@ +using System.Text; +using NLog.LayoutRenderers; +using NLog.Web.Internal; +#if ASP_NET_CORE +using Microsoft.AspNetCore.Http; +#else +using System.Web; +#endif + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Remote Port of the Connection + /// + /// + /// ${aspnet-request-remote-port} + /// + [LayoutRenderer("aspnet-request-remote-port")] + public class AspNetRequestRemotePortLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Render Remote Port + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var httpContext = HttpContextAccessor.HttpContext; + +#if ASP_NET_CORE + var connection = httpContext.Connection; + if (connection == null) + { + return; + } + + builder.Append(connection.RemotePort); +#else + var request = httpContext.TryGetRequest(); + if (request == null) + { + return; + } + builder.Append(request.ServerVariables?["REMOTE_PORT"]); +#endif + } + } +} \ No newline at end of file diff --git a/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs new file mode 100644 index 00000000..e64f0558 --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs @@ -0,0 +1,72 @@ +using System.Text; +using NLog.LayoutRenderers; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Web Socket Requested Protocols + /// + /// + /// ${aspnet-request-web-socket-requested-protocols} + /// + /// + /// Example usage of ${aspnet-request-web-socket-requested-protocols} + /// + /// ${aspnet-request-web-socket-requested-protocols:OutputFormat=Flat} + /// ${aspnet-request-web-socket-requested-protocols:OutputFormat=JsonArray} + /// + /// + [LayoutRenderer("aspnet-request-web-socket-requested-protocols")] + public class AspNetRequestWebSocketRequestedProtocolsLayoutRenderer : AspNetLayoutMultiValueRendererBase + { + /// + /// Renders the specified ASP.NET Core HttpContext.WebSocketManager.WebSocketRequestedProtocols variable and appends it to the specified . + /// + /// The to append the rendered data to. + /// Logging event. + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + // Not available on .NET 3.5 +#if ASP_NET_CORE + var websockets = HttpContextAccessor.HttpContext?.WebSockets; + if (websockets == null) + { + return; + } + + if (websockets.WebSocketRequestedProtocols == null) + { + return; + + } + + if (websockets.WebSocketRequestedProtocols.Count == 0) + { + return; + } + + SerializeValues(websockets.WebSocketRequestedProtocols, builder, logEvent); + +#elif NET46_OR_GREATER + var httpContext = HttpContextAccessor.HttpContext; + if (httpContext == null) + { + return; + } + + if (httpContext.WebSocketRequestedProtocols == null) + { + return; + + } + + if (httpContext.WebSocketRequestedProtocols.Count == 0) + { + return; + } + + SerializeValues(httpContext.WebSocketRequestedProtocols, builder, logEvent); +#endif + } + } +} diff --git a/src/Shared/LayoutRenderers/AspNetResponseContentTypeLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetResponseContentTypeLayoutRenderer.cs new file mode 100644 index 00000000..c1347e2b --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetResponseContentTypeLayoutRenderer.cs @@ -0,0 +1,37 @@ +using System.Text; +using NLog.Config; +using NLog.LayoutRenderers; +using NLog.Web.Internal; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET HttpResponse Content-Type Header + /// + /// + /// ${aspnet-request-contenttype} + /// + [LayoutRenderer("aspnet-response-contenttype")] + public class AspNetResponseContentTypeLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Renders the specified ASP.NET Application variable and appends it to the specified . + /// + /// The to append the rendered data to. + /// Logging event. + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var response = HttpContextAccessor.HttpContext.TryGetResponse(); + if (response == null) + { + return; + } + + var contentType = response.ContentType; + if (!string.IsNullOrEmpty(contentType)) + { + builder.Append(contentType); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRenderer.cs new file mode 100644 index 00000000..c3aad82a --- /dev/null +++ b/src/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRenderer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog.LayoutRenderers; +using NLog.Web.Internal; +#if !ASP_NET_CORE +using System.Collections.Specialized; +#else +using Microsoft.AspNetCore.Http; +#endif + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Response Headers + /// + /// + /// Example usage of ${aspnet-response-headers} + /// + /// ${aspnet-response-headers:OutputFormat=Flat} + /// ${aspnet-response-headers:OutputFormat=JsonArray} + /// ${aspnet-response-headers:OutputFormat=JsonDictionary} + /// ${aspnet-response-headers:OutputFormat=JsonDictionary:HeaderNames=username} + /// ${aspnet-response-headers:OutputFormat=JsonDictionary:Exclude=access_token} + /// + /// + [LayoutRenderer("aspnet-response-headers")] + public class AspNetResponseHeadersLayoutRenderer : AspNetLayoutMultiValueRendererBase + { + /// + /// Header names to be rendered. + /// If null or empty array, all headers will be rendered. + /// + public List HeaderNames { get; set; } + + /// + /// Gets or sets the keys to exclude from the output. If omitted, none are excluded. + /// + /// +#if ASP_NET_CORE + public ISet Exclude { get; set; } +#else + public HashSet Exclude { get; set; } +#endif + + /// + /// Initializes a new instance of the class. + /// + public AspNetResponseHeadersLayoutRenderer() + { + Exclude = new HashSet(new[] { "ALL_HTTP", "ALL_RAW", "AUTH_PASSWORD" }, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Renders the ASP.NET Headers appends it to the specified . + /// + /// The to append the rendered data to. + /// Logging event. + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var httpResponse = HttpContextAccessor.HttpContext.TryGetResponse(); + if (httpResponse == null) + { + return; + } + + var headers = httpResponse.Headers; + if (headers?.Count > 0) + { + bool checkForExclude = (HeaderNames == null || HeaderNames.Count == 0) && Exclude?.Count > 0; + var headersDictionary = GetHeaders(headers); + var headerValues = GetHeaderValues(headersDictionary, checkForExclude); + SerializePairs(headerValues, builder, logEvent); + } + } + +#if!ASP_NET_CORE + private static Dictionary GetHeaders(NameValueCollection headers) + { + return headers.Keys.Cast().ToDictionary(headerKey => headerKey, headerKey => headers[headerKey]); + } +#else + private static Dictionary GetHeaders(IHeaderDictionary headers) + { + return headers.Keys.ToDictionary(headerKey => headerKey, headerKey => headers[headerKey]); + } +#endif + private IEnumerable> GetHeaderValues(Dictionary headers, bool checkForExclude) + { + var headerNames = HeaderNames?.Count > 0 ? HeaderNames : headers.Keys.ToList(); + foreach (var headerName in headerNames) + { + if (checkForExclude && Exclude.Contains(headerName)) + { + continue; + } + + if (!headers.ContainsKey(headerName)) + { + continue; + } + + if (!headers.TryGetValue(headerName, out var headerValue)) + { + continue; + } + + yield return new KeyValuePair(headerName, headerValue); + } + } + } +} \ No newline at end of file diff --git a/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs new file mode 100644 index 00000000..db416df8 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Security.Cryptography.X509Certificates; +using System.Web; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestClientCertificateRendererTests : LayoutRenderersTestBase + { +#if ASP_NET_CORE + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + var certificate = new X509Certificate2( + Convert.FromBase64String( +"MIIC7DCCAdSgAwIBAgIQJq2oGnSgP79FVWGrezYIBDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIwMzExMDQyOTI4WhcNMjcwMzEwMDAwMDAwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfSpPySBJetaQdagcgNMs6owXj6QwDv4BKB/qRG4AM5myL3T+7cTiUUOaHywIR+79k2K+LVOLKzfXLJvrzYZSj3yd02ScmM4BEJEcbauY7wCYjgFfB6K9Klh87UAP6+gUUHjIVfzI/Go3883c9D29S3PbO3z5Yz1hTVS0Hes5ZE0d7TDevVSXm2ZpUZSPz7W50+FBq2z3uI3pSBg2oZYHhUvbFIhMI0VIFAPSiyU9XIo+RCv3eN27Fq8g3Qo0z+8wnk7zSldncVEZko5WGKNL781U/TDuhBigkvEme9goaRPRW8oNjm//v+vyJwo/WDzghSwc6jCIdjTXUIqxw4THBAgMBAAGjOjA4MAsGA1UdDwQEAwIEsDATBgNVHSUEDDAKBggrBgEFBQcDATAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAEqAzHpQgeiW1abdOj4LClM+8uU813tvFhepneLL59yvtZ2NT6ruSd7Fa15CT8bIKACgxzUOaB7N/KseORqLFrNiwvu2A1vvEkhueH0TLC2KJt7SvlEKw5QooiLdOIfNBaL8h/YE/TK9hXCdTgmsPuXuc47vxSyLUUzMGFlW4olFkOEHOP0havplJvPabLY/WpPTV0+mKUe+CKKEKNduH8il9heXguak06XGufP8UQP1fE+GVDFJDqX0S2TMcaoohxL2lV4VqNnadGJ/VA97ZDTWKFteDdTNwZYyb0KvxLtUCc6cHak9ZRs1E7+SZyNx/pcB4vgpnWPXKX8WDr3VGw0=")); + + httpContext.Connection.ClientCertificate.Returns(certificate); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(certificate.ToString(), result); + } + + [Fact] + public void NullTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.Connection.ClientCertificate.ReturnsNull(); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } +#else + [Fact] + public void SuccessTest() + { + var httpContext = Substitute.For(); + var renderer = new AspNetRequestClientCertificateLayoutRenderer(); + renderer.HttpContextAccessor = new FakeHttpContextAccessor(httpContext); + + // Arrange + var byteArray = Convert.FromBase64String( + "MIIC7DCCAdSgAwIBAgIQJq2oGnSgP79FVWGrezYIBDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIwMzExMDQyOTI4WhcNMjcwMzEwMDAwMDAwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfSpPySBJetaQdagcgNMs6owXj6QwDv4BKB/qRG4AM5myL3T+7cTiUUOaHywIR+79k2K+LVOLKzfXLJvrzYZSj3yd02ScmM4BEJEcbauY7wCYjgFfB6K9Klh87UAP6+gUUHjIVfzI/Go3883c9D29S3PbO3z5Yz1hTVS0Hes5ZE0d7TDevVSXm2ZpUZSPz7W50+FBq2z3uI3pSBg2oZYHhUvbFIhMI0VIFAPSiyU9XIo+RCv3eN27Fq8g3Qo0z+8wnk7zSldncVEZko5WGKNL781U/TDuhBigkvEme9goaRPRW8oNjm//v+vyJwo/WDzghSwc6jCIdjTXUIqxw4THBAgMBAAGjOjA4MAsGA1UdDwQEAwIEsDATBgNVHSUEDDAKBggrBgEFBQcDATAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAEqAzHpQgeiW1abdOj4LClM+8uU813tvFhepneLL59yvtZ2NT6ruSd7Fa15CT8bIKACgxzUOaB7N/KseORqLFrNiwvu2A1vvEkhueH0TLC2KJt7SvlEKw5QooiLdOIfNBaL8h/YE/TK9hXCdTgmsPuXuc47vxSyLUUzMGFlW4olFkOEHOP0havplJvPabLY/WpPTV0+mKUe+CKKEKNduH8il9heXguak06XGufP8UQP1fE+GVDFJDqX0S2TMcaoohxL2lV4VqNnadGJ/VA97ZDTWKFteDdTNwZYyb0KvxLtUCc6cHak9ZRs1E7+SZyNx/pcB4vgpnWPXKX8WDr3VGw0="); + + var certificate = new X509Certificate2(byteArray); + + // We need Microsoft Fakes here, but we only have Visual Studio 2022 Community, not Enterprise + // and we also cannot require NLog.Web community of developer to have Enterprise edition + //httpContext.Request.ClientCertificate = new HttpClientCertificate(byteArray); + + // This throws NullReferenceException + //httpContext.Request.ClientCertificate.Certificate.Returns(byteArray); + + var httpRequest = Substitute.For(); + httpContext.Request.Returns(httpRequest); + + // This throws NotSupportedException due to no public constructors, from Castle.DynamicProxy.Generators + //var clientCertificate = Substitute.For(); + + //clientCertificate.Certificate.Returns(byteArray); + //httpRequest.ClientCertificate.Returns(clientCertificate); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + //Assert.Equal(certificate.ToString(), result); + Assert.Equal(string.Empty, result); + } + [Fact] + public void NullTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.Request.ClientCertificate.ReturnsNull(); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } +#endif + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRendererTests.cs new file mode 100644 index 00000000..60e40a56 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRendererTests.cs @@ -0,0 +1,95 @@ +using NLog.Web.LayoutRenderers; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class + AspNetRequestIsWebSocketLayoutRendererTests : LayoutRenderersTestBase + { + +#if !ASP_NET_CORE && NET46_OR_GREATER + [Fact] + public void TrueCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.IsWebSocketRequest.Returns(true); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(bool.TrueString, result); + } + + [Fact] + public void FalseCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.IsWebSocketRequest.Returns(false); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(bool.FalseString, result); + } + +#endif + + +#if ASP_NET_CORE + [Fact] + public void TrueCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.WebSockets.IsWebSocketRequest.Returns(true); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(bool.TrueString, result); + } + + [Fact] + public void FalseCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSockets.IsWebSocketRequest.Returns(false); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(bool.FalseString, result); + } + + [Fact] + public void NullCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSockets.ReturnsNull(); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } +#endif + +#if ASP_NET_CORE || NET46_OR_GREATER + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal(bool.FalseString,result); + } +#endif + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetRequestLocalIpRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestLocalIpRendererTests.cs new file mode 100644 index 00000000..f69ba857 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestLocalIpRendererTests.cs @@ -0,0 +1,34 @@ +using System.Collections.Specialized; +using System.Net; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestLocalIpRendererTests : LayoutRenderersTestBase + { + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); +#if ASP_NET_CORE + httpContext.Connection.LocalIpAddress.Returns(IPAddress.Parse("127.0.0.1")); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("127.0.0.1", result); +#else + httpContext.Request.ServerVariables.Returns(new NameValueCollection + { + {"LOCAL_ADDR","127.0.0.1"} + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("127.0.0.1", result); +#endif + } + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetRequestLocalPortRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestLocalPortRendererTests.cs new file mode 100644 index 00000000..c310e49e --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestLocalPortRendererTests.cs @@ -0,0 +1,54 @@ +using System.Collections.Specialized; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestLocalPortRendererTests : LayoutRenderersTestBase + { +#if ASP_NET_CORE + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.Connection.LocalPort.Returns(8080); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("8080", result); + } + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal("0",result); + } +#else + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.Request.ServerVariables.Returns(new NameValueCollection + { + {"LOCAL_PORT", "8080"} + }); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal("8080", result); + } +#endif + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetRequestRemotePortRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestRemotePortRendererTests.cs new file mode 100644 index 00000000..d0f61672 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestRemotePortRendererTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Specialized; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestRemotePortRendererTests : LayoutRenderersTestBase + { + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); +#if ASP_NET_CORE + httpContext.Connection.RemotePort.Returns(8080); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("8080", result); +#else + httpContext.Request.ServerVariables.Returns(new NameValueCollection + { + {"REMOTE_PORT","8080"} + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("8080", result); +#endif + } + +#if ASP_NET_CORE + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal("0",result); + } +#endif + + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRendererTests.cs new file mode 100644 index 00000000..e5fbdb30 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRendererTests.cs @@ -0,0 +1,144 @@ +using System.Collections; +using System.Collections.Generic; +using NLog.Web.Enums; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class + AspNetRequestWebSocketRequestedProtocolsLayoutRendererTests : LayoutRenderersTestBase + { + +#if !ASP_NET_CORE && NET46_OR_GREATER + [Fact] + public void SuccessCaseFlat() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.WebSocketRequestedProtocols.Returns( + new List() + { + "XML", + "Json", + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("XML,Json", result); + } + + [Fact] + public void SuccessCaseJson() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.Json; + httpContext.WebSocketRequestedProtocols.Returns( + new List() + { + "XML", + "Json", + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("[\"XML\",\"Json\"]", result); + } + + [Fact] + public void NullCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSocketRequestedProtocols.Returns((IList)null); + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(string.Empty, result); + } + + [Fact] + public void EmptyCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSocketRequestedProtocols.Returns(new List()); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } +#endif + + +#if ASP_NET_CORE + [Fact] + public void SuccessCaseFlat() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.WebSockets.WebSocketRequestedProtocols.Returns( + new List() + { + "XML", + "Json", + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("XML,Json", result); + } + + [Fact] + public void SuccessCaseJson() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.Json; + httpContext.WebSockets.WebSocketRequestedProtocols.Returns( + new List() + { + "XML", + "Json", + }); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("[\"XML\",\"Json\"]", result); + } + + [Fact] + public void NullCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSockets.WebSocketRequestedProtocols.Returns((IList)null); + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(string.Empty, result); + } + + [Fact] + public void EmptyCase() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.WebSockets.WebSocketRequestedProtocols.Returns(new List()); + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } +#endif + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetResponseContentTypeRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetResponseContentTypeRendererTests.cs new file mode 100644 index 00000000..6cb5d432 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetResponseContentTypeRendererTests.cs @@ -0,0 +1,23 @@ +using NLog.Web.LayoutRenderers; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetResponseContentTypeRendererTests : LayoutRenderersTestBase + { + [Fact] + public void StatusCode_Set_Renderer() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.Response.ContentType.Returns("text/json"); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal("text/json", result); + } + } +} diff --git a/tests/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRendererTests.cs new file mode 100644 index 00000000..a3572c65 --- /dev/null +++ b/tests/Shared/LayoutRenderers/AspNetResponseHeadersLayoutRendererTests.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using NLog.Web.LayoutRenderers; +using NLog.Web.Enums; +using Xunit; +using System.Collections.Specialized; + +#if !ASP_NET_CORE +using NSubstitute; +using System.Web; +#else +using Microsoft.Extensions.Primitives; +#endif + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetResponseHeadersLayoutRendererTests : TestInvolvingAspNetHttpContext + { + [Fact] + public void NullKeyRendersAllHeaders() + { + var expectedResult = "key=TEST,Key1=TEST1"; + var renderer = CreateRenderer(); + renderer.HeaderNames = null; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void NullKeyRendersAllHeadersExceptExcluded() + { + var expectedResult = "Key1=TEST1"; + var renderer = CreateRenderer(); + renderer.HeaderNames = null; + renderer.Exclude.Add("key"); + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyNotFoundRendersEmptyString_Flat_Formatting() + { + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.Flat; + renderer.HeaderNames = new List { "notfound" }; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Empty(result); + } + + [Fact] + public void KeyNotFoundRendersEmptyString_Json_Formatting() + { + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray; + renderer.HeaderNames = new List { "notfound" }; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Empty(result); + } + + [Fact] + public void KeyFoundRendersValue_Multiple_Headers_Flat_Formatting() + { + var expectedResult = "key=TEST,Key1=TEST1"; + + var renderer = CreateRenderer(); + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Multiple_Headers_Flat_Formatting_separators() + { + var expectedResult = "key:TEST|Key1:TEST1"; + + var renderer = CreateRenderer(); + renderer.ValueSeparator = ":"; + renderer.ItemSeparator = "|"; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Multiple_Headers_Flat_Formatting_separators_layouts() + { + var expectedResult = "key>TEST" + Environment.NewLine + "Key1>TEST1"; + + var renderer = CreateRenderer(); + renderer.ValueSeparator = "${event-properties:valueSeparator1}"; + renderer.ItemSeparator = "${newline}"; + + var logEventInfo = new LogEventInfo(); + logEventInfo.Properties["valueSeparator1"] = ">"; + + // Act + string result = renderer.Render(logEventInfo); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Header_Flat_Formatting() + { + var expectedResult = "key=TEST"; + + var renderer = CreateRenderer(addSecondHeader: false); + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Header_Json_Formatting() + { + var expectedResult = "[{\"key\":\"TEST\"}]"; + + var renderer = CreateRenderer(addSecondHeader: false); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Header_Json_Formatting_no_array() + { + var expectedResult = "{\"key\":\"TEST\"}"; + + var renderer = CreateRenderer(addSecondHeader: false); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonDictionary; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Multiple_Headers_Json_Formatting() + { + var expectedResult = "[{\"key\":\"TEST\"},{\"Key1\":\"TEST1\"}]"; + + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Multiple_Headers_Json_Formatting_no_array() + { + var expectedResult = "{\"key\":\"TEST\",\"Key1\":\"TEST1\"}"; + + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonDictionary; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyNotFoundRendersEmptyString_Flat_Formatting_ValuesOnly() + { + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.Flat; + renderer.HeaderNames = new List { "notfound" }; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Empty(result); + } + + [Fact] + public void KeyNotFoundRendersEmptyString_Json_Formatting_ValuesOnly() + { + var renderer = CreateRenderer(); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray; + renderer.HeaderNames = new List { "notfound" }; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Empty(result); + } + + [Fact] + public void KeyFoundRendersValue_Header_Multiple_Items_Flat_Formatting_ValuesOnly() + { + var expectedResult = "TEST,TEST1"; + + var renderer = CreateRenderer(); + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Header_Multiple_Items_Flat_Formatting_separators_ValuesOnly() + { + var expectedResult = "TEST|TEST1"; + + var renderer = CreateRenderer(); + renderer.ValueSeparator = ":"; + renderer.ItemSeparator = "|"; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Item_Flat_Formatting_ValuesOnly() + { + var expectedResult = "TEST"; + + var renderer = CreateRenderer(addSecondHeader: false); + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Item_Json_Formatting_ValuesOnly() + { + var expectedResult = "[\"TEST\"]"; + + var renderer = CreateRenderer(addSecondHeader: false); + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Single_Item_Json_Formatting_no_array_ValuesOnly() + { + // With ValuesOnly enabled, only arrays are valid + var expectedResult = "[\"TEST\"]"; + + var renderer = CreateRenderer(addSecondHeader: false); + + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonDictionary; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void KeyFoundRendersValue_Header_Multiple_Items_Json_Formatting_ValuesOnly() + { + var expectedResult = "[\"TEST\",\"TEST1\"]"; + + var renderer = CreateRenderer(); + + renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonDictionary; + renderer.ValuesOnly = true; + + string result = renderer.Render(new LogEventInfo()); + + Assert.Equal(expectedResult, result); + } + + /// + /// Create headers renderer with mocked HTTP context + /// + /// Add second header + /// Created headers layout renderer + private AspNetResponseHeadersLayoutRenderer CreateRenderer(bool addSecondHeader = true) + { + var headerNames = new List(); +#if ASP_NET_CORE + var httpContext = HttpContext; +#else + var httpContext = Substitute.For(); +#endif + +#if ASP_NET_CORE + headerNames.Add("key"); + httpContext.Response.Headers.Add("key", new StringValues("TEST")); + + if (addSecondHeader) + { + headerNames.Add("Key1"); + httpContext.Response.Headers.Add("Key1", new StringValues("TEST1")); + } +#else + var headers = new NameValueCollection(); + headers.Add("key", "TEST"); + headerNames.Add("key"); + + if (addSecondHeader) + { + headers.Add("Key1", "TEST1"); + headerNames.Add("Key1"); + } + + httpContext.Response.Headers.Returns(headers); +#endif + + var renderer = new AspNetResponseHeadersLayoutRenderer(); + renderer.HttpContextAccessor = new FakeHttpContextAccessor(httpContext); + renderer.HeaderNames = headerNames; + return renderer; + } + } +}