diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 942177dc8..fa88484ca 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -267,7 +267,7 @@ private static void RunOnLocal() } } - public static void Run() + public static async Task RunAsync() { //RunSse(); //RunOnLocal(); @@ -290,25 +290,56 @@ public static void Run() var server = WireMockServer.Start(); + //server + // .Given(Request.Create() + // .WithPath("todos") + // .UsingGet() + // ) + // .RespondWith(Response.Create() + // .WithBodyAsJson(todos.Values) + // ); + + //server + // .Given(Request.Create() + // .UsingGet() + // .WithPath("todos") + // .WithParam("id") + // ) + // .RespondWith(Response.Create() + // .WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())]) + // ); + + var pX = 0.80; server - .Given(Request.Create() - .WithPath("todos") - .UsingGet() - ) - .RespondWith(Response.Create() - .WithBodyAsJson(todos.Values) - ); + .Given(Request.Create().UsingGet().WithPath("/p")) + .WithProbability(pX) + .RespondWith(Response.Create().WithStatusCode(200).WithBody("X")); server - .Given(Request.Create() - .UsingGet() - .WithPath("todos") - .WithParam("id") - ) - .RespondWith(Response.Create() - .WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())]) - ); + .Given(Request.Create().UsingGet().WithPath("/p")) + .RespondWith(Response.Create().WithStatusCode(200).WithBody("default")); + // Act + var requestUri = new Uri($"http://localhost:{server.Port}/p"); + var c = server.CreateClient(); + var xCount = 0; + var defaultCount = 0; + var tot = 1000; + for (var i = 0; i < tot; i++) + { + var response = await c.GetAsync(requestUri); + var value = await response.Content.ReadAsStringAsync(); + if (value == "X") + { + xCount++; + } + else if (value == "default") + { + defaultCount++; + } + } + System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2:0.00} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot); + return; using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings { HostingScheme = HostingScheme.HttpAndHttps, diff --git a/examples/WireMock.Net.Console.NET8/Program.cs b/examples/WireMock.Net.Console.NET8/Program.cs index 2a8ae4c44..9ca8b2004 100644 --- a/examples/WireMock.Net.Console.NET8/Program.cs +++ b/examples/WireMock.Net.Console.NET8/Program.cs @@ -2,6 +2,7 @@ using System.IO; using System.Reflection; +using System.Threading.Tasks; using log4net; using log4net.Config; using log4net.Repository; @@ -14,10 +15,10 @@ static class Program private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); - static void Main(params string[] args) + static async Task Main(params string[] args) { XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); - MainApp.Run(); + await MainApp.RunAsync(); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs index 878618136..0180e8650 100644 --- a/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs +++ b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs @@ -9,16 +9,10 @@ namespace WireMock.Owin; -internal class MappingMatcher : IMappingMatcher +internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1) : IMappingMatcher { - private readonly IWireMockMiddlewareOptions _options; - private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1; - - public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1) - { - _options = Guard.NotNull(options); - _randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1); - } + private readonly IWireMockMiddlewareOptions _options = Guard.NotNull(options); + private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1); public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request) { @@ -28,7 +22,7 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe var mappings = _options.Mappings.Values .Where(m => m.TimeSettings.IsValid()) - .Where(m => m.Probability is null || m.Probability <= _randomizerDoubleBetween0And1.Generate()) + .Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability) .ToArray(); foreach (var mapping in mappings) @@ -41,10 +35,10 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails .Where(md => md.Exception != null) - .Select(md => md.Exception) + .Select(md => md.Exception!) .ToArray(); - if (!exceptions.Any()) + if (exceptions.Length == 0) { possibleMappings.Add(mappingMatcherResult); } @@ -52,7 +46,7 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe { foreach (var ex in exceptions) { - LogException(mapping, ex!); + LogException(mapping, ex); } } } @@ -62,14 +56,16 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe } } - var partialMappings = possibleMappings + var partialMatches = possibleMappings .Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) .OrderBy(m => m.RequestMatchResult) .ThenBy(m => m.RequestMatchResult.TotalNumber) .ThenBy(m => m.Mapping.Priority) + .ThenByDescending(m => m.Mapping.Probability) .ThenByDescending(m => m.Mapping.UpdatedAt) - .ToList(); - var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0); + .Where(pm => pm.RequestMatchResult.AverageTotalScore > 0.0) + .ToArray(); + var partialMatch = partialMatches.FirstOrDefault(); if (_options.AllowPartialMapping == true) { @@ -78,7 +74,11 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe var match = possibleMappings .Where(m => m.RequestMatchResult.IsPerfectMatch) - .OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult).ThenByDescending(m => m.Mapping.UpdatedAt) + .OrderBy(m => m.Mapping.Priority) + .ThenBy(m => m.RequestMatchResult) + .ThenBy(m => m.RequestMatchResult.TotalNumber) + .ThenByDescending(m => m.Mapping.Probability) + .ThenByDescending(m => m.Mapping.UpdatedAt) .FirstOrDefault(); return (match, partialMatch); diff --git a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs index 48f325e1b..e81bce1e4 100644 --- a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs +++ b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs @@ -9,7 +9,6 @@ using WireMock.Models; using WireMock.Owin; using WireMock.Services; -using WireMock.Util; using Xunit; namespace WireMock.Net.Tests.Owin; @@ -26,7 +25,7 @@ public MappingMatcherTests() _optionsMock = new Mock(); _optionsMock.SetupAllProperties(); _optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary()); - _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); + _optionsMock.Setup(o => o.LogEntries).Returns([]); _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); var loggerMock = new Mock(); @@ -35,7 +34,7 @@ public MappingMatcherTests() _optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object); _randomizerDoubleBetween0And1Mock = new Mock(); - _randomizerDoubleBetween0And1Mock.Setup(r => r.Generate()).Returns(0.0); + _randomizerDoubleBetween0And1Mock.Setup(r => r.Generate()).Returns(0.5); _sut = new MappingMatcher(_optionsMock.Object, _randomizerDoubleBetween0And1Mock.Object); } @@ -84,8 +83,8 @@ public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldRe var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); var mappings = InitMappings ( - (guid1, new[] { 0.1 }, null), - (guid2, new[] { 1.0 }, null) + (guid1, [0.1], null), + (guid2, [1.0], null) ); _optionsMock.Setup(o => o.Mappings).Returns(mappings); @@ -112,8 +111,8 @@ public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExa var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); var mappings = InitMappings ( - (guid1, new[] { 0.1 }, null), - (guid2, new[] { 0.9 }, null) + (guid1, [0.1], null), + (guid2, [0.9], null) ); _optionsMock.Setup(o => o.Mappings).Returns(mappings); @@ -139,8 +138,8 @@ public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldRet _optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true); var mappings = InitMappings( - (guid1, new[] { 0.1 }, null), - (guid2, new[] { 0.9 }, null) + (guid1, [0.1], null), + (guid2, [0.9], null) ); _optionsMock.Setup(o => o.Mappings).Returns(mappings); @@ -166,8 +165,8 @@ public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_With var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); var mappings = InitMappings( - (guid1, new[] { 1.0 }, null), - (guid2, new[] { 1.0, 1.0 }, null) + (guid1, [1.0], null), + (guid2, [1.0, 1.0], null) ); _optionsMock.Setup(o => o.Mappings).Returns(mappings); @@ -187,15 +186,15 @@ public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_With } [Fact] - public void MappingMatcher_FindBestMatch_WhenProbabilityFailsFirst_ShouldReturnSecondMatch() + public void MappingMatcher_FindBestMatch_WhenProbabilityDoesNotMatch_ShouldReturnNormalMatch() { // Assign - var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); + var withProbability = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var noProbability = Guid.Parse("00000000-0000-0000-0000-000000000002"); var mappings = InitMappings ( - (guid1, new[] { 1.0 }, 1.0), - (guid2, new[] { 1.0 }, null) + (withProbability, [1.0], 0.4), + (noProbability, [1.0], null) ); _optionsMock.Setup(o => o.Mappings).Returns(mappings); @@ -206,8 +205,30 @@ public void MappingMatcher_FindBestMatch_WhenProbabilityFailsFirst_ShouldReturnS // Assert result.Match.Should().NotBeNull(); - result.Match!.Mapping.Guid.Should().Be(guid2); - result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0); + result.Match!.Mapping.Guid.Should().Be(noProbability); + } + + [Fact] + public void MappingMatcher_FindBestMatch_WhenProbabilityDoesMatch_ShouldReturnProbabilityMatch() + { + // Assign + var withProbability = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var noProbability = Guid.Parse("00000000-0000-0000-0000-000000000002"); + var mappings = InitMappings + ( + (withProbability, [1.0], 0.6), + (noProbability, [1.0], null) + ); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert + result.Match.Should().NotBeNull(); + result.Match!.Mapping.Guid.Should().Be(withProbability); } private static ConcurrentDictionary InitMappings(params (Guid guid, double[] scores, double? probability)[] matches) diff --git a/test/WireMock.Net.Tests/WireMockServerTests.WithProbability.cs b/test/WireMock.Net.Tests/WireMockServerTests.WithProbability.cs index 458d71493..e98eb0c5c 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.WithProbability.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.WithProbability.cs @@ -32,7 +32,7 @@ public async Task WireMockServer_WithProbability() var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false); // Assert - Assert.True(new[] { HttpStatusCode.OK, HttpStatusCode.InternalServerError }.Contains(response.StatusCode)); + Assert.Contains(response.StatusCode, [HttpStatusCode.OK, HttpStatusCode.InternalServerError]); server.Stop(); }