mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-03-26 02:51:04 +01:00
* Move ScenarioState to Abstractions and add IScenarioStateStore interface ScenarioState is moved to the Abstractions project so it can be referenced by the new IScenarioStateStore interface. The interface defines the contract for storing and retrieving scenario states, enabling distributed implementations. * Add InMemoryScenarioStateStore default implementation Wraps ConcurrentDictionary with OrdinalIgnoreCase comparer, preserving exact current behavior. The Update method encapsulates read-modify-write so distributed implementations can make it atomic. * Wire IScenarioStateStore into middleware options, settings, and consumers Replace direct ConcurrentDictionary<string, ScenarioState> usage with IScenarioStateStore across all consumer files. The store is injectable via WireMockServerSettings.ScenarioStateStore, defaulting to the InMemoryScenarioStateStore for backward compatibility. * Add FileBasedScenarioStateStore for persistent scenario state In-memory ConcurrentDictionary backed by JSON file persistence in __admin/scenarios/. Reads from cache, mutations write through to disk. Constructor loads existing state from disk on startup. * Make ScenarioStateStore non-nullable with default InMemoryScenarioStateStore Move InMemoryScenarioStateStore from WireMock.Net.Minimal to WireMock.Net.Shared so it lives alongside WireMockServerSettings. This allows WireMockServerSettings.ScenarioStateStore to be non-nullable with a default value, following the same pattern as DefaultJsonSerializer. The null-coalescing fallback in WireMockMiddlewareOptionsHelper is no longer needed.
256 lines
9.2 KiB
C#
256 lines
9.2 KiB
C#
// Copyright © WireMock.Net
|
|
|
|
using System.Collections.Concurrent;
|
|
using Moq;
|
|
using WireMock.Handlers;
|
|
using WireMock.Logging;
|
|
using WireMock.Matchers.Request;
|
|
using WireMock.Models;
|
|
using WireMock.Owin;
|
|
using WireMock.Services;
|
|
|
|
namespace WireMock.Net.Tests.Owin;
|
|
|
|
public class MappingMatcherTests
|
|
{
|
|
private readonly Mock<IWireMockMiddlewareOptions> _optionsMock;
|
|
private readonly Mock<IRandomizerDoubleBetween0And1> _randomizerDoubleBetween0And1Mock;
|
|
|
|
private readonly MappingMatcher _sut;
|
|
|
|
public MappingMatcherTests()
|
|
{
|
|
_optionsMock = new Mock<IWireMockMiddlewareOptions>();
|
|
_optionsMock.SetupAllProperties();
|
|
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
|
|
_optionsMock.Setup(o => o.LogEntries).Returns([]);
|
|
_optionsMock.Setup(o => o.ScenarioStateStore).Returns(new InMemoryScenarioStateStore());
|
|
|
|
var loggerMock = new Mock<IWireMockLogger>();
|
|
loggerMock.SetupAllProperties();
|
|
loggerMock.Setup(l => l.Error(It.IsAny<string>()));
|
|
_optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object);
|
|
|
|
_randomizerDoubleBetween0And1Mock = new Mock<IRandomizerDoubleBetween0And1>();
|
|
_randomizerDoubleBetween0And1Mock.Setup(r => r.Generate()).Returns(0.5);
|
|
|
|
_sut = new MappingMatcher(_optionsMock.Object, _randomizerDoubleBetween0And1Mock.Object);
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull()
|
|
{
|
|
// Assign
|
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
|
|
|
// Act
|
|
var result = _sut.FindBestMatch(request);
|
|
|
|
// Assert
|
|
result.Match.Should().BeNull();
|
|
result.Partial.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull()
|
|
{
|
|
// Assign
|
|
var mappingMock = new Mock<IMapping>();
|
|
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>();
|
|
|
|
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
|
mappings.TryAdd(Guid.NewGuid(), mappingMock.Object);
|
|
|
|
_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().BeNull();
|
|
result.Partial.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch()
|
|
{
|
|
// Assign
|
|
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
|
var mappings = InitMappings
|
|
(
|
|
(guid1, [0.1], null),
|
|
(guid2, [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(guid2);
|
|
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
|
|
|
|
result.Partial.Should().NotBeNull();
|
|
result.Partial!.Mapping.Guid.Should().Be(guid2);
|
|
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExactMatch_ShouldReturnNullExactMatch_And_PartialMatch()
|
|
{
|
|
// Assign
|
|
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
|
var mappings = InitMappings
|
|
(
|
|
(guid1, [0.1], null),
|
|
(guid2, [0.9], 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().BeNull();
|
|
|
|
result.Partial.Should().NotBeNull();
|
|
result.Partial!.Mapping.Guid.Should().Be(guid2);
|
|
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch()
|
|
{
|
|
// Assign
|
|
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
|
|
|
_optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true);
|
|
var mappings = InitMappings(
|
|
(guid1, [0.1], null),
|
|
(guid2, [0.9], 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(guid2);
|
|
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
|
|
|
|
result.Partial.Should().NotBeNull();
|
|
result.Partial!.Mapping.Guid.Should().Be(guid2);
|
|
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_WithSameAverageScoreButMoreMatchers_ReturnsMatchWithMoreMatchers()
|
|
{
|
|
// Assign
|
|
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
|
var mappings = InitMappings(
|
|
(guid1, [1.0], null),
|
|
(guid2, [1.0, 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 and Verify
|
|
result.Match.Should().NotBeNull();
|
|
result.Match!.Mapping.Guid.Should().Be(guid2);
|
|
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
|
|
|
|
result.Partial.Should().NotBeNull();
|
|
result.Partial!.Mapping.Guid.Should().Be(guid2);
|
|
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void MappingMatcher_FindBestMatch_WhenProbabilityDoesNotMatch_ShouldReturnNormalMatch()
|
|
{
|
|
// 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.4),
|
|
(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(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<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
|
|
{
|
|
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
|
|
|
foreach (var match in matches)
|
|
{
|
|
var mappingMock = new Mock<IMapping>();
|
|
mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
|
|
|
|
var requestMatchResult = new RequestMatchResult();
|
|
foreach (var score in match.scores)
|
|
{
|
|
requestMatchResult.AddScore(typeof(object), score, null);
|
|
}
|
|
|
|
mappingMock.SetupGet(m => m.Probability).Returns(match.probability);
|
|
|
|
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(requestMatchResult);
|
|
|
|
mappings.TryAdd(match.guid, mappingMock.Object);
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
} |