From cb4503b1a628d44a0c0d95c8b7dc03e707d300e0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 7 Oct 2026 17:35:58 +0200 Subject: [PATCH] Fix DateTime in Response (#1514) * Fix DateTime in Response * // Set the DateTime from the Response to the current DateTime --------- Co-authored-by: Stef Heyenrath --- .../IRequestMessage.cs | 2 +- .../IResponseMessage.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 14 +++++++++ .../Owin/Mappers/OwinRequestMapper.cs | 4 +-- .../Owin/WireMockMiddleware.cs | 8 ++++- .../Owin/WireMockMiddlewareLogger.cs | 6 ++-- .../ResponseBuilders/Response.cs | 2 +- src/WireMock.Net.Minimal/ResponseMessage.cs | 1 - .../Facts/RunOnDockerFact.cs | 29 +++++++++++++++++++ .../Facts/RunOnDockerPlatformFact.cs | 14 ++++++++- .../Owin/WireMockMiddlewareTests.cs | 11 ++++++- .../Testcontainers/TestcontainersTests.cs | 6 ++-- .../Testcontainers/TestcontainersTestsGrpc.cs | 11 +++---- .../WireMockServer.Proxy.cs | 1 + .../WireMockServer.Proxy2.cs | 4 +-- 15 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs create mode 100644 test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index 02a23b63..c60fbe8f 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -32,7 +32,7 @@ public interface IRequestMessage string? ProxyUrl { get; set; } /// - /// Gets the DateTime. + /// The DateTime when this request was done. /// DateTime DateTime { get; } diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs index 8abf4114..4993a649 100644 --- a/src/WireMock.Net.Abstractions/IResponseMessage.cs +++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs @@ -52,7 +52,7 @@ public interface IResponseMessage object? StatusCode { get; set; } /// - /// Gets the DateTime. + /// The DateTime when this response was sent. /// DateTime DateTime { get; } diff --git a/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs b/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..07ca7b81 --- /dev/null +++ b/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs @@ -0,0 +1,14 @@ +// Copyright © WireMock.Net + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Http; + +namespace WireMock.Extensions; + +internal static class HttpContextExtensions +{ + internal static bool TryGet(this HttpContext httpContext, [NotNullWhen(true)] out T? value) + { + return httpContext.Items.TryGetValue(nameof(T), out value); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs index 0db51eb4..a0d2c3c7 100644 --- a/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs +++ b/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs @@ -12,7 +12,7 @@ namespace WireMock.Owin.Mappers; /// /// OwinRequestMapper /// -internal class OwinRequestMapper : IOwinRequestMapper +internal class OwinRequestMapper(IDateTimeUtils dateTimeUtils) : IOwinRequestMapper { /// public async Task MapAsync(HttpContext context, IWireMockMiddlewareOptions options) @@ -72,7 +72,7 @@ internal class OwinRequestMapper : IOwinRequestMapper await request.HttpContext.Connection.GetClientCertificateAsync() ) { - DateTime = DateTime.UtcNow + DateTime = dateTimeUtils.UtcNow }; } diff --git a/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs index bd193e11..89528b3f 100644 --- a/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs @@ -171,6 +171,12 @@ internal class WireMockMiddleware( } finally { + // Set the DateTime from the Response to the current DateTime + if (response is ResponseMessage responseMessage) + { + responseMessage.DateTime = dateTimeUtils.UtcNow; + } + logger.LogRequestAndResponse(logRequest, request, response, result.Match, result.Partial, activity); try @@ -255,4 +261,4 @@ internal class WireMockMiddleware( scenario.Finished = mapping.NextState == null; }); } -} +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs index 2f35b2ce..d44eaf7d 100644 --- a/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs +++ b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs @@ -73,20 +73,20 @@ internal class WireMockMiddlewareLogger( var logEntries = options.LogEntries.ToList(); foreach (var logEntry in logEntries - .OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime) + .OrderBy(le => le.RequestMessage?.DateTime) .Take(logEntries.Count - options.MaxRequestLogCount.Value)) { TryRemoveLogEntry(logEntry); } } - // In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date. + // In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the RequestMessage date. if (options.RequestLogExpirationDuration is > 0) { var logEntries = options.LogEntries.ToList(); var checkTime = dateTimeUtils.UtcNow.AddHours(-options.RequestLogExpirationDuration.Value); - foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime)) + foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime)) { TryRemoveLogEntry(logEntry); } diff --git a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs index 60151c0e..b14631f5 100644 --- a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs @@ -87,7 +87,7 @@ public partial class Response : IResponseBuilder { var message = responseMessage ?? new ResponseMessage { - DateTime = DateTime.UtcNow // We set it here to the current time. + DateTime = DateTime.UtcNow // Initially, this is set to the current time, but will be updated to the real time once the response has been sent. }; return new Response(message); diff --git a/src/WireMock.Net.Minimal/ResponseMessage.cs b/src/WireMock.Net.Minimal/ResponseMessage.cs index 1b5d3f9c..70ae16c1 100644 --- a/src/WireMock.Net.Minimal/ResponseMessage.cs +++ b/src/WireMock.Net.Minimal/ResponseMessage.cs @@ -2,7 +2,6 @@ // This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System.Linq; using Stef.Validation; using WireMock.ResponseBuilders; using WireMock.Types; diff --git a/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs b/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs new file mode 100644 index 00000000..dd179b1c --- /dev/null +++ b/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs @@ -0,0 +1,29 @@ +// Copyright © WireMock.Net +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using WireMock.Net.Testcontainers.Utils; + +namespace WireMock.Net.Tests.Facts; + +[ExcludeFromCodeCoverage] +public sealed class RunOnDockerFact : FactAttribute +{ + public RunOnDockerFact( + [CallerFilePath] string? sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = -1) : base(sourceFilePath, sourceLineNumber) + { + + OSPlatform currentPlatform; + try + { + currentPlatform = TestcontainersUtils.GetImageOSAsync.Value.Result; + } + catch + { + Skip = $"Only run test when Docker is installed."; + } + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs b/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs index d12cdb4f..ee6980f1 100644 --- a/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs +++ b/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs @@ -15,7 +15,19 @@ public sealed class RunOnDockerPlatformFact : FactAttribute [CallerFilePath] string? sourceFilePath = null, [CallerLineNumber] int sourceLineNumber = -1) : base(sourceFilePath, sourceLineNumber) { - if (TestcontainersUtils.GetImageOSAsync.Value.Result != OSPlatform.Create(platform)) + + OSPlatform currentPlatform; + try + { + currentPlatform = TestcontainersUtils.GetImageOSAsync.Value.Result; + } + catch + { + Skip = $"Only run test when Docker is installed."; + return; + } + + if (currentPlatform != OSPlatform.Create(platform)) { Skip = $"Only run test when Docker OS Platform {platform} is used."; } diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index fd149196..d25de1a0 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -106,6 +106,15 @@ public class WireMockMiddlewareTests [Fact] public async Task WireMockMiddleware_Invoke_NoMatch() { + // Arrange + var inititalDateTime = new DateTime(2027, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var responseDateTime = new DateTime(2027, 1, 1, 0, 0, 42, DateTimeKind.Utc); + + _dateTimeUtilsMock.Reset(); + _dateTimeUtilsMock.SetupSequence(d => d.UtcNow) + .Returns(inititalDateTime) + .Returns(responseDateTime); + // Act await _sut.Invoke(_contextMock.Object); @@ -115,7 +124,7 @@ public class WireMockMiddlewareTests Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found" && - r.DateTime == UtcNow; + r.DateTime == responseDateTime; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs index 2de6a1a1..ace85d23 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs @@ -21,7 +21,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) TimestampFormat = "yyy-MM-dd HH:mm:ss.fff" }); - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync() { // Act @@ -57,7 +57,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_WithImage_And_StartAsync_and_StopAsync() { // Arrange @@ -83,7 +83,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_WithImageAsText_And_StartAsync_and_StopAsync() { // Arrange diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs index e01655c4..05a60757 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs @@ -10,6 +10,7 @@ using Meziantou.Extensions.Logging.Xunit.v3; using Microsoft.Extensions.Logging; using WireMock.Constants; using WireMock.Net.Testcontainers; +using WireMock.Net.Tests.Facts; using WireMock.Util; namespace WireMock.Net.Tests.Testcontainers; @@ -25,7 +26,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) TimestampFormat = "yyy-MM-dd HH:mm:ss.fff" }); - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1() { // Arrange @@ -82,7 +83,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) } } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2() { // Arrange @@ -136,7 +137,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) } } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionFromJson_UsingGrpcGeneratedClient() { var wireMockContainer = await Given_WireMockContainerIsStartedForHttpAndGrpcAsync(); @@ -150,7 +151,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient() { var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync(); @@ -164,7 +165,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient_AndWithWatchStaticMappings() { var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync(); diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index df28f611..245940fe 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -18,6 +18,7 @@ using WireMock.Util; namespace WireMock.Net.Tests; +[Collection(nameof(WireMockServerProxyTests))] public class WireMockServerProxyTests { private readonly CancellationToken _ct = TestContext.Current.CancellationToken; diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs index da27e226..ba77567a 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs @@ -45,6 +45,4 @@ public class WireMockServerProxy2Tests server.Dispose(); serverAsProxy.Dispose(); } -} - - +} \ No newline at end of file