From e2f3ffd33a6eb48b7fe13d4a93cec43dcb98962e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 9 Dec 2022 14:18:50 +0100 Subject: [PATCH] Add UpdatedAt property to Mapping (#859) * Add UpdatedAt property to Mapping * . --- .../Program.cs | 156 ++++--- .../Admin/Mappings/MappingModel.cs | 5 + src/WireMock.Net/IMapping.cs | 5 + src/WireMock.Net/Mapping.cs | 6 + src/WireMock.Net/Owin/MappingMatcher.cs | 11 +- src/WireMock.Net/Proxy/ProxyHelper.cs | 2 +- .../Serialization/MappingConverter.cs | 1 + .../Serialization/ProxyMappingConverter.cs | 7 +- .../Server/RespondWithAProvider.cs | 40 +- src/WireMock.Net/Server/WireMockServer.cs | 3 +- src/WireMock.Net/Util/DateTimeUtils.cs | 13 + .../Owin/WireMockMiddlewareTests.cs | 386 +++++++++--------- .../Serialization/MappingConverterTests.cs | 19 +- .../ProxyMappingConverterTests.cs | 5 +- .../Serialization/files/proxy.json | 1 + 15 files changed, 383 insertions(+), 277 deletions(-) create mode 100644 src/WireMock.Net/Util/DateTimeUtils.cs diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs index 62ae3400..ff710595 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using log4net; using log4net.Config; using log4net.Repository; @@ -10,71 +11,112 @@ using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; using WireMock.Settings; -using WireMock.Util; -namespace WireMock.Net.StandAlone.NETCoreApp +namespace WireMock.Net.StandAlone.NETCoreApp; + +static class Program { - static class Program + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + // private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + + private static int sleepTime = 30000; + private static WireMockServer _server; + + static async Task Main(string[] args) { - private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); - // private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + await TestAsync().ConfigureAwait(false); + return; - private static int sleepTime = 30000; - private static WireMockServer _server; + XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); - static void Main(string[] args) + if (!WireMockServerSettingsParser.TryParseArguments(args, out var settings, new WireMockLog4NetLogger())) { - XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); - - if (!WireMockServerSettingsParser.TryParseArguments(args, out var settings, new WireMockLog4NetLogger())) - { - return; - } - - settings.Logger.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); - - _server = WireMockServer.Start(settings); - - //_server.Given(Request.Create().WithPath("/api/sap") - // .UsingPost() - // .WithBody((IBodyData xmlData) => - // { - // //xmlData is always null - // return true; - // })) - // .RespondWith(Response.Create().WithStatusCode(System.Net.HttpStatusCode.OK)); - - //_server - // .Given(Request.Create() - // .UsingAnyMethod()) - // .RespondWith(Response.Create() - // .WithTransformer() - // .WithBody("{{Random Type=\"Integer\" Min=100 Max=999999}} {{DateTime.Now}} {{DateTime.Now \"yyyy-MMM\"}} {{String.Format (DateTime.Now) \"MMM-dd\"}}")); - - Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down"); - - Console.CancelKeyPress += (s, e) => - { - Stop("CancelKeyPress"); - }; - - System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx => - { - Stop("AssemblyLoadContext.Default.Unloading"); - }; - - while (true) - { - Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server running : {_server.IsStarted}"); - Thread.Sleep(sleepTime); - } + return; } - private static void Stop(string why) + settings.Logger.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); + + _server = WireMockServer.Start(settings); + + //_server.Given(Request.Create().WithPath("/api/sap") + // .UsingPost() + // .WithBody((IBodyData xmlData) => + // { + // //xmlData is always null + // return true; + // })) + // .RespondWith(Response.Create().WithStatusCode(System.Net.HttpStatusCode.OK)); + + //_server + // .Given(Request.Create() + // .UsingAnyMethod()) + // .RespondWith(Response.Create() + // .WithTransformer() + // .WithBody("{{Random Type=\"Integer\" Min=100 Max=999999}} {{DateTime.Now}} {{DateTime.Now \"yyyy-MMM\"}} {{String.Format (DateTime.Now) \"MMM-dd\"}}")); + + Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down"); + + Console.CancelKeyPress += (s, e) => { - Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopping because '{why}'"); - _server.Stop(); - Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopped"); + Stop("CancelKeyPress"); + }; + + System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx => + { + Stop("AssemblyLoadContext.Default.Unloading"); + }; + + while (true) + { + Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server running : {_server.IsStarted}"); + Thread.Sleep(sleepTime); } } -} + + private static void Stop(string why) + { + Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopping because '{why}'"); + _server.Stop(); + Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopped"); + } + + private static async Task TestAsync() + { + for (var i = 0; i < 20; i++) + { + var server = WireMockServer.Start(); + + server + .Given( + Request.Create().WithPath("/some/thing").UsingGet() + ) + .RespondWith( + Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "text/plain") + .WithBody("Hello world! : " + i) + ); + + server + .Given( + Request.Create().WithPath("/some/thing").UsingGet() + ) + .RespondWith( + Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "text/plain") + .WithBody("Hello world duplicate! : " + i) + ); + + var client = server.CreateClient(); + + var response = await client.GetAsync($"{server.Url}/some/thing").ConfigureAwait(false); + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Console.WriteLine($"counter {i} value:{content}"); + + server.Reset(); + server.Dispose(); + server.Stop(); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs index aaddafc8..6fe7dc9c 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs @@ -14,6 +14,11 @@ public class MappingModel /// public Guid? Guid { get; set; } + /// + /// The datetime when this mapping was created or updated. + /// + public DateTime? UpdatedAt { get; set; } + /// /// Gets or sets the TimeSettings when which this mapping should be used. /// diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index 3af0f136..c657b6bc 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -17,6 +17,11 @@ public interface IMapping /// Guid Guid { get; } + /// + /// The datetime when this mapping was created or updated. + /// + public DateTime? UpdatedAt { get; set; } + /// /// Gets the TimeSettings (Start, End and TTL). /// diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index 1e978ef5..8b875df7 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -15,6 +15,9 @@ public class Mapping : IMapping /// public Guid Guid { get; } + /// + public DateTime? UpdatedAt { get; set; } + /// public string? Title { get; } @@ -73,6 +76,7 @@ public class Mapping : IMapping /// Initializes a new instance of the class. /// /// The unique identifier. + /// The datetime when this mapping was created. /// The unique title (can be null). /// The description (can be null). /// The full file path from this mapping title (can be null). @@ -89,6 +93,7 @@ public class Mapping : IMapping /// The TimeSettings. [Optional] public Mapping( Guid guid, + DateTime updatedAt, string? title, string? description, string? path, @@ -105,6 +110,7 @@ public class Mapping : IMapping ITimeSettings? timeSettings) { Guid = guid; + UpdatedAt = updatedAt; Title = title; Description = description; Path = path; diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs index 704af303..6360e6a6 100644 --- a/src/WireMock.Net/Owin/MappingMatcher.cs +++ b/src/WireMock.Net/Owin/MappingMatcher.cs @@ -12,13 +12,13 @@ internal class MappingMatcher : IMappingMatcher public MappingMatcher(IWireMockMiddlewareOptions options) { - Guard.NotNull(options, nameof(options)); - - _options = options; + _options = Guard.NotNull(options); } public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request) { + Guard.NotNull(request); + var possibleMappings = new List(); foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid())) @@ -41,8 +41,7 @@ internal class MappingMatcher : IMappingMatcher var partialMappings = possibleMappings .Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) - .OrderBy(m => m.RequestMatchResult) - .ThenBy(m => m.Mapping.Priority) + .OrderBy(m => m.RequestMatchResult).ThenBy(m => m.Mapping.Priority).ThenByDescending(m => m.Mapping.UpdatedAt) .ToList(); var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0); @@ -53,7 +52,7 @@ internal class MappingMatcher : IMappingMatcher var match = possibleMappings .Where(m => m.RequestMatchResult.IsPerfectMatch) - .OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult) + .OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult).ThenByDescending(m => m.Mapping.UpdatedAt) .FirstOrDefault(); return (match, partialMatch); diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index ed7270e5..14f66b16 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -17,7 +17,7 @@ internal class ProxyHelper public ProxyHelper(WireMockServerSettings settings) { _settings = Guard.NotNull(settings); - _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils()); + _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils(), new DateTimeUtils()); } public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index d2ff126c..97900f61 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -40,6 +40,7 @@ internal class MappingConverter var mappingModel = new MappingModel { Guid = mapping.Guid, + UpdatedAt = mapping.UpdatedAt, TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings), Title = mapping.Title, Description = mapping.Description, diff --git a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs index 2ee39de5..d84546aa 100644 --- a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs +++ b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs @@ -1,7 +1,7 @@ -using Stef.Validation; using System; using System.Collections.Generic; using System.Linq; +using Stef.Validation; using WireMock.Constants; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -17,11 +17,13 @@ internal class ProxyMappingConverter { private readonly WireMockServerSettings _settings; private readonly IGuidUtils _guidUtils; + private readonly IDateTimeUtils _dateTimeUtils; - public ProxyMappingConverter(WireMockServerSettings settings, IGuidUtils guidUtils) + public ProxyMappingConverter(WireMockServerSettings settings, IGuidUtils guidUtils, IDateTimeUtils dateTimeUtils) { _settings = Guard.NotNull(settings); _guidUtils = Guard.NotNull(guidUtils); + _dateTimeUtils = Guard.NotNull(dateTimeUtils); } public IMapping ToMapping(IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) @@ -162,6 +164,7 @@ internal class ProxyMappingConverter return new Mapping ( guid: _guidUtils.NewGuid(), + updatedAt: _dateTimeUtils.UtcNow, title: title, description: description, path: null, diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index 25d12286..647f2891 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -29,10 +29,12 @@ internal class RespondWithAProvider : IRespondWithAProvider private readonly IRequestMatcher _requestMatcher; private readonly WireMockServerSettings _settings; private readonly bool _saveToFile; + private readonly IGuidUtils _guidUtils = new GuidUtils(); + private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); private bool _useWebhookFireAndForget; - public Guid Guid { get; private set; } = Guid.NewGuid(); + public Guid Guid { get; private set; } public IWebhook[]? Webhooks { get; private set; } @@ -45,12 +47,19 @@ internal class RespondWithAProvider : IRespondWithAProvider /// The request matcher. /// The WireMockServerSettings. /// Optional boolean to indicate if this mapping should be saved as static mapping file. - public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, WireMockServerSettings settings, bool saveToFile = false) + public RespondWithAProvider( + RegistrationCallback registrationCallback, + IRequestMatcher requestMatcher, + WireMockServerSettings settings, + bool saveToFile = false + ) { - _registrationCallback = registrationCallback; - _requestMatcher = requestMatcher; - _settings = settings; - _saveToFile = saveToFile; + _registrationCallback = Guard.NotNull(registrationCallback); + _requestMatcher = Guard.NotNull(requestMatcher); + _settings = Guard.NotNull(settings); + _saveToFile = Guard.NotNull(saveToFile); + + Guid = _guidUtils.NewGuid(); } /// @@ -59,7 +68,24 @@ internal class RespondWithAProvider : IRespondWithAProvider /// The provider. public void RespondWith(IResponseProvider provider) { - _registrationCallback(new Mapping(Guid, _title, _description, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, _useWebhookFireAndForget, TimeSettings), _saveToFile); + var mapping = new Mapping( + Guid, + _dateTimeUtils.UtcNow, + _title, + _description, + _path, + _settings, + _requestMatcher, + provider, + _priority, + _scenario, + _executionConditionState, + _nextState, + _timesInSameState, + Webhooks, + _useWebhookFireAndForget, + TimeSettings); + _registrationCallback(mapping, _saveToFile); } /// diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 52e80ef0..a214ac34 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -524,9 +524,10 @@ public partial class WireMockServer : IWireMockServer private void RegisterMapping(IMapping mapping, bool saveToFile) { - // Check a mapping exists with the same Guid, if so, replace it. + // Check a mapping exists with the same Guid. If so, update the datetime and replace it. if (_options.Mappings.ContainsKey(mapping.Guid)) { + mapping.UpdatedAt = DateTime.UtcNow; _options.Mappings[mapping.Guid] = mapping; } else diff --git a/src/WireMock.Net/Util/DateTimeUtils.cs b/src/WireMock.Net/Util/DateTimeUtils.cs new file mode 100644 index 00000000..8170aea7 --- /dev/null +++ b/src/WireMock.Net/Util/DateTimeUtils.cs @@ -0,0 +1,13 @@ +using System; + +namespace WireMock.Util; + +internal interface IDateTimeUtils +{ + DateTime UtcNow { get; } +} + +internal class DateTimeUtils : IDateTimeUtils +{ + public DateTime UtcNow => DateTime.UtcNow; +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index c070fc54..1d13da61 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -30,222 +30,222 @@ using IRequest = Microsoft.AspNetCore.Http.HttpRequest; using IResponse = Microsoft.AspNetCore.Http.HttpResponse; #endif -namespace WireMock.Net.Tests.Owin +namespace WireMock.Net.Tests.Owin; + +public class WireMockMiddlewareTests { - public class WireMockMiddlewareTests + private readonly DateTime _updatedAt = new(2022, 12, 4); + private readonly ConcurrentDictionary _mappings = new(); + + private readonly Mock _optionsMock; + private readonly Mock _requestMapperMock; + private readonly Mock _responseMapperMock; + private readonly Mock _matcherMock; + private readonly Mock _mappingMock; + private readonly Mock _contextMock; + + private readonly WireMockMiddleware _sut; + + public WireMockMiddlewareTests() { - private readonly WireMockMiddleware _sut; + _optionsMock = new Mock(); + _optionsMock.SetupAllProperties(); + _optionsMock.Setup(o => o.Mappings).Returns(_mappings); + _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); + _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); + _optionsMock.Setup(o => o.Logger.Warn(It.IsAny(), It.IsAny())); + _optionsMock.Setup(o => o.Logger.Error(It.IsAny(), It.IsAny())); + _optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny(), It.IsAny())); - private readonly Mock _optionsMock; - private readonly Mock _requestMapperMock; - private readonly Mock _responseMapperMock; - private readonly Mock _matcherMock; - private readonly Mock _mappingMock; - private readonly Mock _contextMock; + _requestMapperMock = new Mock(); + _requestMapperMock.SetupAllProperties(); + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); - private readonly ConcurrentDictionary _mappings = new ConcurrentDictionary(); + _responseMapperMock = new Mock(); + _responseMapperMock.SetupAllProperties(); + _responseMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - public WireMockMiddlewareTests() + _matcherMock = new Mock(); + _matcherMock.SetupAllProperties(); + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((new MappingMatcherResult(), new MappingMatcherResult())); + + _contextMock = new Mock(); + + _mappingMock = new Mock(); + + _sut = new WireMockMiddleware(null, _optionsMock.Object, _requestMapperMock.Object, _responseMapperMock.Object, _matcherMock.Object); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_NoMatch() + { + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + + // Assert and Verify + _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); + + Expression> match = r => (int)r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; + _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); + _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); + + _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); + _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); + + var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); + + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + + // Assert and Verify + _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); + + Expression> match = r => (int)r.StatusCode == 401; + _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary { { "h", new[] { "x" } } }); + _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); + + _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); + _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); + + var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); + + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + + // Assert and Verify + _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); + + Expression> match = r => (int)r.StatusCode == 401; + _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_RequestLogExpirationDurationIsDefined() + { + // Assign + _optionsMock.SetupGet(o => o.RequestLogExpirationDuration).Returns(1); + + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_True() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); + _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); + + _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); + + var fileSystemHandlerMock = new Mock(); + fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); + + var logger = new Mock(); + + var proxyAndRecordSettings = new ProxyAndRecordSettings { - _optionsMock = new Mock(); - _optionsMock.SetupAllProperties(); - _optionsMock.Setup(o => o.Mappings).Returns(_mappings); - _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); - _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); - _optionsMock.Setup(o => o.Logger.Warn(It.IsAny(), It.IsAny())); - _optionsMock.Setup(o => o.Logger.Error(It.IsAny(), It.IsAny())); - _optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny(), It.IsAny())); + SaveMapping = true, + SaveMappingToFile = true + }; - _requestMapperMock = new Mock(); - _requestMapperMock.SetupAllProperties(); - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); - - _responseMapperMock = new Mock(); - _responseMapperMock.SetupAllProperties(); - _responseMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - - _matcherMock = new Mock(); - _matcherMock.SetupAllProperties(); - _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((new MappingMatcherResult(), new MappingMatcherResult())); - - _contextMock = new Mock(); - - _mappingMock = new Mock(); - - _sut = new WireMockMiddleware(null, _optionsMock.Object, _requestMapperMock.Object, _responseMapperMock.Object, _matcherMock.Object); - } - - [Fact] - public async Task WireMockMiddleware_Invoke_NoMatch() + var settings = new WireMockServerSettings { - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + FileSystemHandler = fileSystemHandlerMock.Object, + Logger = logger.Object + }; - // Assert and Verify - _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); + var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); - Expression> match = r => (int)r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; - _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); - } + _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); + _mappingMock.SetupGet(m => m.Settings).Returns(settings); - [Fact] - public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() + var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null); + _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); + + var requestBuilder = Request.Create().UsingAnyMethod(); + _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); + + var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); + + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + + // Assert and Verify + fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); + + _mappings.Count.Should().Be(1); + } + + [Fact] + public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_False_But_WireMockServerSettings_SaveMapping_Is_True() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); + _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); + + _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); + + var fileSystemHandlerMock = new Mock(); + fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); + + var logger = new Mock(); + + var proxyAndRecordSettings = new ProxyAndRecordSettings { - // Assign - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); - _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); + SaveMapping = false, + SaveMappingToFile = false + }; - _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); - _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); - - var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; - _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); - - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); - - // Assert and Verify - _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - - Expression> match = r => (int)r.StatusCode == 401; - _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); - } - - [Fact] - public async Task WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401() + var settings = new WireMockServerSettings { - // Assign - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary { { "h", new[] { "x" } } }); - _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); - - _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); - _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); - - var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; - _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); - - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); - - // Assert and Verify - _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - - Expression> match = r => (int)r.StatusCode == 401; - _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); - } - - [Fact] - public async Task WireMockMiddleware_Invoke_RequestLogExpirationDurationIsDefined() - { - // Assign - _optionsMock.SetupGet(o => o.RequestLogExpirationDuration).Returns(1); - - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); - } - - [Fact] - public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_True() - { - // Assign - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); - _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); - - _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); - - var fileSystemHandlerMock = new Mock(); - fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); - - var logger = new Mock(); - - var proxyAndRecordSettings = new ProxyAndRecordSettings + FileSystemHandler = fileSystemHandlerMock.Object, + Logger = logger.Object, + ProxyAndRecordSettings = new ProxyAndRecordSettings { SaveMapping = true, SaveMappingToFile = true - }; + } + }; - var settings = new WireMockServerSettings - { - FileSystemHandler = fileSystemHandlerMock.Object, - Logger = logger.Object - }; + var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); - var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); + _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); + _mappingMock.SetupGet(m => m.Settings).Returns(settings); - _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); - _mappingMock.SetupGet(m => m.Settings).Returns(settings); + var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null); + _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); - var newMappingFromProxy = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null); - _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); + var requestBuilder = Request.Create().UsingAnyMethod(); + _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); - var requestBuilder = Request.Create().UsingAnyMethod(); - _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); + var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); - var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; - _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + // Assert and Verify + fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); - // Assert and Verify - fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); - - _mappings.Count.Should().Be(1); - } - - [Fact] - public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_False_But_WireMockServerSettings_SaveMapping_Is_True() - { - // Assign - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); - _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); - - _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); - - var fileSystemHandlerMock = new Mock(); - fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); - - var logger = new Mock(); - - var proxyAndRecordSettings = new ProxyAndRecordSettings - { - SaveMapping = false, - SaveMappingToFile = false - }; - - var settings = new WireMockServerSettings - { - FileSystemHandler = fileSystemHandlerMock.Object, - Logger = logger.Object, - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - SaveMapping = true, - SaveMappingToFile = true - } - }; - - var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); - - _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); - _mappingMock.SetupGet(m => m.Settings).Returns(settings); - - var newMappingFromProxy = new Mapping(Guid.NewGuid(), "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null); - _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); - - var requestBuilder = Request.Create().UsingAnyMethod(); - _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); - - var result = new MappingMatcherResult { Mapping = _mappingMock.Object }; - _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); - - // Act - await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); - - // Assert and Verify - fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); - - _mappings.Count.Should().Be(1); - } + _mappings.Count.Should().Be(1); } -} \ No newline at end of file +} diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs index b54a513a..a765f7ac 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs @@ -15,6 +15,7 @@ namespace WireMock.Net.Tests.Serialization; public class MappingConverterTests { + private readonly DateTime _updatedAt = new(2022, 12, 4); private readonly WireMockServerSettings _settings = new(); private readonly MappingConverter _sut; @@ -52,7 +53,7 @@ public class MappingConverterTests } } }; - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -122,7 +123,7 @@ public class MappingConverterTests } } }; - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, true, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, true, null); // Act var model = _sut.ToMappingModel(mapping); @@ -157,7 +158,7 @@ public class MappingConverterTests var description = "my-description"; var request = Request.Create(); var response = Response.Create(); - var mapping = new Mapping(Guid.NewGuid(), title, description, null, _settings, request, response, 0, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, title, description, null, _settings, request, response, 0, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -174,7 +175,7 @@ public class MappingConverterTests // Assign var request = Request.Create(); var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer(); - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -200,7 +201,7 @@ public class MappingConverterTests End = end, TTL = ttl }; - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, timeSettings); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, timeSettings); // Act var model = _sut.ToMappingModel(mapping); @@ -228,7 +229,7 @@ public class MappingConverterTests { var request = Request.Create(); var response = Response.Create().WithDelay(test.Delay); - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -246,7 +247,7 @@ public class MappingConverterTests var delay = 1000; var request = Request.Create(); var response = Response.Create().WithDelay(delay); - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -263,7 +264,7 @@ public class MappingConverterTests int minimumDelay = 1000; var request = Request.Create(); var response = Response.Create().WithRandomDelay(minimumDelay); - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); @@ -283,7 +284,7 @@ public class MappingConverterTests int maximumDelay = 2000; var request = Request.Create(); var response = Response.Create().WithRandomDelay(minimumDelay, maximumDelay); - var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); + var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null); // Act var model = _sut.ToMappingModel(mapping); diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs index 228d63b3..6f85a98b 100644 --- a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs @@ -25,9 +25,12 @@ public class ProxyMappingConverterTests var guidUtilsMock = new Mock(); guidUtilsMock.Setup(g => g.NewGuid()).Returns(Guid.Parse("ff55ac0a-fea9-4d7b-be74-5e483a2c1305")); + var dateTimeUtilsMock = new Mock(); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(new DateTime(2022, 12, 4)); + _mappingConverter = new MappingConverter(new MatcherMapper(_settings)); - _sut = new ProxyMappingConverter(_settings, guidUtilsMock.Object); + _sut = new ProxyMappingConverter(_settings, guidUtilsMock.Object, dateTimeUtilsMock.Object); } [Fact] diff --git a/test/WireMock.Net.Tests/Serialization/files/proxy.json b/test/WireMock.Net.Tests/Serialization/files/proxy.json index ec51e133..e7703f65 100644 --- a/test/WireMock.Net.Tests/Serialization/files/proxy.json +++ b/test/WireMock.Net.Tests/Serialization/files/proxy.json @@ -1,5 +1,6 @@ { "Guid": "ff55ac0a-fea9-4d7b-be74-5e483a2c1305", + "UpdatedAt": "2022-12-04T00:00:00", "Title": "my title", "Description": "my description", "Priority": -2000000,