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