Compare commits

..

5 Commits

Author SHA1 Message Date
Stef Heyenrath
abe996671e Add Copilot Setup Steps action (#1419) 2026-01-09 18:20:12 +01:00
Petr Houška
9f819de696 Update aspire to 13.1 (examples + code) (#1417)
Allows usage of aspire CLI which is very useful for dev in codespaces (for my next PR).
2026-01-09 18:01:45 +01:00
Stef Heyenrath
f5d53453e5 1.23.0 2026-01-05 21:34:11 +01:00
samlatham
0e60e3f3f9 Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers (#1416)
Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers so that optional handlebars helpers can be enabled.

Co-authored-by: Sam Latham <sam.latham@citrix.com>
2026-01-05 21:24:48 +01:00
Luca Ma
9cee6dde00 Pass the parameter matchOperator in Request.WithPath to its inner calls (#1414)
Co-authored-by: Luca Ma <lucama@microsoft.com>
2026-01-04 08:03:19 +01:00
32 changed files with 237 additions and 543 deletions

View File

@@ -0,0 +1,36 @@
name: "Copilot Setup Steps"
# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest
# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read
# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Install .NET 10.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x
dotnet-quality: preview
- name: dotnet --info
run: dotnet --info

View File

@@ -1,3 +1,9 @@
# 1.23.0 (05 January 2026)
- [#1414](https://github.com/wiremock/WireMock.Net/pull/1414) - Pass the parameter matchOperator in Request.WithPath to its inner calls [bug] contributed by [gbamqzkdyg](https://github.com/gbamqzkdyg)
- [#1416](https://github.com/wiremock/WireMock.Net/pull/1416) - Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library contributed by [samlatham](https://github.com/samlatham)
- [#1413](https://github.com/wiremock/WireMock.Net/issues/1413) - Parameter `matchOperator` is not respected in the method Request.WithPath [bug]
- [#1415](https://github.com/wiremock/WireMock.Net/issues/1415) - HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug]
# 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]

View File

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

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.22.0
SET version=1.23.0
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,5 +1,7 @@
# 1.22.0 (02 January 2026)
- #1412 chore(testcontainers): bump up Testcontainers to version 4.10.0 [feature]
- #1411 WireMock.Net.Testcontainers isn't compatible with Testcontainers 4.10.0 [bug]
# 1.23.0 (05 January 2026)
- #1414 Pass the parameter matchOperator in Request.WithPath to its inner calls [bug]
- #1416 Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library
- #1413 Parameter `matchOperator` is not respected in the method Request.WithPath [bug]
- #1415 HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
</Project>

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="8.0.0" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.5.3" />

View File

@@ -32,7 +32,7 @@ public class ResponseModel
public object? BodyAsJson { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Json Body String needs to be indented.
/// 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.
/// </summary>
public bool? BodyAsJsonIndented { get; set; }

View File

@@ -37,7 +37,7 @@ public interface IBodyData
object? BodyAsJson { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Json Body String needs to be indented.
/// 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.
/// </summary>
bool? BodyAsJsonIndented { get; set; }

View File

@@ -45,7 +45,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting" Version="13.1.0" />
</ItemGroup>
<ItemGroup>

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ public partial class Request
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers));
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}

View File

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

View File

@@ -7,7 +7,7 @@ using System.Linq;
using System.Net;
using System.Text;
using JetBrains.Annotations;
using JsonConverter.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
@@ -236,7 +236,7 @@ public partial class WireMockServer
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{
var mappingModels = _mappingSerializer.DeserializeJsonToArray<MappingModel>(value);
var mappingModels = DeserializeJsonToArray<MappingModel>(value);
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))
{
ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path);
@@ -826,31 +826,25 @@ public partial class WireMockServer
}
}
private ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
private static Encoding? ToEncoding(EncodingModel? encodingModel)
{
var jsonOptions = new JsonConverterOptions
{
WriteIndented = true,
IgnoreNullValues = !keepNullValues
};
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = _settings.DefaultJsonSerializer.Serialize(result!, jsonOptions)
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = statusCode ?? (int)HttpStatusCode.OK,
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)
{
return new ResponseMessage
@@ -865,18 +859,6 @@ 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()
{
switch (requestMessage.BodyData?.DetectedBodyType)
@@ -892,4 +874,32 @@ public partial class WireMockServer
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))
{
var mappings = _mappingSerializer.DeserializeJsonToArray<OrgMapping>(value);
var mappings = DeserializeJsonToArray<OrgMapping>(value);
foreach (var mapping in mappings)
{
if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))

View File

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

View File

@@ -39,6 +39,8 @@ internal static class WireMockHandlebarsHelpers
#endif
o.CustomHelperPaths = paths;
o.Categories = settings.HandlebarsSettings?.AllowedHandlebarsHelpers ?? HandlebarsSettings.DefaultAllowedHandlebarsHelpers;
o.CustomHelpers = new Dictionary<string, IHelpers>();
if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true)
{

View File

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

View File

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

View File

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

View File

@@ -14,8 +14,6 @@ using WireMock.RegularExpressions;
using WireMock.Types;
using System.Globalization;
using WireMock.Models;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
#if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection;
@@ -340,14 +338,4 @@ public class WireMockServerSettings
/// </summary>
[PublicAPI]
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>
<Description>Shared interfaces, models, enumerations and types.</Description>
<Authors>Stef Heyenrath</Authors>
@@ -48,10 +48,9 @@
</PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" />
<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="JsonConverter.Abstractions" Version="0.8.0" />-->
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="JsonConverter.Abstractions" Version="0.7.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
<PackageReference Include="Codecov" Version="1.13.0" />
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>

View File

@@ -4,7 +4,6 @@ using System;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using JsonConverter.Newtonsoft.Json;
using Moq;
using Newtonsoft.Json.Linq;
using NFluent;
@@ -317,11 +316,12 @@ public class ResponseWithBodyTests
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request1, _settings).ConfigureAwait(false);
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]
public async Task Response_ProvideResponse_WithBody_NewtonsoftJsonConverter()
public async Task Response_ProvideResponse_WithBody_IJsonConverter_SystemTextJson()
{
// Arrange
var requestBody = new BodyData
@@ -331,34 +331,13 @@ public class ResponseWithBodyTests
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody);
var responseBuilder = Response.Create().WithBody(new { foo = "< > & ' 😀 👍 ❤️", n = 42 }, new NewtonsoftJsonConverter());
var responseBuilder = Response.Create().WithBody(new { foo = "bar", 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":"< > & ' 😀 👍 ","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}""");
response.Message.BodyData!.BodyAsString.Should().Be(@"{""foo"":""bar"",""n"":42}");
}
#endif
}

View File

@@ -1,326 +0,0 @@
// 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

@@ -0,0 +1,93 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using HandlebarsDotNet;
using HandlebarsDotNet.Helpers.Enums;
using Moq;
using WireMock.Handlers;
using WireMock.Models;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using Xunit;
namespace WireMock.Net.Tests.Settings;
public class HandlebarsSettingsTests
{
private const string ClientIp = "::1";
private readonly WireMockServerSettings _settings;
private readonly Mock<IMapping> _mappingMock;
private readonly Mock<IFileSystemHandler> _fileSystemHandlerMock;
public HandlebarsSettingsTests()
{
_mappingMock = new Mock<IMapping>();
_fileSystemHandlerMock = new Mock<IFileSystemHandler>(MockBehavior.Strict);
_settings = new WireMockServerSettings
{
FileSystemHandler = _fileSystemHandlerMock.Object
};
}
[Fact]
public async Task Response_HandlebarsHelpers_Environment_NotAllowed_By_Default()
{
// Arrange
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
var responseBuilder = Response.Create()
.WithBody("Username: {{Environment.GetEnvironmentVariable \"USERNAME\"}}")
.WithTransformer();
// Act
Func<Task> action = () => responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings);
// Assert
await action.Should().ThrowAsync<HandlebarsRuntimeException>();
}
[Fact]
public async Task Response_HandlebarsHelpers_Environment_Allowed_When_Configured()
{
// Arrange
var settingsWithEnv = new WireMockServerSettings
{
FileSystemHandler = _fileSystemHandlerMock.Object,
HandlebarsSettings = new HandlebarsSettings
{
AllowedHandlebarsHelpers = HandlebarsSettings.DefaultAllowedHandlebarsHelpers
.Concat([Category.Environment])
.ToArray()
}
};
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
var responseBuilder = Response.Create()
.WithBody("User: {{Environment.GetEnvironmentVariable \"USERNAME\"}}")
.WithTransformer();
// Act
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, settingsWithEnv).ConfigureAwait(false);
// Assert
response.Message?.BodyData?.BodyAsString.Should().NotContain("{{Environment.GetEnvironmentVariable");
response.Message?.BodyData?.BodyAsString.Should().StartWith("User: ");
}
[Fact]
public void DefaultAllowedHandlebarsHelpers_Should_Not_Include_EnvironmentAndDynamicLinq()
{
// Assert
HandlebarsSettings.DefaultAllowedHandlebarsHelpers.Should()
.NotContain(Category.Environment)
.And
.NotContain(Category.DynamicLinq);
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>Stef Heyenrath</Authors>
@@ -107,7 +107,7 @@
<ItemGroup Condition="'$(TargetFramework)' != 'net452'">
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
<!--<PackageReference Include="JsonConverter.System.Text.Json" Version="0.8.0" />-->
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.7.0" />
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0">

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
@@ -33,10 +34,11 @@ public partial class WireMockServerTests
Request.Create()
.WithPath("/a")
.WithBody(
[
new IMatcher[]
{
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
],
},
MatchOperator.And
)
.UsingPost()
@@ -47,10 +49,11 @@ public partial class WireMockServerTests
Request.Create()
.WithPath("/a")
.WithBody(
[
new IMatcher[]
{
new JmesPathMatcher("requestId == '2'"),
new JmesPathMatcher("value == 'A'")
],
},
MatchOperator.And
)
.UsingPost()
@@ -78,11 +81,12 @@ public partial class WireMockServerTests
Request.Create()
.WithPath("/a")
.WithBody(
[
new IMatcher[]
{
new JmesPathMatcher("extra == 'X'"),
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
],
},
MatchOperator.And
)
.UsingPost()
@@ -94,10 +98,11 @@ public partial class WireMockServerTests
Request.Create()
.WithPath("/a")
.WithBody(
[
new IMatcher[]
{
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
],
},
MatchOperator.And
)
.UsingPost()
@@ -183,7 +188,6 @@ 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"}}}
""")
.WithStatusCode(200)
.WithTransformer(true)
);
// Act
@@ -195,47 +199,15 @@ public partial class WireMockServerTests
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseText = await response.Content.ReadAsStringAsync();
var responseText = await response.RequestMessage!.Content!.ReadAsStringAsync();
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]
public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithFunc()
{
// Arrange
using var server = WireMockServer.Start();
var server = WireMockServer.Start();
server.Given(
Request.Create()
.UsingPost()
@@ -247,7 +219,7 @@ public partial class WireMockServerTests
);
// Act
var content = new FormUrlEncodedContent([new KeyValuePair<string, string>("key1", "value1")]);
var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("key1", "value1") });
var response = await new HttpClient()
.PostAsync($"{server.Url}/foo", content)
.ConfigureAwait(false);