diff --git a/SearchApi.Tests/Properties/launchSettings.json b/SearchApi.Tests/Properties/launchSettings.json new file mode 100644 index 00000000..d521fe12 --- /dev/null +++ b/SearchApi.Tests/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51761/", + "sslPort": 44398 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "LBH_search_api.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/SearchApi.Tests/V1/Controllers/BaseControllerTests.cs b/SearchApi.Tests/V1/Controllers/BaseControllerTests.cs new file mode 100644 index 00000000..0333ca47 --- /dev/null +++ b/SearchApi.Tests/V1/Controllers/BaseControllerTests.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using NUnit.Framework; +using SearchApi.V1.Controllers; +using SearchApi.V1.Infrastructure; + +namespace SearchApi.Tests.V1.Controllers +{ + [TestFixture] + public class BaseControllerTests + { + private BaseController _sut; + private ControllerContext _controllerContext; + private HttpContext _stubHttpContext; + + [SetUp] + public void Init() + { + _stubHttpContext = new DefaultHttpContext(); + _controllerContext = new ControllerContext(new ActionContext(_stubHttpContext, new RouteData(), new ControllerActionDescriptor())); + _sut = new BaseController(); + + _sut.ControllerContext = _controllerContext; + } + + [Test] + public void GetCorrelationShouldThrowExceptionIfCorrelationHeaderUnavailable() + { + // Arrange + Act + Assert + _sut.Invoking(x => x.GetCorrelationId()) + .Should().Throw() + .WithMessage("Request is missing a correlationId"); + } + + [Test] + public void GetCorrelationShouldReturnCorrelationIdWhenExists() + { + // Arrange + _stubHttpContext.Request.Headers.Add(Constants.CorrelationId, "123"); + + // Act + var result = _sut.GetCorrelationId(); + + // Assert + result.Should().BeEquivalentTo("123"); + } + } +} diff --git a/SearchApi.Tests/V1/Gateways/ExampleGatewayTests.cs b/SearchApi.Tests/V1/Gateways/ExampleGatewayTests.cs index 4511b7d6..60b8d187 100644 --- a/SearchApi.Tests/V1/Gateways/ExampleGatewayTests.cs +++ b/SearchApi.Tests/V1/Gateways/ExampleGatewayTests.cs @@ -10,7 +10,7 @@ namespace SearchApi.Tests.V1.Gateways //TODO: Rename Tests to match gateway name //For instruction on how to run tests please see the wiki: https://github.com/LBHackney-IT/lbh-base-api/wiki/Running-the-test-suite. [TestFixture] - [Ignore("Deciding on what DB to use")] + [Ignore("Not needed until we decide on DB")] public class ExampleGatewayTests : DatabaseTests { private readonly Fixture _fixture = new Fixture(); diff --git a/SearchApi.Tests/V1/Infrastructure/CorrelationMiddlewareTest.cs b/SearchApi.Tests/V1/Infrastructure/CorrelationMiddlewareTest.cs new file mode 100644 index 00000000..1dc4a5da --- /dev/null +++ b/SearchApi.Tests/V1/Infrastructure/CorrelationMiddlewareTest.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using NUnit.Framework; +using SearchApi.V1.Infrastructure; + +namespace SearchApi.Tests.V1.Infrastructure +{ + [TestFixture] + public class CorrelationMiddlewareTest + { + private CorrelationMiddleware _sut; + + [SetUp] + public void Init() + { + _sut = new CorrelationMiddleware(null); + } + + [Test] + public async Task DoesNotReplaceCorrelationIdIfOneExists() + { + // Arrange + var httpContext = new DefaultHttpContext(); + var headerValue = "123"; + + httpContext.HttpContext.Request.Headers.Add(Constants.CorrelationId, headerValue); + + // Act + await _sut.InvokeAsync(httpContext).ConfigureAwait(false); + + // Assert + httpContext.HttpContext.Request.Headers[Constants.CorrelationId].Should().BeEquivalentTo(headerValue); + } + + [Test] + public async Task AddsCorrelationIdIfOneDoesNotExist() + { + // Arrange + var httpContext = new DefaultHttpContext(); + + // Act + await _sut.InvokeAsync(httpContext).ConfigureAwait(false); + + // Assert + httpContext.HttpContext.Request.Headers[Constants.CorrelationId].Should().HaveCountGreaterThan(0); + } + } +} diff --git a/SearchApi.Tests/V1/Infrastructure/ExampleContextTests.cs b/SearchApi.Tests/V1/Infrastructure/ExampleContextTests.cs index 84725d50..3728f6d9 100644 --- a/SearchApi.Tests/V1/Infrastructure/ExampleContextTests.cs +++ b/SearchApi.Tests/V1/Infrastructure/ExampleContextTests.cs @@ -5,7 +5,7 @@ namespace SearchApi.Tests.V1.Infrastructure { [TestFixture] - [Ignore("Deciding on what DB to use")] + [Ignore("Not needed until we decide on DB")] public class DatabaseContextTest : DatabaseTests { [Test] diff --git a/SearchApi/Startup.cs b/SearchApi/Startup.cs index 3e61b3af..d092efab 100644 --- a/SearchApi/Startup.cs +++ b/SearchApi/Startup.cs @@ -132,6 +132,8 @@ private static void RegisterUseCases(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseCorrelation(); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/SearchApi/V1/Controllers/BaseController.cs b/SearchApi/V1/Controllers/BaseController.cs index 62a2e245..5e0fd00c 100644 --- a/SearchApi/V1/Controllers/BaseController.cs +++ b/SearchApi/V1/Controllers/BaseController.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using SearchApi.V1.Infrastructure; namespace SearchApi.V1.Controllers { @@ -11,6 +13,14 @@ public BaseController() ConfigureJsonSerializer(); } + public string GetCorrelationId() + { + if (HttpContext.Request.Headers[Constants.CorrelationId].Count == 0) + throw new KeyNotFoundException("Request is missing a correlationId"); + + return HttpContext.Request.Headers[Constants.CorrelationId]; + } + public static void ConfigureJsonSerializer() { JsonConvert.DefaultSettings = () => diff --git a/SearchApi/V1/Infrastructure/Constants.cs b/SearchApi/V1/Infrastructure/Constants.cs new file mode 100644 index 00000000..cdc0e4f1 --- /dev/null +++ b/SearchApi/V1/Infrastructure/Constants.cs @@ -0,0 +1,7 @@ +namespace SearchApi.V1.Infrastructure +{ + public static class Constants + { + public const string CorrelationId = "x-correlation-id"; + } +} diff --git a/SearchApi/V1/Infrastructure/CorrelationMiddleware.cs b/SearchApi/V1/Infrastructure/CorrelationMiddleware.cs new file mode 100644 index 00000000..33fe71f8 --- /dev/null +++ b/SearchApi/V1/Infrastructure/CorrelationMiddleware.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace SearchApi.V1.Infrastructure +{ + public class CorrelationMiddleware + { + private readonly RequestDelegate _next; + + public CorrelationMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + if (context.Request.Headers[Constants.CorrelationId].Count == 0) + { + context.Request.Headers[Constants.CorrelationId] = Guid.NewGuid().ToString(); + } + + if (_next != null) + await _next(context).ConfigureAwait(false); + } + } + + public static class CorrelationMiddlewareExtensions + { + public static IApplicationBuilder UseCorrelation( + this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +}