From dc639ed65d35549d243517e43ad4d395b582b3ab Mon Sep 17 00:00:00 2001 From: hippy Date: Mon, 4 Sep 2023 20:13:55 -0700 Subject: [PATCH] dh: Teach to handle multiple correlationIds --- .../CorrelationIdHandler.cs | 36 +++-- .../rm.DelegatingHandlers.csproj | 1 + .../CorrelationIdHandlerTests.cs | 126 +++++++++++++++++- 3 files changed, 152 insertions(+), 11 deletions(-) diff --git a/src/rm.DelegatingHandlers/CorrelationIdHandler.cs b/src/rm.DelegatingHandlers/CorrelationIdHandler.cs index 796dae1..a1a7dd5 100644 --- a/src/rm.DelegatingHandlers/CorrelationIdHandler.cs +++ b/src/rm.DelegatingHandlers/CorrelationIdHandler.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Primitives; using rm.Extensions; namespace rm.DelegatingHandlers; @@ -31,23 +33,39 @@ protected override async Task SendAsync( throw new InvalidOperationException($"{RequestHeaders.CorrelationId} header already present with value '{value}'"); } - var correlationId = correlationIdContext.GetValue(); - if (correlationId.IsNullOrWhiteSpace()) + var correlationIds = correlationIdContext.GetValue().Where(c => !c.IsNullOrWhiteSpace()); + if (correlationIds.IsNullOrEmpty()) { throw new InvalidOperationException($"{RequestHeaders.CorrelationId} header value cannot be null/empty/whitespace"); } - request.Headers.Add(RequestHeaders.CorrelationId, correlationId); + foreach (var correlationId in correlationIds) + { + request.Headers.Add(RequestHeaders.CorrelationId, correlationId); + } var response = await base.SendAsync(request, cancellationToken) .ConfigureAwait(false); - // add correlationId to response header - // if correlationId already present, noop - // assume valid correlationId value if already present - if (!response.Headers.Contains(ResponseHeaders.CorrelationId)) + // add correlationId(s) to response header + // if correlationId(s) already present, dedupe but can't guarantee order + response.Headers.TryGetValues(ResponseHeaders.CorrelationId, out var responseCorrelationIds); + responseCorrelationIds = responseCorrelationIds.EmptyIfDefault(); + foreach (var correlationId in correlationIds) { - response.Headers.Add(ResponseHeaders.CorrelationId, correlationId); + var found = false; + foreach (var responseCorrelationId in responseCorrelationIds) + { + if (correlationId == responseCorrelationId) + { + found = true; + break; + } + } + if (!found) + { + response.Headers.Add(ResponseHeaders.CorrelationId, correlationId); + } } return response; @@ -62,5 +80,5 @@ public interface ICorrelationIdContext /// /// Returns correlationId value. /// - string GetValue(); + StringValues GetValue(); } diff --git a/src/rm.DelegatingHandlers/rm.DelegatingHandlers.csproj b/src/rm.DelegatingHandlers/rm.DelegatingHandlers.csproj index 2716db8..871b968 100644 --- a/src/rm.DelegatingHandlers/rm.DelegatingHandlers.csproj +++ b/src/rm.DelegatingHandlers/rm.DelegatingHandlers.csproj @@ -27,6 +27,7 @@ + diff --git a/tests/rm.DelegatingHandlersTest/CorrelationIdHandlerTests.cs b/tests/rm.DelegatingHandlersTest/CorrelationIdHandlerTests.cs index ddf46c0..0056fe2 100644 --- a/tests/rm.DelegatingHandlersTest/CorrelationIdHandlerTests.cs +++ b/tests/rm.DelegatingHandlersTest/CorrelationIdHandlerTests.cs @@ -1,6 +1,7 @@ using System.Net; using AutoFixture; using AutoFixture.AutoMoq; +using Microsoft.Extensions.Primitives; using Moq; using NUnit.Framework; using rm.DelegatingHandlers; @@ -32,11 +33,88 @@ public async Task Adds_CorrelationId() } [Test] - public async Task Does_Not_Add_CorrelationId_To_Response_If_Already_Present() + public async Task Adds_Multiple_CorrelationIds() { var fixture = new Fixture().Customize(new AutoMoqCustomization()); var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); - var correlationIdAlreadyPresent = "oops!"; + var correlationId1 = "boom1!"; + var correlationId2 = "boom2!"; + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(new StringValues(new[] { correlationId1, correlationId2, })); + var correlationIdHandler = fixture.Create(); + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + using var response = await invoker.SendAsync(request, CancellationToken.None); + + Assert.IsTrue(request.Headers.Contains(RequestHeaders.CorrelationId)); + Assert.AreEqual(new[] { correlationId1, correlationId2 }, request.Headers.GetValues(RequestHeaders.CorrelationId)); + + Assert.IsTrue(response.Headers.Contains(RequestHeaders.CorrelationId)); + Assert.AreEqual(new[] { correlationId1, correlationId2 }, response.Headers.GetValues(RequestHeaders.CorrelationId)); + } + + [Test] + public void Throws_If_CorrelationId_Values_Is_Null_Value() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(new StringValues(new string[] { null!, })); + var correlationIdHandler = fixture.Create(); + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + Assert.ThrowsAsync(async () => + { + using var response = await invoker.SendAsync(request, CancellationToken.None); + }); + } + + [Test] + public void Throws_If_CorrelationId_Values_Is_Empty_Value() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(new StringValues(new string[] { })); + var correlationIdHandler = fixture.Create(); + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + Assert.ThrowsAsync(async () => + { + using var response = await invoker.SendAsync(request, CancellationToken.None); + }); + } + + [Test] + public void Throws_If_CorrelationId_Values_Is_WhiteSpace_Value() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(new StringValues(new string[] { "" })); + var correlationIdHandler = fixture.Create(); + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + Assert.ThrowsAsync(async () => + { + using var response = await invoker.SendAsync(request, CancellationToken.None); + }); + } + + [Test] + public async Task Does_Not_Add_CorrelationId_To_Response_If_Same_Value_Already_Present() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdAlreadyPresent = "boom!"; var addsCorrelationIdToResponseHandler = new AddsCorrelationIdToResponseHandler(correlationIdAlreadyPresent); var correlationId = "boom!"; var correlationIdContextMock = fixture.Freeze>(); @@ -51,4 +129,48 @@ public async Task Does_Not_Add_CorrelationId_To_Response_If_Already_Present() Assert.IsTrue(response.Headers.Contains(RequestHeaders.CorrelationId)); Assert.AreEqual(correlationIdAlreadyPresent, response.Headers.GetValues(RequestHeaders.CorrelationId).Single()); } + + [Test] + public async Task Adds_CorrelationId_To_Response_If_Different_Value_Present() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdAlreadyPresent = "oops!"; + var addsCorrelationIdToResponseHandler = new AddsCorrelationIdToResponseHandler(correlationIdAlreadyPresent); + var correlationId = "boom!"; + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(correlationId); + var correlationIdHandler = fixture.Create(); + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, addsCorrelationIdToResponseHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + using var response = await invoker.SendAsync(request, CancellationToken.None); + + Assert.IsTrue(response.Headers.Contains(RequestHeaders.CorrelationId)); + Assert.AreEqual(new[] { correlationIdAlreadyPresent, correlationId }, response.Headers.GetValues(RequestHeaders.CorrelationId)); + } + + [Test] + public async Task Adds_CorrelationId_Deduped_To_Response_If_Different_Value_Present() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var shortCircuitingCannedResponseHandler = new ShortCircuitingCannedResponseHandler(new HttpResponseMessage(HttpStatusCode.OK)); + var correlationIdAlreadyPresent = "boom2!"; + var addsCorrelationIdToResponseHandler = new AddsCorrelationIdToResponseHandler(correlationIdAlreadyPresent); + var correlationId1 = "boom1!"; + var correlationId2 = "boom2!"; + var correlationIdContextMock = fixture.Freeze>(); + correlationIdContextMock.Setup(x => x.GetValue()).Returns(new StringValues(new[] { correlationId1, correlationId2, })); + var correlationIdHandler = fixture.Create(); + + using var invoker = HttpMessageInvokerFactory.Create( + correlationIdHandler, addsCorrelationIdToResponseHandler, shortCircuitingCannedResponseHandler); + using var request = new HttpRequestMessage(); + + using var response = await invoker.SendAsync(request, CancellationToken.None); + + Assert.IsTrue(response.Headers.Contains(RequestHeaders.CorrelationId)); + Assert.AreEqual(new[] { correlationId2, correlationId1 }, response.Headers.GetValues(RequestHeaders.CorrelationId)); + } }