From f545356e5582a6e4ba538a0f9e70a4c7eb619e8f Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 13 Oct 2025 10:22:20 +0200 Subject: [PATCH 1/4] Fix WithProbability logic --- examples/WireMock.Net.Console.NET8/MainApp.cs | 64 ++++++++++++++----- examples/WireMock.Net.Console.NET8/Program.cs | 5 +- .../Owin/MappingMatcher.cs | 15 +++-- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 942177dc8..f0bc29335 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,24 +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.40; 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} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot); + return; using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings { 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..2c7316d6e 100644 --- a/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs +++ b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs @@ -28,7 +28,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 || m.Probability > _randomizerDoubleBetween0And1.Generate()) .ToArray(); foreach (var mapping in mappings) @@ -62,14 +62,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 ?? 0) .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 +80,10 @@ 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) + .ThenByDescending(m => m.Mapping.Probability ?? 0) + .ThenByDescending(m => m.Mapping.UpdatedAt) .FirstOrDefault(); return (match, partialMatch); From 8c137c0d9b3b7cd17a6f03cb767e6d7ca13da755 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 13 Oct 2025 10:23:54 +0200 Subject: [PATCH 2/4] . --- examples/WireMock.Net.Console.NET8/MainApp.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index f0bc29335..b47ab7078 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -339,7 +339,6 @@ public static async Task RunAsync() } } System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot); - return; using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings { From e43f4a0ff4b49ad6ba124e2f36d2204221c241e0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 13 Oct 2025 11:16:30 +0200 Subject: [PATCH 3/4] FIX --- examples/WireMock.Net.Console.NET8/MainApp.cs | 6 +- .../Owin/MappingMatcher.cs | 25 ++++---- .../Owin/MappingMatcherTests.cs | 57 +++++++++++++------ .../WireMockServerTests.WithProbability.cs | 2 +- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index b47ab7078..fa88484ca 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -309,7 +309,7 @@ public static async Task RunAsync() // .WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())]) // ); - var pX = 0.40; + var pX = 0.80; server .Given(Request.Create().UsingGet().WithPath("/p")) .WithProbability(pX) @@ -338,8 +338,8 @@ public static async Task RunAsync() defaultCount++; } } - System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot); - + 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/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs index 2c7316d6e..cff328861 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) @@ -40,11 +34,11 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe var mappingMatcherResult = new MappingMatcherResult(mapping, mapping.GetRequestMatchResult(request, nextState)); var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails - .Where(md => md.Exception != null) .Select(md => md.Exception) + .OfType() .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); } } } @@ -67,7 +61,7 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe .OrderBy(m => m.RequestMatchResult) .ThenBy(m => m.RequestMatchResult.TotalNumber) .ThenBy(m => m.Mapping.Priority) - .ThenByDescending(m => m.Mapping.Probability ?? 0) + .ThenByDescending(m => m.Mapping.Probability) .ThenByDescending(m => m.Mapping.UpdatedAt) .Where(pm => pm.RequestMatchResult.AverageTotalScore > 0.0) .ToArray(); @@ -82,7 +76,8 @@ public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetwe .Where(m => m.RequestMatchResult.IsPerfectMatch) .OrderBy(m => m.Mapping.Priority) .ThenBy(m => m.RequestMatchResult) - .ThenByDescending(m => m.Mapping.Probability ?? 0) + .ThenBy(m => m.RequestMatchResult.TotalNumber) + .ThenByDescending(m => m.Mapping.Probability) .ThenByDescending(m => m.Mapping.UpdatedAt) .FirstOrDefault(); 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(); } From cbf1e3937afe8c48bbcf877e6bf369aa74db8e95 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 13 Oct 2025 12:11:57 +0200 Subject: [PATCH 4/4] Update src/WireMock.Net.Minimal/Owin/MappingMatcher.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/WireMock.Net.Minimal/Owin/MappingMatcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs index cff328861..0180e8650 100644 --- a/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs +++ b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs @@ -34,8 +34,8 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou var mappingMatcherResult = new MappingMatcherResult(mapping, mapping.GetRequestMatchResult(request, nextState)); var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails - .Select(md => md.Exception) - .OfType() + .Where(md => md.Exception != null) + .Select(md => md.Exception!) .ToArray(); if (exceptions.Length == 0)