#280 Add headers to response (#286)

* #280 can now add response headers inc trace id, now need to consolidate the header place holder stuff

* #280 changed port for linux tests

* #280 lots of hacking around to handle errors and consolidate placeholders into one class
This commit is contained in:
Tom Pallister
2018-03-18 14:58:39 +00:00
committed by GitHub
parent 978b0a43a0
commit b51df71d7b
31 changed files with 700 additions and 60 deletions

View File

@ -70,7 +70,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort
{
Host = "localhost",
Port = 51888,
Port = 51388,
}
},
UpstreamPathTemplate = "/api002/values",
@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests
var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl))
.And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
@ -109,6 +109,60 @@ namespace Ocelot.AcceptanceTests
commandOnAllStateMachines.ShouldBeTrue();
}
[Fact]
public void should_return_tracing_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51387,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak = 10,
TimeoutValue = 5000
},
DownstreamHeaderTransform = new Dictionary<string, string>()
{
{"Trace-Id", "{TraceId}"},
{"Tom", "Laura"}
}
}
}
};
var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
.BDDfy();
}
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
{
_serviceOneBuilder = new WebHostBuilder()

View File

@ -318,6 +318,12 @@ namespace Ocelot.AcceptanceTests
header.First().ShouldBe(value);
}
public void ThenTheTraceHeaderIsSet(string key)
{
var header = _response.Headers.GetValues(key);
header.First().ShouldNotBeNullOrEmpty();
}
public void GivenOcelotIsRunningUsingJsonSerializedCache()
{
_webHostBuilder = new WebHostBuilder();

View File

@ -34,10 +34,10 @@ namespace Ocelot.UnitTests.Configuration
_fileConfig = new FileConfiguration();
_config = new Mock<IConsulPollerConfiguration>();
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
_config.Setup(x => x.Delay).Returns(10);
_config.Setup(x => x.Delay).Returns(100);
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
}
public void Dispose()
{
_poller.Dispose();

View File

@ -826,7 +826,8 @@ namespace Ocelot.UnitTests.Configuration
result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count);
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
}
}
@ -909,7 +910,7 @@ namespace Ocelot.UnitTests.Configuration
private void GivenTheHeaderFindAndReplaceCreatorReturns()
{
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>()));
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>()));
}
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)

View File

@ -5,7 +5,11 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.UnitTests.Responder;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
@ -17,12 +21,17 @@ namespace Ocelot.UnitTests.Configuration
private HeaderFindAndReplaceCreator _creator;
private FileReRoute _reRoute;
private HeaderTransformations _result;
private Mock<IBaseUrlFinder> _finder;
private Mock<IPlaceholders> _placeholders;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IOcelotLogger> _logger;
public HeaderFindAndReplaceCreatorTests()
{
_finder = new Mock<IBaseUrlFinder>();
_creator = new HeaderFindAndReplaceCreator(_finder.Object);
_logger = new Mock<IOcelotLogger>();
_factory = new Mock<IOcelotLoggerFactory>();
_factory.Setup(x => x.CreateLogger<HeaderFindAndReplaceCreator>()).Returns(_logger.Object);
_placeholders = new Mock<IPlaceholders>();
_creator = new HeaderFindAndReplaceCreator(_placeholders.Object, _factory.Object);
}
[Fact]
@ -84,6 +93,40 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy();
}
[Fact]
public void should_log_errors_and_not_add_headers()
{
var reRoute = new FileReRoute
{
DownstreamHeaderTransform = new Dictionary<string, string>
{
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
},
UpstreamHeaderTransform = new Dictionary<string, string>
{
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
}
};
var expected = new List<HeaderFindAndReplace>
{
};
this.Given(x => GivenTheReRoute(reRoute))
.And(x => GivenTheBaseUrlErrors())
.When(x => WhenICreate())
.Then(x => ThenTheFollowingDownstreamIsReturned(expected))
.And(x => ThenTheFollowingUpstreamIsReturned(expected))
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add DownstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add UpstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
.BDDfy();
}
private void ThenTheLoggerIsCalledCorrectly(string message)
{
_logger.Verify(x => x.LogError(message), Times.Once);
}
[Fact]
public void should_use_base_url_partial_placeholder()
{
@ -107,9 +150,41 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy();
}
[Fact]
public void should_add_trace_id_header()
{
var reRoute = new FileReRoute
{
DownstreamHeaderTransform = new Dictionary<string, string>
{
{"Trace-Id", "{TraceId}"},
}
};
var expected = new AddHeader("Trace-Id", "{TraceId}");
this.Given(x => GivenTheReRoute(reRoute))
.And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
.When(x => WhenICreate())
.Then(x => ThenTheFollowingAddHeaderIsReturned(expected))
.BDDfy();
}
private void GivenTheBaseUrlIs(string baseUrl)
{
_finder.Setup(x => x.Find()).Returns(baseUrl);
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new OkResponse<string>(baseUrl));
}
private void GivenTheBaseUrlErrors()
{
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
}
private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader)
{
_result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key);
_result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value);
}
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)

View File

@ -0,0 +1,148 @@
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using Ocelot.Headers;
using System.Net.Http;
using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using System.Linq;
using Moq;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses;
using Ocelot.Infrastructure;
using Ocelot.UnitTests.Responder;
using System;
using Ocelot.Logging;
namespace Ocelot.UnitTests.Headers
{
public class AddHeadersToResponseTests
{
private IAddHeadersToResponse _adder;
private Mock<IPlaceholders> _placeholders;
private HttpResponseMessage _response;
private List<AddHeader> _addHeaders;
private Mock<IOcelotLoggerFactory> _factory;
private Mock<IOcelotLogger> _logger;
public AddHeadersToResponseTests()
{
_factory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_factory.Setup(x => x.CreateLogger<AddHeadersToResponse>()).Returns(_logger.Object);
_placeholders = new Mock<IPlaceholders>();
_adder = new AddHeadersToResponse(_placeholders.Object, _factory.Object);
}
[Fact]
public void should_add_header()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Laura", "Tom")
};
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.And(_ => ThenTheHeaderIsReturned("Laura", "Tom"))
.BDDfy();
}
[Fact]
public void should_add_trace_id_placeholder()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}")
};
var traceId = "123";
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdIs(traceId))
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
.BDDfy();
}
[Fact]
public void should_add_trace_id_placeholder_and_normal()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}"),
new AddHeader("Tom", "Laura")
};
var traceId = "123";
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdIs(traceId))
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
.Then(_ => ThenTheHeaderIsReturned("Tom", "Laura"))
.BDDfy();
}
[Fact]
public void should_do_nothing_and_log_error()
{
var addHeaders = new List<AddHeader>
{
new AddHeader("Trace-Id", "{TraceId}")
};
this.Given(_ => GivenAResponseMessage())
.And(_ => GivenTheTraceIdErrors())
.And(_ => GivenTheAddHeaders(addHeaders))
.When(_ => WhenIAdd())
.Then(_ => ThenTheHeaderIsNotAdded("Trace-Id"))
.And(_ => ThenTheErrorIsLogged())
.BDDfy();
}
private void ThenTheErrorIsLogged()
{
_logger.Verify(x => x.LogError("Unable to add header to response Trace-Id: {TraceId}"), Times.Once);
}
private void ThenTheHeaderIsNotAdded(string key)
{
_response.Headers.TryGetValues(key, out var values).ShouldBeFalse();
}
private void GivenTheTraceIdIs(string traceId)
{
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new OkResponse<string>(traceId));
}
private void GivenTheTraceIdErrors()
{
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new ErrorResponse<string>(new AnyError()));
}
private void ThenTheHeaderIsReturned(string key, string value)
{
var values = _response.Headers.GetValues(key);
values.First().ShouldBe(value);
}
private void WhenIAdd()
{
_adder.Add(_addHeaders, _response);
}
private void GivenAResponseMessage()
{
_response = new HttpResponseMessage();
}
private void GivenTheAddHeaders(List<AddHeader> addHeaders)
{
_addHeaders = addHeaders;
}
}
}

View File

@ -26,6 +26,7 @@ namespace Ocelot.UnitTests.Headers
private HttpHeadersTransformationMiddleware _middleware;
private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next;
private Mock<IAddHeadersToResponse> _addHeaders;
public HttpHeadersTransformationMiddlewareTests()
{
@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Headers
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask;
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object);
_addHeaders = new Mock<IAddHeadersToResponse>();
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object);
}
[Fact]
@ -49,9 +51,16 @@ namespace Ocelot.UnitTests.Headers
.When(x => WhenICallTheMiddleware())
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
.And(x => ThenAddHeadersIsCalledCorrectly())
.BDDfy();
}
private void ThenAddHeadersIsCalledCorrectly()
{
_addHeaders
.Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once);
}
private void WhenICallTheMiddleware()
{
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();

View File

@ -7,20 +7,30 @@ using Ocelot.Configuration;
using System.Collections.Generic;
using Ocelot.Responses;
using System.Linq;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.UnitTests.Headers
{
public class HttpResponseHeaderReplacerTests
{
private HttpResponseMessage _response;
private Placeholders _placeholders;
private HttpResponseHeaderReplacer _replacer;
private List<HeaderFindAndReplace> _headerFindAndReplaces;
private Response _result;
private HttpRequestMessage _request;
private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo;
public HttpResponseHeaderReplacerTests()
{
_replacer = new HttpResponseHeaderReplacer();
_repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object);
_replacer = new HttpResponseHeaderReplacer(_placeholders);
}
[Fact]

View File

@ -0,0 +1,79 @@
using System;
using System.Net.Http;
using Moq;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Responses;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Infrastructure
{
public class PlaceholdersTests
{
private IPlaceholders _placeholders;
private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo;
public PlaceholdersTests()
{
_repo = new Mock<IRequestScopedDataRepository>();
_finder = new Mock<IBaseUrlFinder>();
_placeholders = new Placeholders(_finder.Object, _repo.Object);
}
[Fact]
public void should_return_base_url()
{
var baseUrl = "http://www.bbc.co.uk";
_finder.Setup(x => x.Find()).Returns(baseUrl);
var result = _placeholders.Get("{BaseUrl}");
result.Data.ShouldBe(baseUrl);
}
[Fact]
public void should_return_key_does_not_exist()
{
var result = _placeholders.Get("{Test}");
result.IsError.ShouldBeTrue();
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
}
[Fact]
public void should_return_downstream_base_url_when_port_is_not_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk");
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk/");
}
[Fact]
public void should_return_downstream_base_url_when_port_is_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk:123");
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk:123/");
}
[Fact]
public void should_return_key_does_not_exist_for_http_request_message()
{
var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage());
result.IsError.ShouldBeTrue();
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
}
[Fact]
public void should_return_trace_id()
{
var traceId = "123";
_repo.Setup(x => x.Get<string>("TraceId")).Returns(new OkResponse<string>(traceId));
var result = _placeholders.Get("{TraceId}");
result.Data.ShouldBe(traceId);
}
}
}

View File

@ -1,5 +1,6 @@
using Butterfly.Client.Tracing;
using Moq;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Requester;
using Shouldly;
using Xunit;
@ -10,11 +11,13 @@ namespace Ocelot.UnitTests.Requester
{
private TracingHandlerFactory _factory;
private Mock<IServiceTracer> _tracer;
private Mock<IRequestScopedDataRepository> _repo;
public TracingHandlerFactoryTests()
{
_tracer = new Mock<IServiceTracer>();
_factory = new TracingHandlerFactory(_tracer.Object);
_repo = new Mock<IRequestScopedDataRepository>();
_factory = new TracingHandlerFactory(_tracer.Object, _repo.Object);
}
[Fact]

View File

@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed.
// You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion.
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
}
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)