mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-28 03:07:01 +02:00
Add injectable IScenarioStateStore for distributed scenario state (#1430)
* 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.
This commit is contained in:
@@ -27,7 +27,7 @@ internal interface IWireMockMiddlewareOptions
|
||||
|
||||
ConcurrentDictionary<Guid, IMapping> Mappings { get; }
|
||||
|
||||
ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
|
||||
IScenarioStateStore ScenarioStateStore { get; set; }
|
||||
|
||||
ConcurrentObservableCollection<LogEntry> LogEntries { get; }
|
||||
|
||||
|
||||
@@ -89,14 +89,13 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
|
||||
|
||||
private string? GetNextState(IMapping mapping)
|
||||
{
|
||||
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,
|
||||
// If the mapping does not have a scenario or the store does not contain this scenario,
|
||||
// just return null to indicate that there is no next state.
|
||||
if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario))
|
||||
if (mapping.Scenario == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Else just return the next state
|
||||
return _options.Scenarios[mapping.Scenario].NextState;
|
||||
return _options.ScenarioStateStore.TryGet(mapping.Scenario, out var state) ? state.NextState : null;
|
||||
}
|
||||
}
|
||||
@@ -81,9 +81,9 @@ internal class WireMockMiddleware(
|
||||
}
|
||||
|
||||
// Set scenario start
|
||||
if (!options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
|
||||
if (!options.ScenarioStateStore.ContainsKey(mapping.Scenario) && mapping.IsStartState)
|
||||
{
|
||||
options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState
|
||||
options.ScenarioStateStore.TryAdd(mapping.Scenario, new ScenarioState
|
||||
{
|
||||
Name = mapping.Scenario
|
||||
});
|
||||
@@ -233,20 +233,21 @@ internal class WireMockMiddleware(
|
||||
|
||||
private void UpdateScenarioState(IMapping mapping)
|
||||
{
|
||||
var scenario = options.Scenarios[mapping.Scenario!];
|
||||
|
||||
// Increase the number of times this state has been executed
|
||||
scenario.Counter++;
|
||||
|
||||
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
|
||||
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
|
||||
options.ScenarioStateStore.Update(mapping.Scenario!, scenario =>
|
||||
{
|
||||
scenario.NextState = mapping.NextState;
|
||||
scenario.Counter = 0;
|
||||
}
|
||||
// Increase the number of times this state has been executed
|
||||
scenario.Counter++;
|
||||
|
||||
// Else just update Started and Finished
|
||||
scenario.Started = true;
|
||||
scenario.Finished = mapping.NextState == null;
|
||||
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
|
||||
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
|
||||
{
|
||||
scenario.NextState = mapping.NextState;
|
||||
scenario.Counter = 0;
|
||||
}
|
||||
|
||||
// Else just update Started and Finished
|
||||
scenario.Started = true;
|
||||
scenario.Finished = mapping.NextState == null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
|
||||
|
||||
public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>();
|
||||
|
||||
public ConcurrentDictionary<string, ScenarioState> Scenarios { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
public IScenarioStateStore ScenarioStateStore { get; set; } = new InMemoryScenarioStateStore();
|
||||
|
||||
public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ internal static class WireMockMiddlewareOptionsHelper
|
||||
options.FileSystemHandler = settings.FileSystemHandler;
|
||||
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
|
||||
options.Logger = settings.Logger;
|
||||
options.ScenarioStateStore = settings.ScenarioStateStore;
|
||||
options.MaxRequestLogCount = settings.MaxRequestLogCount;
|
||||
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
|
||||
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
|
||||
|
||||
Reference in New Issue
Block a user