mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-03 15:37:01 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3214c2ebc7 | ||
|
|
6c6a42979e | ||
|
|
b4f5b9256c | ||
|
|
070e4b6ab9 | ||
|
|
f919929cb7 | ||
|
|
cdd33695e5 | ||
|
|
c4caa25eb6 | ||
|
|
ca788cb9b0 | ||
|
|
d08ce944b6 | ||
|
|
0a9f37e857 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,13 @@
|
||||
# 2.2.0 (30 March 2026)
|
||||
- [#1433](https://github.com/wiremock/WireMock.Net/pull/1433) - Add comments for ScenarioStateStore related code [refactor] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1434](https://github.com/wiremock/WireMock.Net/pull/1434) - Upgrade Scriban.Signed [security] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 2.1.0 (29 March 2026)
|
||||
- [#1425](https://github.com/wiremock/WireMock.Net/pull/1425) - Add helpers for query params fluent MappingModelBuilder [feature] contributed by [biltongza](https://github.com/biltongza)
|
||||
- [#1430](https://github.com/wiremock/WireMock.Net/pull/1430) - Add injectable IScenarioStateStore for distributed scenario state [feature] contributed by [m4tchl0ck](https://github.com/m4tchl0ck)
|
||||
- [#1431](https://github.com/wiremock/WireMock.Net/pull/1431) - Fix WireMockLogger implementation in dotnet-WireMock [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1432](https://github.com/wiremock/WireMock.Net/pull/1432) - Add WireMockAspNetCoreLogger to log Kestrel warnings/errors [feature] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 2.0.0 (11 March 2026)
|
||||
- [#1359](https://github.com/wiremock/WireMock.Net/pull/1359) - Version 2.x contributed by [StefH](https://github.com/StefH)
|
||||
- [#1394](https://github.com/wiremock/WireMock.Net/pull/1394) - MappingSerializer (Newtonsoft or SystemText)-Json [feature] contributed by [StefH](https://github.com/StefH)
|
||||
@@ -1356,11 +1366,11 @@
|
||||
- [#145](https://github.com/wiremock/WireMock.Net/pull/145) - Cancellation token not passed to server instance in .NET Core 2 [bug] contributed by [Bob11327](https://github.com/Bob11327)
|
||||
|
||||
# 1.0.3.18 (25 May 2018)
|
||||
- [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers contributed by [StefH](https://github.com/StefH)
|
||||
- [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#122](https://github.com/wiremock/WireMock.Net/issues/122) - WireMock.Net not responding in unit tests - same works in console application
|
||||
- [#126](https://github.com/wiremock/WireMock.Net/issues/126) - Question: UsingHead always returns 0 for Content-Length header even when explicitly specified
|
||||
- [#132](https://github.com/wiremock/WireMock.Net/issues/132) - LogEntries not being recorded on subsequent tests
|
||||
- [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header
|
||||
- [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header [bug]
|
||||
- [#137](https://github.com/wiremock/WireMock.Net/issues/137) - Question: How to specify Transfer-Encoding response header?
|
||||
- [#139](https://github.com/wiremock/WireMock.Net/issues/139) - Wiki link https://github.com/StefH/WireMock.Net/wiki/Record-(via-proxy)-and-Save is dead
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>2.0.0</VersionPrefix>
|
||||
<VersionPrefix>2.2.0</VersionPrefix>
|
||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
rem https://github.com/StefH/GitHubReleaseNotes
|
||||
|
||||
SET version=2.0.0
|
||||
SET version=2.2.0
|
||||
|
||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
||||
|
||||
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%
|
||||
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%
|
||||
@@ -1,8 +1,5 @@
|
||||
# 2.0.0 (11 March 2026)
|
||||
- #1359 Version 2.x
|
||||
- #1394 MappingSerializer (Newtonsoft or SystemText)-Json [feature]
|
||||
- #1341 Configurable JSON serialization support (Newtonsoft.Json vs System.Text.Json) [feature]
|
||||
- #1422 WireMock.Net seems to be incompatible with Microsoft.Owin.Security.Interop [bug]
|
||||
- #1424 WireMock.Net.FluentAssertions is incompatible with WireMock.Net.Aspire [feature]
|
||||
# 2.2.0 (30 March 2026)
|
||||
- #1433 Add comments for ScenarioStateStore related code [refactor]
|
||||
- #1434 Upgrade Scriban.Signed [security]
|
||||
|
||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||
@@ -71,6 +71,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
| **WireMock.Net.OpenTelemetry** | [](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry)
|
||||
| | | |
|
||||
| **WireMock.Net.RestClient** | [](https://www.nuget.org/packages/WireMock.Net.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
|
||||
| **WireMock.Net.RestClient.AwesomeAssertions** | [](https://www.nuget.org/packages/WireMock.Net.RestClient.AwesomeAssertions) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient.AwesomeAssertions)
|
||||
| **WireMock.Org.RestClient** | [](https://www.nuget.org/packages/WireMock.Org.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
|
||||
|
||||
<br />
|
||||
|
||||
@@ -82,6 +82,51 @@ class Program
|
||||
)
|
||||
);
|
||||
|
||||
mappingBuilder.Given(m => m
|
||||
.WithRequest(req => req
|
||||
.WithPath("/testRequestWithQueryParams")
|
||||
.UsingGet()
|
||||
.WithParams(p => p.WithParam("param1", pb => pb.WithExactMatcher("value1")))
|
||||
).WithResponse(rsp => rsp
|
||||
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||
.WithStatusCode(200)
|
||||
.WithBodyAsJson(new
|
||||
{
|
||||
status = "ok"
|
||||
}, true)
|
||||
)
|
||||
);
|
||||
|
||||
mappingBuilder.Given(m => m
|
||||
.WithRequest(req => req
|
||||
.WithPath("/testRequestWithHeaders")
|
||||
.UsingGet()
|
||||
.WithHeaders(h => h.WithHeader("Accept", hb => hb.WithExactMatcher("application/json")))
|
||||
).WithResponse(rsp => rsp
|
||||
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||
.WithStatusCode(200)
|
||||
.WithBodyAsJson(new
|
||||
{
|
||||
status = "ok"
|
||||
}, true)
|
||||
)
|
||||
);
|
||||
|
||||
mappingBuilder.Given(m => m
|
||||
.WithRequest(req => req
|
||||
.WithPath("/testRequestWithCookie")
|
||||
.UsingGet()
|
||||
.WithCookies(c => c.WithCookie("cookie1", cb => cb.WithExactMatcher("cookievalue1")))
|
||||
).WithResponse(rsp => rsp
|
||||
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||
.WithStatusCode(200)
|
||||
.WithBodyAsJson(new
|
||||
{
|
||||
status = "ok"
|
||||
}, true)
|
||||
)
|
||||
);
|
||||
|
||||
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
|
||||
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using SharpYaml.Model;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Models;
|
||||
@@ -288,7 +289,24 @@ namespace WireMock.Net.ConsoleApplication
|
||||
|
||||
var todos = new Dictionary<int, Todo>();
|
||||
|
||||
var server = WireMockServer.Start();
|
||||
var server = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new WireMockConsoleLogger(),
|
||||
|
||||
Port = 9091
|
||||
});
|
||||
|
||||
server
|
||||
.WhenRequest(r => r
|
||||
.WithPath("/Content-Length")
|
||||
.UsingAnyMethod()
|
||||
)
|
||||
.ThenRespondWith(r => r
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithHeader("Content-Length", "42")
|
||||
);
|
||||
|
||||
System.Console.ReadLine();
|
||||
|
||||
//server
|
||||
// .Given(Request.Create()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
public partial class ArrayMatcherModelBuilder
|
||||
{
|
||||
public ArrayMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public ArrayMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public ArrayMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public ArrayMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
|
||||
{
|
||||
return Add(mb => mb
|
||||
.WithName("NotNullOrEmptyMatcher")
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
);
|
||||
}
|
||||
|
||||
private ArrayMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
|
||||
{
|
||||
return Add(mb => mb
|
||||
.WithName(name)
|
||||
.WithPattern(pattern)
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
.WithIgnoreCase(ignoreCase)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
public partial class IListCookieModelBuilder
|
||||
{
|
||||
public IListCookieModelBuilder WithCookie(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||
{
|
||||
return Add(cookieBuilder => cookieBuilder
|
||||
.WithName(name)
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
public partial class IListHeaderModelBuilder
|
||||
{
|
||||
public IListHeaderModelBuilder WithHeader(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||
{
|
||||
return Add(headerBuilder => headerBuilder
|
||||
.WithName(name)
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
public partial class IListMatcherModelBuilder
|
||||
{
|
||||
public IListMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public IListMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public IListMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||
{
|
||||
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||
}
|
||||
|
||||
public IListMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
|
||||
{
|
||||
return Add(mb => mb
|
||||
.WithName("NotNullOrEmptyMatcher")
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
);
|
||||
}
|
||||
|
||||
private IListMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
|
||||
{
|
||||
return Add(mb => mb
|
||||
.WithName(name)
|
||||
.WithPattern(pattern)
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
.WithIgnoreCase(ignoreCase)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
public partial class IListParamModelBuilder
|
||||
{
|
||||
public IListParamModelBuilder WithParam(string name, Action<ArrayMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||
{
|
||||
return Add(paramBuilder => paramBuilder
|
||||
.WithName(name)
|
||||
.WithRejectOnMatch(rejectOnMatch)
|
||||
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace WireMock.Handlers;
|
||||
|
||||
public interface IScenarioStateStore
|
||||
{
|
||||
bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state);
|
||||
|
||||
IReadOnlyList<ScenarioState> GetAll();
|
||||
|
||||
bool ContainsKey(string name);
|
||||
|
||||
bool TryAdd(string name, ScenarioState scenarioState);
|
||||
|
||||
ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory);
|
||||
|
||||
ScenarioState? Update(string name, Action<ScenarioState> updateAction);
|
||||
|
||||
bool TryRemove(string name);
|
||||
|
||||
void Clear();
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class MatchDetail
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the matcher.
|
||||
/// </summary>
|
||||
public required Type MatcherType { get; set; }
|
||||
public required string MatcherType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the matcher.
|
||||
|
||||
@@ -31,4 +31,4 @@ public class ScenarioState
|
||||
/// Gets or sets the state counter.
|
||||
/// </summary>
|
||||
public int Counter { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,6 @@ public interface IWireMockServer : IDisposable
|
||||
/// </summary>
|
||||
IReadOnlyList<MappingModel> MappingModels { get; }
|
||||
|
||||
// <summary>
|
||||
// Gets the mappings.
|
||||
// </summary>
|
||||
//[PublicAPI]
|
||||
//IEnumerable<IMapping> Mappings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ports.
|
||||
/// </summary>
|
||||
@@ -69,8 +63,6 @@ public interface IWireMockServer : IDisposable
|
||||
/// </summary>
|
||||
string? Provider { get; }
|
||||
|
||||
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [log entries changed].
|
||||
/// </summary>
|
||||
@@ -115,8 +107,6 @@ public interface IWireMockServer : IDisposable
|
||||
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
|
||||
IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers);
|
||||
|
||||
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a static mapping file and adds or updates a single mapping.
|
||||
///
|
||||
|
||||
145
src/WireMock.Net.Minimal/Handlers/FileBasedScenarioStateStore.cs
Normal file
145
src/WireMock.Net.Minimal/Handlers/FileBasedScenarioStateStore.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a file-based implementation of <see cref="IScenarioStateStore" /> that persists scenario states to disk and allows concurrent access.
|
||||
/// </summary>
|
||||
public class FileBasedScenarioStateStore : IScenarioStateStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ScenarioState> _scenarios = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly string _scenariosFolder;
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the FileBasedScenarioStateStore class using the specified root folder as the base directory for scenario state storage.
|
||||
/// </summary>
|
||||
/// <param name="rootFolder">The root directory under which scenario state data will be stored. Must be a valid file system path.</param>
|
||||
public FileBasedScenarioStateStore(string rootFolder)
|
||||
{
|
||||
Guard.NotNullOrEmpty(rootFolder);
|
||||
|
||||
_scenariosFolder = Path.Combine(rootFolder, "__admin", "scenarios");
|
||||
Directory.CreateDirectory(_scenariosFolder);
|
||||
LoadScenariosFromDisk();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state)
|
||||
{
|
||||
return _scenarios.TryGetValue(name, out state);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ScenarioState> GetAll()
|
||||
{
|
||||
return _scenarios.Values.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(string name)
|
||||
{
|
||||
return _scenarios.ContainsKey(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryAdd(string name, ScenarioState scenarioState)
|
||||
{
|
||||
if (_scenarios.TryAdd(name, scenarioState))
|
||||
{
|
||||
WriteScenarioToFile(name, scenarioState);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var result = _scenarios.AddOrUpdate(name, addFactory, updateFactory);
|
||||
WriteScenarioToFile(name, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScenarioState? Update(string name, Action<ScenarioState> updateAction)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_scenarios.TryGetValue(name, out var state))
|
||||
{
|
||||
updateAction(state);
|
||||
WriteScenarioToFile(name, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryRemove(string name)
|
||||
{
|
||||
if (_scenarios.TryRemove(name, out _))
|
||||
{
|
||||
DeleteScenarioFile(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_scenarios.Clear();
|
||||
|
||||
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetScenarioFilePath(string name)
|
||||
{
|
||||
var sanitized = string.Concat(name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
|
||||
return Path.Combine(_scenariosFolder, sanitized + ".json");
|
||||
}
|
||||
|
||||
private void WriteScenarioToFile(string name, ScenarioState state)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(state, Formatting.Indented);
|
||||
File.WriteAllText(GetScenarioFilePath(name), json);
|
||||
}
|
||||
|
||||
private void DeleteScenarioFile(string name)
|
||||
{
|
||||
var path = GetScenarioFilePath(name);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadScenariosFromDisk()
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
|
||||
{
|
||||
var json = File.ReadAllText(file);
|
||||
var state = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||
if (state != null)
|
||||
{
|
||||
_scenarios.TryAdd(state.Name, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/WireMock.Net.Minimal/Logging/WireMockAspNetCoreLogger.cs
Normal file
38
src/WireMock.Net.Minimal/Logging/WireMockAspNetCoreLogger.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WireMock.Logging;
|
||||
|
||||
internal sealed class WireMockAspNetCoreLogger(IWireMockLogger logger, string categoryName) : ILogger
|
||||
{
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = formatter(state, exception);
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
message = $"{message} | Exception: {exception}";
|
||||
}
|
||||
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Warning:
|
||||
logger.Warn("[{0}] {1}", categoryName, message);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.Error("[{0}] {1}", categoryName, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WireMock.Logging;
|
||||
|
||||
internal sealed class WireMockAspNetCoreLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly IWireMockLogger _logger;
|
||||
|
||||
public WireMockAspNetCoreLoggerProvider(IWireMockLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => new WireMockAspNetCoreLogger(_logger, categoryName);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +28,7 @@ public class RequestMatchResult : IRequestMatchResult
|
||||
return AddMatchDetail(new MatchDetail
|
||||
{
|
||||
Name = matcherType.Name.Replace("RequestMessage", string.Empty),
|
||||
MatcherType = matcherType,
|
||||
MatcherType = matcherType.Name,
|
||||
Score = score,
|
||||
Exception = exception
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stef.Validation;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Owin.Mappers;
|
||||
@@ -57,6 +57,12 @@ internal partial class AspNetCoreSelfHost
|
||||
_host = builder
|
||||
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
|
||||
.ConfigureAppConfigurationUsingEnvironmentVariables()
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddProvider(new WireMockAspNetCoreLoggerProvider(_logger));
|
||||
logging.SetMinimumLevel(LogLevel.Warning);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(_wireMockMiddlewareOptions);
|
||||
@@ -169,10 +175,10 @@ internal partial class AspNetCoreSelfHost
|
||||
|
||||
return _host.RunAsync(token);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
RunningException = e;
|
||||
_logger.Error(e.ToString());
|
||||
RunningException = ex;
|
||||
_logger.Error("Error while RunAsync", ex);
|
||||
|
||||
IsStarted = false;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
@@ -88,19 +86,15 @@ namespace WireMock.Owin.Mappers
|
||||
break;
|
||||
}
|
||||
|
||||
var statusCodeType = responseMessage.StatusCode?.GetType();
|
||||
if (statusCodeType != null)
|
||||
if (responseMessage.StatusCode is HttpStatusCode or int)
|
||||
{
|
||||
if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
|
||||
}
|
||||
else if (statusCodeType == typeof(string))
|
||||
{
|
||||
// Note: this case will also match on null
|
||||
int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt);
|
||||
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
|
||||
}
|
||||
response.StatusCode = MapStatusCode((int) responseMessage.StatusCode);
|
||||
}
|
||||
else if (responseMessage.StatusCode is string statusCodeAsString)
|
||||
{
|
||||
// Note: this case will also match on null
|
||||
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
|
||||
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
|
||||
}
|
||||
|
||||
SetResponseHeaders(responseMessage, bytes != null, response);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -672,7 +672,7 @@ public partial class WireMockServer
|
||||
#region Scenarios
|
||||
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel
|
||||
var scenariosStates = Scenarios.Select(s => new ScenarioStateModel
|
||||
{
|
||||
Name = s.Name,
|
||||
NextState = s.NextState,
|
||||
@@ -705,7 +705,7 @@ public partial class WireMockServer
|
||||
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
|
||||
if (!_options.Scenarios.ContainsKey(name))
|
||||
if (!_options.ScenarioStateStore.ContainsKey(name))
|
||||
{
|
||||
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
||||
return target?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value)
|
||||
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object? value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -46,7 +46,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value)
|
||||
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object? value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -56,7 +56,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value)
|
||||
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object? value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace WireMock.Transformers.Scriban;
|
||||
|
||||
internal class WireMockTemplateContext : TemplateContext
|
||||
{
|
||||
protected override IObjectAccessor GetMemberAccessorImpl(object target)
|
||||
protected override IObjectAccessor? GetMemberAccessorImpl(object target)
|
||||
{
|
||||
return target?.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ?
|
||||
return target.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ?
|
||||
new WireMockListAccessor() :
|
||||
base.GetMemberAccessorImpl(target);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
|
||||
<PackageReference Include="Scriban.Signed" Version="5.5.0" />
|
||||
<PackageReference Include="Scriban.Signed" Version="7.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace WireMock.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an in-memory implementation of the <see cref="IScenarioStateStore" /> interface for managing scenario state objects by name.
|
||||
/// </summary>
|
||||
public class InMemoryScenarioStateStore : IScenarioStateStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ScenarioState> _scenarios = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state)
|
||||
{
|
||||
return _scenarios.TryGetValue(name, out state);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ScenarioState> GetAll()
|
||||
{
|
||||
return _scenarios.Values.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(string name)
|
||||
{
|
||||
return _scenarios.ContainsKey(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryAdd(string name, ScenarioState scenarioState)
|
||||
{
|
||||
return _scenarios.TryAdd(name, scenarioState);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory)
|
||||
{
|
||||
return _scenarios.AddOrUpdate(name, addFactory, updateFactory);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScenarioState? Update(string name, Action<ScenarioState> updateAction)
|
||||
{
|
||||
if (_scenarios.TryGetValue(name, out var state))
|
||||
{
|
||||
updateAction(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryRemove(string name)
|
||||
{
|
||||
return _scenarios.TryRemove(name, out _);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_scenarios.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers.Request;
|
||||
@@ -119,7 +118,7 @@ public class MatchResult
|
||||
return new MatchDetail
|
||||
{
|
||||
Name = Name,
|
||||
MatcherType = typeof(MatchResult),
|
||||
MatcherType = typeof(MatchResult).Name,
|
||||
Score = Score,
|
||||
Exception = Exception,
|
||||
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
|
||||
|
||||
@@ -175,6 +175,17 @@ public class WireMockServerSettings
|
||||
[JsonIgnore]
|
||||
public IFileSystemHandler FileSystemHandler { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the store used to persist scenario state information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The scenario state store manages the storage and retrieval of state data associated with scenarios.
|
||||
/// By default, an in-memory implementation is used, but this property can be set to a custom implementation to support alternative storage mechanisms such as databases or distributed caches.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
[JsonIgnore]
|
||||
public IScenarioStateStore ScenarioStateStore { get; set; } = new InMemoryScenarioStateStore();
|
||||
|
||||
/// <summary>
|
||||
/// Action which can be used to add additional Handlebars registrations. [Optional]
|
||||
/// </summary>
|
||||
@@ -254,7 +265,7 @@ public class WireMockServerSettings
|
||||
/// Whether to accept any client certificate
|
||||
/// </summary>
|
||||
public bool AcceptAnyClientCertificate { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the global IWebhookSettings to use.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Admin.Requests;
|
||||
using WireMock.Types;
|
||||
@@ -9,10 +10,13 @@ namespace WireMock.Net.Json;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(EncodingModel))]
|
||||
[JsonSerializable(typeof(JArray))]
|
||||
[JsonSerializable(typeof(JObject))]
|
||||
[JsonSerializable(typeof(LogEntryModel))]
|
||||
[JsonSerializable(typeof(LogRequestModel))]
|
||||
[JsonSerializable(typeof(LogResponseModel))]
|
||||
[JsonSerializable(typeof(LogRequestMatchModel))]
|
||||
[JsonSerializable(typeof(StatusModel))]
|
||||
[JsonSerializable(typeof(WireMockList<string>))]
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WireMock.Admin.Requests;
|
||||
@@ -11,11 +10,6 @@ namespace WireMock.Net;
|
||||
|
||||
public class WireMockLogger : IWireMockLogger
|
||||
{
|
||||
private readonly JsonSerializerOptions _options = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public WireMockLogger(ILogger logger)
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="coverlet.collector" Version="8.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageReference Include="WireMock.Net" Version="1.25.0" />
|
||||
<PackageReference Include="WireMock.Net" Version="2.1.0" />
|
||||
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Handlers;
|
||||
using Xunit;
|
||||
|
||||
namespace WireMock.Net.Tests.Handlers;
|
||||
|
||||
public class FileBasedScenarioStateStoreTests : IDisposable
|
||||
{
|
||||
private readonly string _tempFolder;
|
||||
private readonly string _scenariosFolder;
|
||||
|
||||
public FileBasedScenarioStateStoreTests()
|
||||
{
|
||||
_tempFolder = Path.Combine(Path.GetTempPath(), "WireMock_Tests_" + Guid.NewGuid().ToString("N"));
|
||||
_scenariosFolder = Path.Combine(_tempFolder, "__admin", "scenarios");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_tempFolder))
|
||||
{
|
||||
Directory.Delete(_tempFolder, true);
|
||||
}
|
||||
}
|
||||
|
||||
private FileBasedScenarioStateStore CreateSut() => new(_tempFolder);
|
||||
|
||||
// --- Mirror tests from InMemoryScenarioStateStoreTests ---
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ShouldAddNewScenario()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
var state = new ScenarioState { Name = "scenario1" };
|
||||
|
||||
sut.TryAdd("scenario1", state).Should().BeTrue();
|
||||
|
||||
sut.ContainsKey("scenario1").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ShouldReturnFalse_WhenScenarioAlreadyExists()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
var state = new ScenarioState { Name = "scenario1" };
|
||||
sut.TryAdd("scenario1", state);
|
||||
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" }).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldReturnTrue_WhenExists()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
var state = new ScenarioState { Name = "scenario1", NextState = "state2" };
|
||||
sut.TryAdd("scenario1", state);
|
||||
|
||||
sut.TryGet("scenario1", out var result).Should().BeTrue();
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldReturnFalse_WhenNotExists()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryGet("nonexistent", out var result).Should().BeFalse();
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ShouldReturnAllScenarios()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||
|
||||
var result = sut.GetAll();
|
||||
|
||||
result.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ShouldReturnEmpty_WhenNoScenarios()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.GetAll().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldModifyExistingScenario()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||
|
||||
var result = sut.Update("scenario1", s => { s.Counter = 5; s.NextState = "state2"; });
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Counter.Should().Be(5);
|
||||
result.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldReturnNull_WhenNotExists()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.Update("nonexistent", s => { s.Counter = 5; }).Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdate_ShouldAddNewScenario()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
var result = sut.AddOrUpdate(
|
||||
"scenario1",
|
||||
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||
(_, current) => { current.NextState = "updated"; return current; }
|
||||
);
|
||||
|
||||
result.NextState.Should().Be("added");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdate_ShouldUpdateExistingScenario()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "initial" });
|
||||
|
||||
var result = sut.AddOrUpdate(
|
||||
"scenario1",
|
||||
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||
(_, current) => { current.NextState = "updated"; return current; }
|
||||
);
|
||||
|
||||
result.NextState.Should().Be("updated");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ShouldRemoveExistingScenario()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
|
||||
sut.TryRemove("scenario1").Should().BeTrue();
|
||||
sut.ContainsKey("scenario1").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ShouldReturnFalse_WhenNotExists()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryRemove("nonexistent").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_ShouldRemoveAllScenarios()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||
|
||||
sut.Clear();
|
||||
|
||||
sut.GetAll().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsKey_ShouldBeCaseInsensitive()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1" });
|
||||
|
||||
sut.ContainsKey("scenario1").Should().BeTrue();
|
||||
sut.ContainsKey("SCENARIO1").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldBeCaseInsensitive()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1", NextState = "state2" });
|
||||
|
||||
sut.TryGet("scenario1", out var result1).Should().BeTrue();
|
||||
result1!.NextState.Should().Be("state2");
|
||||
|
||||
sut.TryGet("SCENARIO1", out var result2).Should().BeTrue();
|
||||
result2!.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
// --- File-persistence-specific tests ---
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ShouldCreateJsonFileOnDisk()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "state2" });
|
||||
|
||||
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||
File.Exists(filePath).Should().BeTrue();
|
||||
|
||||
var json = File.ReadAllText(filePath);
|
||||
var deserialized = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||
deserialized!.Name.Should().Be("scenario1");
|
||||
deserialized.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ShouldDeleteJsonFileFromDisk()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
|
||||
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||
File.Exists(filePath).Should().BeTrue();
|
||||
|
||||
sut.TryRemove("scenario1");
|
||||
|
||||
File.Exists(filePath).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_ShouldDeleteAllJsonFilesFromDisk()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||
|
||||
Directory.GetFiles(_scenariosFolder, "*.json").Should().HaveCount(2);
|
||||
|
||||
sut.Clear();
|
||||
|
||||
Directory.GetFiles(_scenariosFolder, "*.json").Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ShouldLoadExistingScenariosFromDisk()
|
||||
{
|
||||
// Pre-write JSON files before constructing the store
|
||||
Directory.CreateDirectory(_scenariosFolder);
|
||||
var state1 = new ScenarioState { Name = "scenario1", NextState = "loaded1" };
|
||||
var state2 = new ScenarioState { Name = "scenario2", NextState = "loaded2", Counter = 3 };
|
||||
File.WriteAllText(Path.Combine(_scenariosFolder, "scenario1.json"), JsonConvert.SerializeObject(state1));
|
||||
File.WriteAllText(Path.Combine(_scenariosFolder, "scenario2.json"), JsonConvert.SerializeObject(state2));
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.GetAll().Should().HaveCount(2);
|
||||
sut.TryGet("scenario1", out var loaded1).Should().BeTrue();
|
||||
loaded1!.NextState.Should().Be("loaded1");
|
||||
sut.TryGet("scenario2", out var loaded2).Should().BeTrue();
|
||||
loaded2!.Counter.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldPersistChangesToDisk()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||
|
||||
sut.Update("scenario1", s => { s.Counter = 10; s.NextState = "persisted"; });
|
||||
|
||||
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||
var json = File.ReadAllText(filePath);
|
||||
var deserialized = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||
deserialized!.Counter.Should().Be(10);
|
||||
deserialized.NextState.Should().Be("persisted");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Handlers;
|
||||
using Xunit;
|
||||
|
||||
namespace WireMock.Net.Tests.Handlers;
|
||||
|
||||
public class InMemoryScenarioStateStoreTests
|
||||
{
|
||||
private readonly InMemoryScenarioStateStore _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ShouldAddNewScenario()
|
||||
{
|
||||
var state = new ScenarioState { Name = "scenario1" };
|
||||
|
||||
_sut.TryAdd("scenario1", state).Should().BeTrue();
|
||||
|
||||
_sut.ContainsKey("scenario1").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ShouldReturnFalse_WhenScenarioAlreadyExists()
|
||||
{
|
||||
var state = new ScenarioState { Name = "scenario1" };
|
||||
_sut.TryAdd("scenario1", state);
|
||||
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" }).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldReturnTrue_WhenExists()
|
||||
{
|
||||
var state = new ScenarioState { Name = "scenario1", NextState = "state2" };
|
||||
_sut.TryAdd("scenario1", state);
|
||||
|
||||
_sut.TryGet("scenario1", out var result).Should().BeTrue();
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldReturnFalse_WhenNotExists()
|
||||
{
|
||||
_sut.TryGet("nonexistent", out var result).Should().BeFalse();
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ShouldReturnAllScenarios()
|
||||
{
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
_sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||
|
||||
var result = _sut.GetAll();
|
||||
|
||||
result.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ShouldReturnEmpty_WhenNoScenarios()
|
||||
{
|
||||
_sut.GetAll().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldModifyExistingScenario()
|
||||
{
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||
|
||||
var result = _sut.Update("scenario1", s => { s.Counter = 5; s.NextState = "state2"; });
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Counter.Should().Be(5);
|
||||
result.NextState.Should().Be("state2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldReturnNull_WhenNotExists()
|
||||
{
|
||||
_sut.Update("nonexistent", s => { s.Counter = 5; }).Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdate_ShouldAddNewScenario()
|
||||
{
|
||||
var result = _sut.AddOrUpdate(
|
||||
"scenario1",
|
||||
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||
(_, current) => { current.NextState = "updated"; return current; }
|
||||
);
|
||||
|
||||
result.NextState.Should().Be("added");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdate_ShouldUpdateExistingScenario()
|
||||
{
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "initial" });
|
||||
|
||||
var result = _sut.AddOrUpdate(
|
||||
"scenario1",
|
||||
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||
(_, current) => { current.NextState = "updated"; return current; }
|
||||
);
|
||||
|
||||
result.NextState.Should().Be("updated");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ShouldRemoveExistingScenario()
|
||||
{
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
|
||||
_sut.TryRemove("scenario1").Should().BeTrue();
|
||||
_sut.ContainsKey("scenario1").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ShouldReturnFalse_WhenNotExists()
|
||||
{
|
||||
_sut.TryRemove("nonexistent").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_ShouldRemoveAllScenarios()
|
||||
{
|
||||
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||
_sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||
|
||||
_sut.Clear();
|
||||
|
||||
_sut.GetAll().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsKey_ShouldBeCaseInsensitive()
|
||||
{
|
||||
_sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1" });
|
||||
|
||||
_sut.ContainsKey("scenario1").Should().BeTrue();
|
||||
_sut.ContainsKey("SCENARIO1").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ShouldBeCaseInsensitive()
|
||||
{
|
||||
_sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1", NextState = "state2" });
|
||||
|
||||
_sut.TryGet("scenario1", out var result1).Should().BeTrue();
|
||||
result1!.NextState.Should().Be("state2");
|
||||
|
||||
_sut.TryGet("SCENARIO1", out var result2).Should().BeTrue();
|
||||
result2!.NextState.Should().Be("state2");
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class HttpRequestMessageHelperTests
|
||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||
|
||||
// Assert
|
||||
message.Headers.GetValues("x").Should().Equal(new[] { "value-1" });
|
||||
message.Headers.GetValues("x").Should().Equal(["value-1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -101,7 +101,7 @@ public class HttpRequestMessageHelperTests
|
||||
|
||||
// Assert
|
||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/json" });
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(["application/json"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,7 +121,7 @@ public class HttpRequestMessageHelperTests
|
||||
|
||||
// Assert
|
||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/json; charset=utf-8" });
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(["application/json; charset=utf-8"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -142,7 +142,7 @@ public class HttpRequestMessageHelperTests
|
||||
|
||||
// Assert
|
||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "multipart/form-data" });
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(["multipart/form-data"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ public class HttpRequestMessageHelperTests
|
||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||
|
||||
// Assert
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml" });
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -181,7 +181,7 @@ public class HttpRequestMessageHelperTests
|
||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||
|
||||
// Assert
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml; charset=UTF-8" });
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml; charset=UTF-8"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -200,7 +200,7 @@ public class HttpRequestMessageHelperTests
|
||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||
|
||||
// Assert
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml; charset=Ascii" });
|
||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml; charset=Ascii"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -242,7 +242,7 @@ public class HttpRequestMessageHelperTests
|
||||
|
||||
// Assert
|
||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be(body);
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "multipart/form-data" });
|
||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(["multipart/form-data"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -269,7 +269,4 @@ public class HttpRequestMessageHelperTests
|
||||
// Assert
|
||||
message.Content?.Headers.ContentLength.Should().Be(resultShouldBe ? value : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Moq;
|
||||
using WireMock.Handlers;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Models;
|
||||
@@ -23,7 +24,7 @@ public class MappingMatcherTests
|
||||
_optionsMock.SetupAllProperties();
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
|
||||
_optionsMock.Setup(o => o.LogEntries).Returns([]);
|
||||
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
|
||||
_optionsMock.Setup(o => o.ScenarioStateStore).Returns(new InMemoryScenarioStateStore());
|
||||
|
||||
var loggerMock = new Mock<IWireMockLogger>();
|
||||
loggerMock.SetupAllProperties();
|
||||
|
||||
@@ -55,7 +55,7 @@ public class WireMockMiddlewareTests
|
||||
_optionsMock.SetupAllProperties();
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(_mappings);
|
||||
_optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection<LogEntry>());
|
||||
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
|
||||
_optionsMock.Setup(o => o.ScenarioStateStore).Returns(new InMemoryScenarioStateStore());
|
||||
_optionsMock.Setup(o => o.Logger.Warn(It.IsAny<string>(), It.IsAny<object[]>()));
|
||||
_optionsMock.Setup(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()));
|
||||
_optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny<LogEntryModel>(), It.IsAny<bool>()));
|
||||
|
||||
@@ -325,26 +325,26 @@ public class StatefulBehaviorTests
|
||||
var getResponse1 = await client.GetStringAsync("/todo/items", cancelationToken);
|
||||
getResponse1.Should().Be("Buy milk");
|
||||
|
||||
server.Scenarios["To do list"].Name.Should().Be("To do list");
|
||||
server.Scenarios["To do list"].NextState.Should().Be("TodoList State Started");
|
||||
server.Scenarios["To do list"].Started.Should().BeTrue();
|
||||
server.Scenarios["To do list"].Finished.Should().BeFalse();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Name.Should().Be("To do list");
|
||||
server.Scenarios.First(s => s.Name == "To do list").NextState.Should().Be("TodoList State Started");
|
||||
server.Scenarios.First(s => s.Name == "To do list").Started.Should().BeTrue();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Finished.Should().BeFalse();
|
||||
|
||||
var postResponse = await client.PostAsync("/todo/items", new StringContent("Cancel newspaper subscription"), cancelationToken);
|
||||
postResponse.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
|
||||
server.Scenarios["To do list"].Name.Should().Be("To do list");
|
||||
server.Scenarios["To do list"].NextState.Should().Be("Cancel newspaper item added");
|
||||
server.Scenarios["To do list"].Started.Should().BeTrue();
|
||||
server.Scenarios["To do list"].Finished.Should().BeFalse();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Name.Should().Be("To do list");
|
||||
server.Scenarios.First(s => s.Name == "To do list").NextState.Should().Be("Cancel newspaper item added");
|
||||
server.Scenarios.First(s => s.Name == "To do list").Started.Should().BeTrue();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Finished.Should().BeFalse();
|
||||
|
||||
string getResponse2 = await client.GetStringAsync("/todo/items", cancelationToken);
|
||||
getResponse2.Should().Be("Buy milk;Cancel newspaper subscription");
|
||||
|
||||
server.Scenarios["To do list"].Name.Should().Be("To do list");
|
||||
server.Scenarios["To do list"].NextState.Should().BeNull();
|
||||
server.Scenarios["To do list"].Started.Should().BeTrue();
|
||||
server.Scenarios["To do list"].Finished.Should().BeTrue();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Name.Should().Be("To do list");
|
||||
server.Scenarios.First(s => s.Name == "To do list").NextState.Should().BeNull();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Started.Should().BeTrue();
|
||||
server.Scenarios.First(s => s.Name == "To do list").Finished.Should().BeTrue();
|
||||
|
||||
server.Stop();
|
||||
}
|
||||
@@ -372,14 +372,14 @@ public class StatefulBehaviorTests
|
||||
|
||||
// Act and Assert
|
||||
server.SetScenarioState(scenario, "Buy milk");
|
||||
server.Scenarios[scenario].Should().BeEquivalentTo(new { Name = scenario, NextState = "Buy milk" });
|
||||
server.Scenarios.First(s => s.Name == scenario).Should().BeEquivalentTo(new { Name = scenario, NextState = "Buy milk" });
|
||||
|
||||
var getResponse1 = await client.GetStringAsync("/todo/items", cancelationToken);
|
||||
getResponse1.Should().Be("Buy milk");
|
||||
|
||||
server.SetScenarioState(scenario, "Cancel newspaper");
|
||||
server.Scenarios[scenario].Name.Should().Be(scenario);
|
||||
server.Scenarios[scenario].Should().BeEquivalentTo(new { Name = scenario, NextState = "Cancel newspaper" });
|
||||
server.Scenarios.First(s => s.Name == scenario).Name.Should().Be(scenario);
|
||||
server.Scenarios.First(s => s.Name == scenario).Should().BeEquivalentTo(new { Name = scenario, NextState = "Cancel newspaper" });
|
||||
|
||||
var getResponse2 = await client.GetStringAsync("/todo/items", cancelationToken);
|
||||
getResponse2.Should().Be("Buy milk;Cancel newspaper subscription");
|
||||
|
||||
@@ -706,7 +706,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
|
||||
public async Task WithWebSocketProxy_Should_Proxy_Multiple_TextMessages()
|
||||
{
|
||||
// Arrange - Start target echo server
|
||||
using var exampleEchoServer = WireMockServer.Start(new WireMockServerSettings
|
||||
var exampleEchoServer = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new TestOutputHelperWireMockLogger(output),
|
||||
Urls = ["ws://localhost:0"]
|
||||
@@ -722,7 +722,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
|
||||
);
|
||||
|
||||
// Arrange - Start proxy server
|
||||
using var sut = WireMockServer.Start(new WireMockServerSettings
|
||||
var sut = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new TestOutputHelperWireMockLogger(output),
|
||||
Urls = ["ws://localhost:0"]
|
||||
@@ -755,13 +755,21 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
|
||||
}
|
||||
|
||||
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", _ct);
|
||||
|
||||
await Task.Delay(250, _ct);
|
||||
|
||||
sut.Stop();
|
||||
sut.Dispose();
|
||||
|
||||
exampleEchoServer.Stop();
|
||||
exampleEchoServer.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithWebSocketProxy_Should_Proxy_Binary_Messages()
|
||||
{
|
||||
// Arrange - Start target echo server
|
||||
using var exampleEchoServer = WireMockServer.Start(new WireMockServerSettings
|
||||
var exampleEchoServer = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new TestOutputHelperWireMockLogger(output),
|
||||
Urls = ["ws://localhost:0"]
|
||||
@@ -777,7 +785,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
|
||||
);
|
||||
|
||||
// Arrange - Start proxy server
|
||||
using var sut = WireMockServer.Start(new WireMockServerSettings
|
||||
var sut = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new TestOutputHelperWireMockLogger(output),
|
||||
Urls = ["ws://localhost:0"]
|
||||
@@ -811,6 +819,14 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
|
||||
receivedData.Should().BeEquivalentTo(testData, "binary data should be proxied and echoed back");
|
||||
|
||||
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", _ct);
|
||||
|
||||
await Task.Delay(250, _ct);
|
||||
|
||||
sut.Stop();
|
||||
sut.Dispose();
|
||||
|
||||
exampleEchoServer.Stop();
|
||||
exampleEchoServer.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -431,8 +431,13 @@ public partial class WireMockServerTests(ITestOutputHelper testOutputHelper)
|
||||
using var server = WireMockServer.Start();
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath(path).UsingHead())
|
||||
.RespondWith(Response.Create().WithHeader(HttpKnownHeaderNames.ContentLength, length));
|
||||
.WhenRequest(r => r
|
||||
.WithPath(path)
|
||||
.UsingHead()
|
||||
)
|
||||
.ThenRespondWith(r => r
|
||||
.WithHeader(HttpKnownHeaderNames.ContentLength, length)
|
||||
);
|
||||
|
||||
// Act
|
||||
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, path);
|
||||
@@ -442,6 +447,45 @@ public partial class WireMockServerTests(ITestOutputHelper testOutputHelper)
|
||||
response.Content.Headers.GetValues(HttpKnownHeaderNames.ContentLength).Should().Contain(length);
|
||||
}
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
[Theory]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("GET")]
|
||||
[InlineData("OPTIONS")]
|
||||
[InlineData("PATCH")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("TRACE")]
|
||||
public async Task WireMockServer_Should_LogAndThrowExceptionWhenInvalidContentLength(string method)
|
||||
{
|
||||
// Assign
|
||||
const string length = "42";
|
||||
var path = $"/InvalidContentLength_{Guid.NewGuid()}";
|
||||
using var server = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Logger = new TestOutputHelperWireMockLogger(testOutputHelper)
|
||||
});
|
||||
|
||||
server
|
||||
.WhenRequest(r => r
|
||||
.WithPath(path)
|
||||
.UsingAnyMethod()
|
||||
)
|
||||
.ThenRespondWith(r => r
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithHeader(HttpKnownHeaderNames.ContentLength, length)
|
||||
);
|
||||
|
||||
// Act
|
||||
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Parse(method), path);
|
||||
var response = await server.CreateClient().SendAsync(httpRequestMessage, _ct);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
|
||||
testOutputHelper.Output.Should().Contain($"Response Content-Length mismatch: too few bytes written (0 of {length}).");
|
||||
}
|
||||
#endif
|
||||
|
||||
[Theory]
|
||||
[InlineData("TRACE")]
|
||||
[InlineData("GET")]
|
||||
|
||||
Reference in New Issue
Block a user