Compare commits

...

23 Commits

Author SHA1 Message Date
Stef Heyenrath
5f6f5663e1 mm 2026-01-02 21:37:07 +01:00
Stef Heyenrath
803e983645 Merge branch 'master' into MappingSerializer 2026-01-02 21:32:14 +01:00
Stef Heyenrath
c88e7378a7 1.22.0 2026-01-02 21:30:59 +01:00
Vadim Hatsura
b090296559 chore(testcontainers): bump up Testcontainers to version 4.10.0 (#1412) 2026-01-02 21:25:28 +01:00
Stef Heyenrath
8f81fb0d96 test 2026-01-02 21:23:12 +01:00
Stef Heyenrath
e5afd69f7c 1.21.0 2025-12-25 15:00:54 +01:00
Stef Heyenrath
b4af082586 Merge branch 'master' into MappingSerializer 2025-12-25 13:57:28 +01:00
Stef Heyenrath
f38133d7a4 Fix readyness-check for Testcontainers (#1408)
* Add XUnit Logging to TestcontainersTests

* .
2025-12-25 13:56:29 +01:00
Stef Heyenrath
3011509c51 Merge branch 'master' into MappingSerializer 2025-12-24 16:59:57 +01:00
Stef Heyenrath
597c95000e vmImage: 'windows-2025' (#1407) 2025-12-24 16:59:02 +01:00
Stef Heyenrath
ad150280fd Merge branch 'master' into MappingSerializer 2025-12-24 15:30:00 +01:00
Stef Heyenrath
4617b99c30 [Collection("Grpc")] 2025-12-24 12:32:56 +01:00
Stef Heyenrath
6bb506e1f8 Merge branch 'master' into MappingSerializer 2025-12-24 12:18:04 +01:00
Stef Heyenrath
ffd4d89946 Re-enable TestcontainersTestsGrpc (#1406)
* Re-enable TestcontainersTestsGrpc

* //[Collection("Grpc")]
2025-12-24 12:16:56 +01:00
Stef Heyenrath
b40fce694d Merge branch 'master' into MappingSerializer 2025-12-21 09:50:09 +01:00
Stef Heyenrath
79f758fd4d Merge branch 'master' into MappingSerializer 2025-12-20 13:44:43 +01:00
Stef Heyenrath
aafe2339e4 Merge branch 'master' into MappingSerializer 2025-12-20 12:09:09 +01:00
Stef Heyenrath
708af55051 0.8.0 2025-12-20 10:32:26 +01:00
Stef Heyenrath
e6e4138466 Merge branch 'master' into MappingSerializer 2025-12-20 08:16:30 +01:00
Stef Heyenrath
6cc8c04486 Merge branch 'master' into MappingSerializer 2025-12-19 17:55:16 +01:00
Stef Heyenrath
dd4770a78b . 2025-12-18 18:15:37 +01:00
Stef Heyenrath
de15e0d48e json 2025-12-16 23:36:19 +01:00
Stef Heyenrath
f6452d1dce MappingSerializer 2025-12-16 22:37:26 +01:00
30 changed files with 634 additions and 140 deletions

View File

@@ -1,3 +1,10 @@
# 1.22.0 (02 January 2026)
- [#1412](https://github.com/wiremock/WireMock.Net/pull/1412) - chore(testcontainers): bump up Testcontainers to version 4.10.0 [feature] contributed by [vhatsura](https://github.com/vhatsura)
- [#1411](https://github.com/wiremock/WireMock.Net/issues/1411) - WireMock.Net.Testcontainers isn't compatible with Testcontainers 4.10.0 [bug]
# 1.21.0 (25 December 2025)
- [#1408](https://github.com/wiremock/WireMock.Net/pull/1408) - Fix readyness-check for Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
# 1.20.0 (24 December 2025) # 1.20.0 (24 December 2025)
- [#1399](https://github.com/wiremock/WireMock.Net/pull/1399) - Upgrade RamlToOpenApiConverter and YamlDotNet [feature] contributed by [StefH](https://github.com/StefH) - [#1399](https://github.com/wiremock/WireMock.Net/pull/1399) - Upgrade RamlToOpenApiConverter and YamlDotNet [feature] contributed by [StefH](https://github.com/StefH)
- [#1400](https://github.com/wiremock/WireMock.Net/pull/1400) - Add WireMock.Net.NUnit project [feature] contributed by [StefH](https://github.com/StefH) - [#1400](https://github.com/wiremock/WireMock.Net/pull/1400) - Add WireMock.Net.NUnit project [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.20.0</VersionPrefix> <VersionPrefix>1.22.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.20.0 SET version=1.22.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 CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -1,8 +1,5 @@
# 1.20.0 (24 December 2025) # 1.22.0 (02 January 2026)
- #1399 Upgrade RamlToOpenApiConverter and YamlDotNet [feature] - #1412 chore(testcontainers): bump up Testcontainers to version 4.10.0 [feature]
- #1400 Add WireMock.Net.NUnit project [feature] - #1411 WireMock.Net.Testcontainers isn't compatible with Testcontainers 4.10.0 [bug]
- #1405 Fix Testcontainers AddProtoDefinition [bug]
- #1398 Upgrade YamlDotNet dependency [feature]
- #1404 An exception occurs when adding multiple proto definitions in the TestContainer. [bug]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -100,7 +100,7 @@ jobs:
- job: Windows_Build_Test - job: Windows_Build_Test
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
@@ -141,7 +141,7 @@ jobs:
dependsOn: Windows_Build_Test dependsOn: Windows_Build_Test
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
steps: steps:
- script: | - script: |

View File

@@ -1,5 +1,5 @@
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
variables: variables:
Prerelease: '' Prerelease: ''

View File

@@ -32,7 +32,7 @@ public class ResponseModel
public object? BodyAsJson { get; set; } public object? BodyAsJson { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. /// Gets or sets a value indicating whether the Json Body String needs to be indented.
/// </summary> /// </summary>
public bool? BodyAsJsonIndented { get; set; } public bool? BodyAsJsonIndented { get; set; }

View File

@@ -37,7 +37,7 @@ public interface IBodyData
object? BodyAsJson { get; set; } object? BodyAsJson { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. /// Gets or sets a value indicating whether the Json Body String needs to be indented.
/// </summary> /// </summary>
bool? BodyAsJsonIndented { get; set; } bool? BodyAsJsonIndented { get; set; }

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description> <Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
<Authors>Gennadii Saltyshchak</Authors> <Authors>Gennadii Saltyshchak</Authors>
@@ -25,7 +25,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.7.0" /> <PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
@@ -164,8 +163,8 @@ public class MappingBuilder : IMappingBuilder
} }
} }
private static string ToJson(object value) private string ToJson(object value)
{ {
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault); return _settings.DefaultJsonSerializer.Serialize(value, JsonSerializationConstants.JsonConverterOptionsDefault);
} }
} }

View File

@@ -0,0 +1,49 @@
// Copyright © WireMock.Net
using System;
using JsonConverter.Abstractions;
using Newtonsoft.Json.Linq;
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
using System.Text.Json;
#endif
namespace WireMock.Serialization;
internal class MappingSerializer(IJsonConverter jsonConverter)
{
internal T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value)!);
}
internal static T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
if (value is JObject jObject)
{
var singleResult = jObject.ToObject<T>();
return [singleResult!];
}
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
if (value is JsonElement jElement)
{
if (jElement.ValueKind == JsonValueKind.Array)
{
return jElement.Deserialize<T[]>()!;
}
if (jElement.ValueKind == JsonValueKind.Object)
{
var singleResult = jElement.Deserialize<T>();
return [singleResult!];
}
}
#endif
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
}
}

View File

@@ -2,7 +2,8 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Settings; using WireMock.Settings;
@@ -12,12 +13,15 @@ internal class MappingToFileSaver
{ {
private readonly WireMockServerSettings _settings; private readonly WireMockServerSettings _settings;
private readonly MappingConverter _mappingConverter; private readonly MappingConverter _mappingConverter;
private readonly IJsonConverter _jsonConverter;
private readonly MappingFileNameSanitizer _fileNameSanitizer; private readonly MappingFileNameSanitizer _fileNameSanitizer;
public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter)
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_mappingConverter = Guard.NotNull(mappingConverter); _mappingConverter = Guard.NotNull(mappingConverter);
_jsonConverter = settings.DefaultJsonSerializer ?? new NewtonsoftJsonConverter();
_fileNameSanitizer = new MappingFileNameSanitizer(settings); _fileNameSanitizer = new MappingFileNameSanitizer(settings);
} }
@@ -56,6 +60,8 @@ internal class MappingToFileSaver
{ {
_settings.Logger.Info("Saving Mapping file {0}", path); _settings.Logger.Info("Saving Mapping file {0}", path);
_settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault)); var json = _jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsDefault);
_settings.FileSystemHandler.WriteMappingFile(path, json);
} }
} }

View File

@@ -7,7 +7,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using JsonConverter.Abstractions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
@@ -236,7 +236,7 @@ public partial class WireMockServer
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{ {
var mappingModels = DeserializeJsonToArray<MappingModel>(value); var mappingModels = _mappingSerializer.DeserializeJsonToArray<MappingModel>(value);
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))
{ {
ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path); ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path);
@@ -826,25 +826,31 @@ public partial class WireMockServer
} }
} }
private static Encoding? ToEncoding(EncodingModel? encodingModel) private ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{ {
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; var jsonOptions = new JsonConverterOptions
} {
WriteIndented = true,
IgnoreNullValues = !keepNullValues
};
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{
return new ResponseMessage return new ResponseMessage
{ {
BodyData = new BodyData BodyData = new BodyData
{ {
DetectedBodyType = BodyType.String, DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) BodyAsString = _settings.DefaultJsonSerializer.Serialize(result!, jsonOptions)
}, },
StatusCode = statusCode ?? (int)HttpStatusCode.OK, StatusCode = statusCode ?? (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } } Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
}; };
} }
private static Encoding? ToEncoding(EncodingModel? encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToResponseMessage(string text) private static ResponseMessage ToResponseMessage(string text)
{ {
return new ResponseMessage return new ResponseMessage
@@ -859,6 +865,18 @@ public partial class WireMockServer
}; };
} }
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
return MappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
}
throw new NotSupportedException();
}
private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new() private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
{ {
switch (requestMessage.BodyData?.DetectedBodyType) switch (requestMessage.BodyData?.DetectedBodyType)
@@ -874,32 +892,4 @@ public partial class WireMockServer
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
return DeserializeObjectToArray<T>(bodyAsJson);
}
throw new NotSupportedException();
}
private static T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
}
private static T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult! };
}
} }

View File

@@ -31,7 +31,7 @@ public partial class WireMockServer
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{ {
var mappings = DeserializeJsonToArray<OrgMapping>(value); var mappings = _mappingSerializer.DeserializeJsonToArray<OrgMapping>(value);
foreach (var mapping in mappings) foreach (var mapping in mappings)
{ {
if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))

View File

@@ -11,6 +11,7 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using AnyOfTypes; using AnyOfTypes;
using JetBrains.Annotations; using JetBrains.Annotations;
using JsonConverter.Newtonsoft.Json;
using Newtonsoft.Json; using Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
@@ -47,6 +48,7 @@ public partial class WireMockServer : IWireMockServer
private readonly MappingBuilder _mappingBuilder; private readonly MappingBuilder _mappingBuilder;
private readonly IGuidUtils _guidUtils = new GuidUtils(); private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
private readonly MappingSerializer _mappingSerializer;
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
@@ -357,6 +359,8 @@ public partial class WireMockServer : IWireMockServer
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_mappingSerializer = new MappingSerializer(settings.DefaultJsonSerializer ?? new NewtonsoftJsonConverter());
// Set default values if not provided // Set default values if not provided
_settings.Logger = settings.Logger ?? new WireMockNullLogger(); _settings.Logger = settings.Logger ?? new WireMockNullLogger();
_settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
@@ -639,7 +643,7 @@ public partial class WireMockServer : IWireMockServer
[PublicAPI] [PublicAPI]
public IWireMockServer WithMapping(string mappings) public IWireMockServer WithMapping(string mappings)
{ {
var mappingModels = DeserializeJsonToArray<MappingModel>(mappings); var mappingModels = _mappingSerializer.DeserializeJsonToArray<MappingModel>(mappings);
foreach (var mappingModel in mappingModels) foreach (var mappingModel in mappingModels)
{ {
ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid());

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>Minimal version from the lightweight Http Mocking Server for .NET</Description> <Description>Minimal version from the lightweight Http Mocking Server for .NET</Description>
<AssemblyTitle>WireMock.Net.Minimal</AssemblyTitle> <AssemblyTitle>WireMock.Net.Minimal</AssemblyTitle>
@@ -57,8 +57,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JsonConverter.Abstractions" Version="0.7.0" /> <!--<PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" />-->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <!--<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />-->
<PackageReference Include="NJsonSchema.Extensions" Version="0.1.0" /> <PackageReference Include="NJsonSchema.Extensions" Version="0.1.0" />
<PackageReference Include="NSwag.Core" Version="13.16.1" /> <PackageReference Include="NSwag.Core" Version="13.16.1" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" /> <PackageReference Include="SimMetrics.Net" Version="1.0.5" />
@@ -118,11 +118,13 @@
<!-- https://github.com/wiremock/WireMock.Net/issues/507 --> <!-- https://github.com/wiremock/WireMock.Net/issues/507 -->
<PackageReference Include="Microsoft.AspNetCore.Server.IIS" Version="2.2.6" /> <PackageReference Include="Microsoft.AspNetCore.Server.IIS" Version="2.2.6" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Scriban.Signed" Version="5.5.0" /> <PackageReference Include="Scriban.Signed" Version="5.5.0" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">

View File

@@ -33,7 +33,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.7.0" /> <PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="RestEase" Version="1.6.4" /> <PackageReference Include="RestEase" Version="1.6.4" />
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using JsonConverter.Abstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@@ -7,24 +8,30 @@ namespace WireMock.Serialization;
internal static class JsonSerializationConstants internal static class JsonSerializationConstants
{ {
public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new() internal static readonly JsonConverterOptions JsonConverterOptionsDefault = new()
{ {
Formatting = Formatting.Indented, WriteIndented = true,
NullValueHandling = NullValueHandling.Ignore IgnoreNullValues = true
}; };
public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new() //internal static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new()
//{
// Formatting = Formatting.Indented,
// NullValueHandling = NullValueHandling.Ignore
//};
internal static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new()
{ {
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Include NullValueHandling = NullValueHandling.Include
}; };
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new() internal static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new()
{ {
DateParseHandling = DateParseHandling.None DateParseHandling = DateParseHandling.None
}; };
public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new() internal static readonly JsonSerializerSettings JsonSerializerSettingsPact = new()
{ {
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,

View File

@@ -14,6 +14,8 @@ using WireMock.RegularExpressions;
using WireMock.Types; using WireMock.Types;
using System.Globalization; using System.Globalization;
using WireMock.Models; using WireMock.Models;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
#if USE_ASPNETCORE #if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -338,4 +340,14 @@ public class WireMockServerSettings
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public HandlebarsSettings? HandlebarsSettings { get; set; } public HandlebarsSettings? HandlebarsSettings { get; set; }
/// <summary>
/// Gets or sets the default JSON converter used for serialization.
/// </summary>
/// <remarks>
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
/// Default is <see cref="NewtonsoftJsonConverter"/>.
/// </remarks>
[PublicAPI]
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
} }

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>Shared interfaces, models, enumerations and types.</Description> <Description>Shared interfaces, models, enumerations and types.</Description>
<Authors>Stef Heyenrath</Authors> <Authors>Stef Heyenrath</Authors>
@@ -48,9 +48,10 @@
</PackageReference> </PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="AnyOf" Version="0.4.0" /> <PackageReference Include="AnyOf" Version="0.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <!--<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />-->
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" />
<PackageReference Include="JsonConverter.Abstractions" Version="0.7.0" /> <!--<PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" />-->
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -39,7 +39,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Testcontainers" Version="4.8.0" /> <PackageReference Include="Testcontainers" Version="4.10.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers;
/// <summary> /// <summary>
/// A container for running WireMock in a docker environment. /// A container for running WireMock in a docker environment.
/// </summary> /// </summary>
public sealed class WireMockContainer : DockerContainer /// <remarks>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </remarks>
/// <param name="configuration">The container configuration.</param>
public sealed class WireMockContainer(WireMockConfiguration configuration) : DockerContainer(configuration)
{ {
private const int EnhancedFileSystemWatcherTimeoutMs = 2000; private const int EnhancedFileSystemWatcherTimeoutMs = 2000;
internal const int ContainerPort = 80; internal const int ContainerPort = 80;
private readonly WireMockConfiguration _configuration; private readonly WireMockConfiguration _configuration = Guard.NotNull(configuration);
private IWireMockAdminApi? _adminApi; private IWireMockAdminApi? _adminApi;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private IDictionary<int, Uri>? _publicUris; private IDictionary<int, Uri>? _publicUris;
/// <summary>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WireMockContainer(WireMockConfiguration configuration) : base(configuration)
{
_configuration = Guard.NotNull(configuration);
Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs);
}
/// <summary> /// <summary>
/// Gets the public Url. /// Gets the public Url.
/// </summary> /// </summary>
@@ -157,14 +150,28 @@ public sealed class WireMockContainer : DockerContainer
try try
{ {
var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken); var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
Logger.LogInformation("ReloadStaticMappings result: {Result}", result); Logger.LogInformation("WireMock.Net -> ReloadStaticMappings result: {Result}", result);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); Logger.LogWarning(ex, "WireMock.Net -> Error calling /__admin/mappings/reloadStaticMappings");
} }
} }
/// <summary>
/// Performs additional actions after the container is ready.
/// </summary>
public Task CallAdditionalActionsAfterReadyAsync()
{
Logger.LogInformation("WireMock.Net -> Calling additional actions.");
_adminApi = CreateWireMockAdminClient();
RegisterEnhancedFileSystemWatcher();
return AddProtoDefinitionsAsync();
}
/// <inheritdoc /> /// <inheritdoc />
protected override ValueTask DisposeAsyncCore() protected override ValueTask DisposeAsyncCore()
{ {
@@ -197,15 +204,6 @@ public sealed class WireMockContainer : DockerContainer
} }
} }
private async Task WireMockContainerStartedAsync(object sender, EventArgs e)
{
_adminApi = CreateWireMockAdminClient();
RegisterEnhancedFileSystemWatcher();
await CallAdditionalActionsAfterStartedAsync();
}
private void RegisterEnhancedFileSystemWatcher() private void RegisterEnhancedFileSystemWatcher()
{ {
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath)) if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
@@ -223,22 +221,22 @@ public sealed class WireMockContainer : DockerContainer
_enhancedFileSystemWatcher.EnableRaisingEvents = true; _enhancedFileSystemWatcher.EnableRaisingEvents = true;
} }
private async Task CallAdditionalActionsAfterStartedAsync() private async Task AddProtoDefinitionsAsync()
{ {
foreach (var kvp in _configuration.ProtoDefinitions) foreach (var kvp in _configuration.ProtoDefinitions)
{ {
Logger.LogInformation("Adding ProtoDefinition {Id}", kvp.Key); Logger.LogInformation("WireMock.Net -> Adding ProtoDefinition '{Id}'", kvp.Key);
foreach (var protoDefinition in kvp.Value) foreach (var protoDefinition in kvp.Value)
{ {
try try
{ {
var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition); var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
Logger.LogInformation("AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result); Logger.LogInformation("WireMock.Net -> AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key); Logger.LogWarning(ex, "WireMock.Net -> Error adding ProtoDefinition '{Id}'.", kvp.Key);
} }
} }
} }
@@ -255,17 +253,17 @@ public sealed class WireMockContainer : DockerContainer
try try
{ {
await ReloadStaticMappingsAsync(args.FullPath); await ReloadStaticMappingsAsync(args.FullPath);
Logger.LogInformation("ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath); Logger.LogInformation("WireMock.Net -> ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error reloading static mappings from '{FullPath}'.", args.FullPath); Logger.LogWarning(ex, "WireMock.Net -> Error reloading static mappings from '{FullPath}'.", args.FullPath);
} }
} }
private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default) private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default)
{ {
Logger.LogInformation("MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path); Logger.LogInformation("WireMock.Net -> MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
await ReloadStaticMappingsAsync(cancellationToken); await ReloadStaticMappingsAsync(cancellationToken);
} }

View File

@@ -253,8 +253,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
builder.Validate(); builder.Validate();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer(); var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
builder builder = builder
.WithWaitStrategy(waitForContainerOS .WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
.UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy .UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy
.ForPort(WireMockContainer.ContainerPort) .ForPort(WireMockContainer.ContainerPort)
.WithMethod(HttpMethod.Get) .WithMethod(HttpMethod.Get)
@@ -267,6 +268,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
return content?.Contains("Healthy") == true; return content?.Contains("Healthy") == true;
}) })
) )
.AddCustomWaitStrategy(new WireMockWaitStrategy())
); );
return new WireMockContainer(builder.DockerResourceConfiguration); return new WireMockContainer(builder.DockerResourceConfiguration);
@@ -277,13 +279,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
{ {
var builder = base.Init(); var builder = base.Init();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
return builder return builder
.WithPortBinding(WireMockContainer.ContainerPort, true) .WithPortBinding(WireMockContainer.ContainerPort, true)
.WithCommand($"--WireMockLogger {DefaultLogger}") .WithCommand($"--WireMockLogger {DefaultLogger}");
.WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -0,0 +1,23 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
namespace WireMock.Net.Testcontainers;
internal class WireMockWaitStrategy : IWaitUntil
{
public async Task<bool> UntilAsync(IContainer container)
{
if (container is not WireMockContainer wireMockContainer)
{
throw new InvalidOperationException("The passed container is not a WireMockContainer.");
}
await wireMockContainer.CallAdditionalActionsAfterReadyAsync();
return true;
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using JsonConverter.Newtonsoft.Json;
using Moq; using Moq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NFluent; using NFluent;
@@ -316,12 +317,11 @@ public class ResponseWithBodyTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request1, _settings).ConfigureAwait(false); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request1, _settings).ConfigureAwait(false);
Check.That(response.Message.StatusCode).IsEqualTo(200); Check.That(response.Message.StatusCode).IsEqualTo(200);
Check.That(response.Message.BodyData.BodyAsString).Contains("File deleted."); Check.That(response.Message.BodyData?.BodyAsString).Contains("File deleted.");
} }
#if !(NET451 || NET452)
[Fact] [Fact]
public async Task Response_ProvideResponse_WithBody_IJsonConverter_SystemTextJson() public async Task Response_ProvideResponse_WithBody_NewtonsoftJsonConverter()
{ {
// Arrange // Arrange
var requestBody = new BodyData var requestBody = new BodyData
@@ -331,13 +331,34 @@ public class ResponseWithBodyTests
}; };
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody); var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody);
var responseBuilder = Response.Create().WithBody(new { foo = "bar", n = 42 }, new JsonConverter.System.Text.Json.SystemTextJsonConverter()); var responseBuilder = Response.Create().WithBody(new { foo = "< > & ' 😀 👍 ❤️", n = 42 }, new NewtonsoftJsonConverter());
// Act // Act
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false);
// Assert // Assert
response.Message.BodyData!.BodyAsString.Should().Be(@"{""foo"":""bar"",""n"":42}"); response.Message.BodyData!.BodyAsString.Should().Be("""{"foo":"< > & ' 😀 👍 ","n":42}""");
}
#if !(NET451 || NET452 || NET461)
[Fact]
public async Task Response_ProvideResponse_WithBody_SystemTextJsonConverter()
{
// Arrange
var requestBody = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = "abc"
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody);
var responseBuilder = Response.Create().WithBody(new { foo = "< > & ' 😀 👍 ❤️", n = 42 }, new JsonConverter.System.Text.Json.SystemTextJsonConverter());
// Act
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false);
// Assert
response.Message.BodyData!.BodyAsString.Should().Be("""{"foo":"\u003C \u003E \u0026 \u0027 \uD83D\uDE00 \uD83D\uDC4D \u2764\uFE0F","n":42}""");
} }
#endif #endif
} }

View File

@@ -0,0 +1,326 @@
// Copyright © WireMock.Net
using System;
using FluentAssertions;
using JsonConverter.Newtonsoft.Json;
using WireMock.Admin.Mappings;
using WireMock.Serialization;
using Xunit;
#if NET8_0_OR_GREATER
using JsonConverter.System.Text.Json;
#endif
namespace WireMock.Net.Tests.Serialization;
public class MappingSerializerTests
{
private const string SingleMappingJson =
"""
{
"Guid": "12345678-1234-1234-1234-123456789012",
"Priority": 1,
"Request": {
"Path": "/test"
},
"Response": {
"StatusCode": 200
}
}
""";
private const string ArrayMappingJson =
"""
[
{
"Guid": "12345678-1234-1234-1234-123456789012",
"Priority": 1,
"Request": {
"Path": "/test1"
},
"Response": {
"StatusCode": 200
}
},
{
"Guid": "87654321-4321-4321-4321-210987654321",
"Priority": 2,
"Request": {
"Path": "/test2"
},
"Response": {
"StatusCode": 404
}
}
]
""";
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_SingleObject_ShouldReturnArray()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(SingleMappingJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(1);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Priority.Should().Be(1);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_Array_ShouldReturnArray()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(ArrayMappingJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(2);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Priority.Should().Be(1);
result[1].Guid.Should().Be(Guid.Parse("87654321-4321-4321-4321-210987654321"));
result[1].Priority.Should().Be(2);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_EmptyArray_ShouldReturnEmptyArray()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var emptyArrayJson = "[]";
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(emptyArrayJson);
// Assert
result.Should().NotBeNull();
result.Should().BeEmpty();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_InvalidJson_ShouldThrowException()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var invalidJson = "not valid json";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(invalidJson);
// Assert
act.Should().Throw<Exception>();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_ComplexMapping_ShouldDeserializeCorrectly()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var complexJson =
"""
{
"Guid": "12345678-1234-1234-1234-123456789012",
"Title": "Test Mapping",
"Description": "A test mapping",
"Priority": 10,
"Request": {
"Path": "/api/test",
"Methods": ["GET", "POST"]
},
"Response": {
"StatusCode": 201,
"Body": "Test Response"
}
}
""";
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(complexJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(1);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Title.Should().Be("Test Mapping");
result[0].Description.Should().Be("A test mapping");
result[0].Priority.Should().Be(10);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_NullValue_ShouldThrowException()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var nullJson = "null";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(nullJson);
// Assert
act.Should().Throw<Exception>();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_PrimitiveValue_ShouldThrowInvalidOperationException()
{
// Arrange
var jsonConverter = new NewtonsoftJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var primitiveJson = "\"string value\"";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(primitiveJson);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("Cannot deserialize the provided value to an array or object.");
}
#if NET8_0_OR_GREATER
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_SingleObject_ShouldReturnArray()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(SingleMappingJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(1);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Priority.Should().Be(1);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_Array_ShouldReturnArray()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(ArrayMappingJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(2);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Priority.Should().Be(1);
result[1].Guid.Should().Be(Guid.Parse("87654321-4321-4321-4321-210987654321"));
result[1].Priority.Should().Be(2);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_EmptyArray_ShouldReturnEmptyArray()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var emptyArrayJson = "[]";
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(emptyArrayJson);
// Assert
result.Should().NotBeNull();
result.Should().BeEmpty();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_InvalidJson_ShouldThrowException()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var invalidJson = "not valid json";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(invalidJson);
// Assert
act.Should().Throw<Exception>();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_ComplexMapping_ShouldDeserializeCorrectly()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var complexJson =
"""
{
"Guid": "12345678-1234-1234-1234-123456789012",
"Title": "Test Mapping",
"Description": "A test mapping",
"Priority": 10,
"Request": {
"Path": "/api/test",
"Methods": ["GET", "POST"]
},
"Response": {
"StatusCode": 201,
"Body": "Test Response"
}
}
""";
// Act
var result = serializer.DeserializeJsonToArray<MappingModel>(complexJson);
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(1);
result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012"));
result[0].Title.Should().Be("Test Mapping");
result[0].Description.Should().Be("A test mapping");
result[0].Priority.Should().Be(10);
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_NullValue_ShouldThrowException()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var nullJson = "null";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(nullJson);
// Assert
act.Should().Throw<Exception>();
}
[Fact]
public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_PrimitiveValue_ShouldThrowInvalidOperationException()
{
// Arrange
var jsonConverter = new SystemTextJsonConverter();
var serializer = new MappingSerializer(jsonConverter);
var primitiveJson = "\"string value\"";
// Act
Action act = () => serializer.DeserializeJsonToArray<MappingModel>(primitiveJson);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("Cannot deserialize the provided value to an array or object.");
}
#endif
}

View File

@@ -7,6 +7,8 @@ using System.Threading.Tasks;
using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Builders;
using FluentAssertions; using FluentAssertions;
using FluentAssertions.Execution; using FluentAssertions.Execution;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Net.Testcontainers; using WireMock.Net.Testcontainers;
using WireMock.Net.Testcontainers.Utils; using WireMock.Net.Testcontainers.Utils;
using WireMock.Net.Tests.Facts; using WireMock.Net.Tests.Facts;
@@ -17,6 +19,12 @@ namespace WireMock.Net.Tests.Testcontainers;
public class TestcontainersTests(ITestOutputHelper testOutputHelper) public class TestcontainersTests(ITestOutputHelper testOutputHelper)
{ {
private readonly ILogger _logger = new XUnitLogger(testOutputHelper, new LoggerExternalScopeProvider(), nameof(TestcontainersTests), new XUnitLoggerOptions
{
IncludeCategory = true,
TimestampFormat = "yyy-MM-dd HH:mm:ss.fff"
});
[Fact] [Fact]
public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync() public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync()
{ {
@@ -24,6 +32,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}"; var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}"; var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword) .WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithAutoRemove(true) .WithAutoRemove(true)
.WithCleanUp(true) .WithCleanUp(true)
@@ -43,6 +52,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
.Build(); .Build();
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithNetwork(dummyNetwork) .WithNetwork(dummyNetwork)
.WithWatchStaticMappings(true) .WithWatchStaticMappings(true)
.Build(); .Build();
@@ -58,6 +68,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}"; var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}"; var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder() var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword); .WithAdminUserNameAndPassword(adminUsername, adminPassword);
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value; var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
@@ -83,6 +94,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}"; var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}"; var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder() var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword); .WithAdminUserNameAndPassword(adminUsername, adminPassword);
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value; var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;

View File

@@ -11,6 +11,8 @@ using FluentAssertions;
using FluentAssertions.Execution; using FluentAssertions.Execution;
using Greet; using Greet;
using Grpc.Net.Client; using Grpc.Net.Client;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Constants; using WireMock.Constants;
using WireMock.Net.Testcontainers; using WireMock.Net.Testcontainers;
using WireMock.Util; using WireMock.Util;
@@ -22,7 +24,13 @@ namespace WireMock.Net.Tests.Testcontainers;
[Collection("Grpc")] [Collection("Grpc")]
public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
{ {
[Fact(Skip = "TODO")] private readonly ILogger _logger = new XUnitLogger(testOutputHelper, new LoggerExternalScopeProvider(), nameof(TestcontainersTestsGrpc), new XUnitLoggerOptions
{
IncludeCategory = true,
TimestampFormat = "yyy-MM-dd HH:mm:ss.fff"
});
[Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1() public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1()
{ {
// Arrange // Arrange
@@ -32,6 +40,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
// Act // Act
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword) .WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithCommand("--UseHttp2") .WithCommand("--UseHttp2")
.WithCommand("--Urls", $"http://*:80 grpc://*:{port}") .WithCommand("--Urls", $"http://*:80 grpc://*:{port}")
@@ -78,7 +87,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
} }
} }
[Fact(Skip = "TODO")] [Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2() public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2()
{ {
// Arrange // Arrange
@@ -88,6 +97,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
// Act // Act
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword) .WithAdminUserNameAndPassword(adminUsername, adminPassword)
.AddUrl($"http://*:{ports[0]}") .AddUrl($"http://*:{ports[0]}")
.AddUrl($"grpc://*:{ports[1]}") .AddUrl($"grpc://*:{ports[1]}")
@@ -131,7 +141,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
} }
} }
[Fact(Skip = "TODO")] [Fact]
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionFromJson_UsingGrpcGeneratedClient() public async Task WireMockContainer_Build_Grpc_ProtoDefinitionFromJson_UsingGrpcGeneratedClient()
{ {
var wireMockContainer = await Given_WireMockContainerIsStartedForHttpAndGrpcAsync(); var wireMockContainer = await Given_WireMockContainerIsStartedForHttpAndGrpcAsync();
@@ -145,7 +155,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
await StopAsync(wireMockContainer); await StopAsync(wireMockContainer);
} }
[Fact(Skip = "TODO")] [Fact]
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient() public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient()
{ {
var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync(); var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync();
@@ -159,7 +169,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
await StopAsync(wireMockContainer); await StopAsync(wireMockContainer);
} }
[Fact(Skip = "TODO")] [Fact]
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient_AndWithWatchStaticMappings() public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient_AndWithWatchStaticMappings()
{ {
var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync(); var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync();
@@ -222,10 +232,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
} }
} }
private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync() private async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
{ {
var port = PortUtils.FindFreeTcpPort(); var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}") .AddUrl($"grpc://*:{port}")
.Build(); .Build();
@@ -234,10 +245,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
return wireMockContainer; return wireMockContainer;
} }
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync() private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
{ {
var port = PortUtils.FindFreeTcpPort(); var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}") .AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto")) .AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.Build(); .Build();
@@ -247,10 +259,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
return wireMockContainer; return wireMockContainer;
} }
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync() private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
{ {
var port = PortUtils.FindFreeTcpPort(); var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder() var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}") .AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto")) .AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.WithMappings(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings")) .WithMappings(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings"))

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Authors>Stef Heyenrath</Authors> <Authors>Stef Heyenrath</Authors>
@@ -107,7 +107,7 @@
<ItemGroup Condition="'$(TargetFramework)' != 'net452'"> <ItemGroup Condition="'$(TargetFramework)' != 'net452'">
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" /> <PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.7.0" /> <!--<PackageReference Include="JsonConverter.System.Text.Json" Version="0.8.0" />-->
<PackageReference Include="Google.Protobuf" Version="3.25.1" /> <PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0"> <PackageReference Include="Grpc.Tools" Version="2.60.0">
@@ -121,6 +121,7 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<ProjectReference Include="..\..\src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj" />
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.21" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
@@ -34,11 +33,10 @@ public partial class WireMockServerTests
Request.Create() Request.Create()
.WithPath("/a") .WithPath("/a")
.WithBody( .WithBody(
new IMatcher[] [
{
new JmesPathMatcher("requestId == '1'"), new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'") new JmesPathMatcher("value == 'A'")
}, ],
MatchOperator.And MatchOperator.And
) )
.UsingPost() .UsingPost()
@@ -49,11 +47,10 @@ public partial class WireMockServerTests
Request.Create() Request.Create()
.WithPath("/a") .WithPath("/a")
.WithBody( .WithBody(
new IMatcher[] [
{
new JmesPathMatcher("requestId == '2'"), new JmesPathMatcher("requestId == '2'"),
new JmesPathMatcher("value == 'A'") new JmesPathMatcher("value == 'A'")
}, ],
MatchOperator.And MatchOperator.And
) )
.UsingPost() .UsingPost()
@@ -81,12 +78,11 @@ public partial class WireMockServerTests
Request.Create() Request.Create()
.WithPath("/a") .WithPath("/a")
.WithBody( .WithBody(
new IMatcher[] [
{
new JmesPathMatcher("extra == 'X'"), new JmesPathMatcher("extra == 'X'"),
new JmesPathMatcher("requestId == '1'"), new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'") new JmesPathMatcher("value == 'A'")
}, ],
MatchOperator.And MatchOperator.And
) )
.UsingPost() .UsingPost()
@@ -98,11 +94,10 @@ public partial class WireMockServerTests
Request.Create() Request.Create()
.WithPath("/a") .WithPath("/a")
.WithBody( .WithBody(
new IMatcher[] [
{
new JmesPathMatcher("requestId == '1'"), new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'") new JmesPathMatcher("value == 'A'")
}, ],
MatchOperator.And MatchOperator.And
) )
.UsingPost() .UsingPost()
@@ -188,6 +183,7 @@ public partial class WireMockServerTests
{"jsonrpc":"2.0","id":"{{request.bodyAsJson.id}}","result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"ExampleServer","version":"1.0.0"}}} {"jsonrpc":"2.0","id":"{{request.bodyAsJson.id}}","result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"ExampleServer","version":"1.0.0"}}}
""") """)
.WithStatusCode(200) .WithStatusCode(200)
.WithTransformer(true)
); );
// Act // Act
@@ -199,15 +195,47 @@ public partial class WireMockServerTests
// Assert // Assert
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseText = await response.RequestMessage!.Content!.ReadAsStringAsync(); var responseText = await response.Content.ReadAsStringAsync();
responseText.Should().Contain("ec475f56d4694b48bc737500ba575b35-1"); responseText.Should().Contain("ec475f56d4694b48bc737500ba575b35-1");
} }
#if NET6_0_OR_GREATER
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsync_And_JsonPartialWildcardMatcher_And_SystemTextJson_ShouldMatch()
{
// Arrange
using var server = WireMockServer.Start(x => x.DefaultJsonSerializer = new JsonConverter.System.Text.Json.SystemTextJsonConverter() );
var matcher = new JsonPartialWildcardMatcher(new { id = "^[a-f0-9]{32}-[0-9]$" }, ignoreCase: true, regex: true);
server.Given(Request.Create()
.WithHeader("Content-Type", "application/json*")
.UsingPost()
.WithPath("/system-text-json")
.WithBody(matcher)
)
.RespondWith(Response.Create()
.WithBody("OK")
);
// Act
var content = """{"id":"ec475f56d4694b48bc737500ba575b35-1"}""";
var response = await new HttpClient()
.PostAsync($"{server.Url}/system-text-json", new StringContent(content, Encoding.UTF8, "application/json"))
.ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseText = await response.Content.ReadAsStringAsync();
responseText.Should().Contain("OK");
}
#endif
[Fact] [Fact]
public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithFunc() public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithFunc()
{ {
// Arrange // Arrange
var server = WireMockServer.Start(); using var server = WireMockServer.Start();
server.Given( server.Given(
Request.Create() Request.Create()
.UsingPost() .UsingPost()
@@ -219,7 +247,7 @@ public partial class WireMockServerTests
); );
// Act // Act
var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("key1", "value1") }); var content = new FormUrlEncodedContent([new KeyValuePair<string, string>("key1", "value1")]);
var response = await new HttpClient() var response = await new HttpClient()
.PostAsync($"{server.Url}/foo", content) .PostAsync($"{server.Url}/foo", content)
.ConfigureAwait(false); .ConfigureAwait(false);