Compare commits

...

24 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
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
e5afd69f7c 1.21.0 2025-12-25 15:00:54 +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
597c95000e vmImage: 'windows-2025' (#1407) 2025-12-24 16:59:02 +01:00
Stef Heyenrath
4617b99c30 [Collection("Grpc")] 2025-12-24 12:32:56 +01:00
Stef Heyenrath
ffd4d89946 Re-enable TestcontainersTestsGrpc (#1406)
* Re-enable TestcontainersTestsGrpc

* //[Collection("Grpc")]
2025-12-24 12:16:56 +01:00
Stef Heyenrath
2d46c86f47 1.20.0 2025-12-24 10:11:53 +01:00
Stef Heyenrath
75f4fbe9d0 Fix Testcontainers AddProtoDefinition (#1405)
* Fix Testcontainers AddProtoDefinition

* .

* UntilHttpRequestIsSucceeded

* WireMockContainer.ContainerPort

* System.Net/System.Net.Http

* ...

* WithWaitStrategy

* MaxHealthCheckRetries

* for

* _adminApi

* static

* ...

* testOutputHelper.WriteLine("Dumping WireMock logs:");

* Console.WriteLine(

* testOutputHelper.WriteLine("Dumping WireMock.Net mappings:");

* fix WithWaitStrategy

* [Fact]

* <PackageReference Include="ProtoBufJsonConverter" Version="0.11.0" />

* [Collection("Grpc")] / [Fact(Skip = "TODO")]

* ...
2025-12-24 10:09:30 +01:00
Stef Heyenrath
16e3872402 Run the Grpc TestcontainersTests sequential (#1402) 2025-12-21 09:40:16 +01:00
Stef Heyenrath
4c797c328f Add WireMock.Net.NUnit project (#1400)
* Add WireMock.Net.NUnit project

* <Version>0.0.1-preview-01</Version>

* --v
2025-12-20 13:43:54 +01:00
Stef Heyenrath
a5e75a7278 Fix Grpc tests (#1401)
* Fix some Grpc tests

* await Task.Delay(1000);

* ports
2025-12-20 12:08:41 +01:00
Stef Heyenrath
56f65c19e2 Upgrade RamlToOpenApiConverter and YamlDotNet (#1399)
* Upgrade RamlToOpenApiConverter and YamlDotNet

* fix
2025-12-19 18:33:58 +01:00
Stef Heyenrath
6aef4816a5 WireMockServer_WithRequiredClientCertificates_Should_Work_Correct --> IgnoreOnContinuousIntegrationFact 2025-12-19 17:52:20 +01:00
Stef Heyenrath
197a211a52 TestcontainersTests 2025-12-13 11:48:48 +01:00
Stef Heyenrath
3cfeec6035 1.19.0 2025-12-12 11:16:38 +01:00
Stef Heyenrath
b57d5e7548 WireMockContainerBuilder: allow only docker images named wiremock (#1392) 2025-12-11 22:21:39 +01:00
Stef Heyenrath
36b89afce5 fix CI link in Readme 2025-12-11 11:25:28 +01:00
Stef Heyenrath
e2acac55a4 Update WireMockContainerBuilder (WithImage and WithCustomImage) (#1391)
* Update WireMockContainerBuilder (WithImage and WithCustomImage)

* .
2025-12-11 10:55:31 +01:00
43 changed files with 731 additions and 129 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,28 @@
# 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]
# 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)
- [#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)
- [#1405](https://github.com/wiremock/WireMock.Net/pull/1405) - Fix Testcontainers AddProtoDefinition [bug] contributed by [StefH](https://github.com/StefH)
- [#1398](https://github.com/wiremock/WireMock.Net/issues/1398) - Upgrade YamlDotNet dependency [feature]
- [#1404](https://github.com/wiremock/WireMock.Net/issues/1404) - An exception occurs when adding multiple proto definitions in the TestContainer. [bug]
# 1.19.0 (12 December 2025)
- [#1391](https://github.com/wiremock/WireMock.Net/pull/1391) - Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature] contributed by [StefH](https://github.com/StefH)
- [#1392](https://github.com/wiremock/WireMock.Net/pull/1392) - WireMockContainerBuilder: allow all docker images named wiremock [feature] contributed by [StefH](https://github.com/StefH)
- [#1390](https://github.com/wiremock/WireMock.Net/issues/1390) - Unable to build WireMockContainerBuilder with custom image [feature]
# 1.18.0 (09 December 2025)
- [#1388](https://github.com/wiremock/WireMock.Net/pull/1388) - Add WithBodyAsType to RequestMatcher [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.18.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.18.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.18.0 (09 December 2025)
- #1388 Add WithBodyAsType to RequestMatcher [feature]
- #330 Feature: Add support for CSharpCode Transformer [wontfix]
# 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

@@ -33,7 +33,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/wiremock/WireMock.Net/issues) |
| | |
| ***Quality*** | &nbsp; |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=7) |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|
@@ -55,6 +55,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &nbsp;&nbsp;**WireMock.Net.xUnit** | [![NuGet Badge WireMock.Net.xUnit](https://img.shields.io/nuget/v/WireMock.Net.xUnit)](https://www.nuget.org/packages/WireMock.Net.xUnit) | [![MyGet Badge WireMock.Net.xUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.xUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit)
| &nbsp;&nbsp;**WireMock.Net.xUnit.v3** | [![NuGet Badge WireMock.Net.xUnit](https://img.shields.io/nuget/v/WireMock.Net.xUnit.v3)](https://www.nuget.org/packages/WireMock.Net.xUnit.v3) | [![MyGet Badge WireMock.Net.xUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.xUnit.v3?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit.v3)
| &nbsp;&nbsp;**WireMock.Net.TUnit** | [![NuGet Badge WireMock.Net.TUnit](https://img.shields.io/nuget/v/WireMock.Net.TUnit)](https://www.nuget.org/packages/WireMock.Net.TUnit) | [![MyGet Badge WireMock.Net.TUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.TUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.TUnit)
| &nbsp;&nbsp;**WireMock.Net.NUnit** | [![NuGet Badge WireMock.Net.NUnit](https://img.shields.io/nuget/v/WireMock.Net.NUnit)](https://www.nuget.org/packages/WireMock.Net.NUnit) | [![MyGet Badge WireMock.Net.TUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.NUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.NUnit)
| | | |
| &nbsp;&nbsp;**WireMock.Net.Extensions.Routing** | [![NuGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/nuget/v/WireMock.Net.Extensions.Routing)](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [![MyGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Extensions.Routing?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/nuget/v/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.CSharpCode?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157 d18.0
VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
EndProject
@@ -146,6 +146,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.xUnit.v3", "src\WireMock.Net.xUnit.v3\WireMock.Net.xUnit.v3.csproj", "{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.NUnit", "src\WireMock.Net.NUnit\WireMock.Net.NUnit.csproj", "{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -356,6 +358,10 @@ Global
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.Build.0 = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -414,6 +420,7 @@ Global
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{B47413AA-55D3-49A7-896A-17ADBFF72407} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

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

View File

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

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

@@ -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

@@ -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

@@ -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

@@ -0,0 +1,71 @@
// Copyright © WireMock.Net
using System;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using NUnit.Framework;
using WireMock.Admin.Requests;
using WireMock.Logging;
namespace WireMock.Net.NUnit;
/// <summary>
/// When using NUnit, this class enables to log the output from WireMock.Net using the <see cref="TestContext"/>.
/// </summary>
public sealed class TestContextWireMockLogger(IJsonConverter? jsonConverter = null) : IWireMockLogger
{
private readonly JsonConverterOptions _jsonConverterOptions = new() { WriteIndented = true, IgnoreNullValues = true };
private readonly IJsonConverter _jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
/// <inheritdoc />
public void Debug(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Debug", formatString, args));
}
/// <inheritdoc />
public void Info(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Info", formatString, args));
}
/// <inheritdoc />
public void Warn(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Warning", formatString, args));
}
/// <inheritdoc />
public void Error(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Error", formatString, args));
}
/// <inheritdoc />
public void Error(string message, Exception exception)
{
TestContext.WriteLine(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{
ae.Handle(ex =>
{
TestContext.WriteLine(Format("Error", "Exception {0}", ex));
return true;
});
}
}
/// <inheritdoc />
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
var message = _jsonConverter.Serialize(logEntryModel, _jsonConverterOptions);
TestContext.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
{
var message = args.Length > 0 ? string.Format(formatString, args) : formatString;
return $"{DateTime.UtcNow} [{level}] : {message}";
}
}

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Some extensions for NUnit</Description>
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net462;net6.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.NUnit</AssemblyName>
<RootNamespace>WireMock.Net.NUnit</RootNamespace>
<PackageId>WireMock.Net.NUnit</PackageId>
<PackageTags>tdd;wiremock;test;unittest;nunit</PackageTags>
<ProjectGuid>{2DBBD70D-8051-441F-92BB-3F9B8B4B4983}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -5,9 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Extensions;

View File

@@ -6,8 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;

View File

@@ -1,7 +1,7 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings;

View File

@@ -1,7 +1,7 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;

View File

@@ -1,8 +1,7 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings;

View File

@@ -3,8 +3,7 @@
using System;
using System.Linq;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;

View File

@@ -27,22 +27,22 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.8.0" />
<PackageReference Include="YamlDotNet" Version="8.1.0" />
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.11.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.19" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="2.0.0-preview.17" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="SharpYaml" Version="2.1.1" />
<PackageReference Include="SharpYaml" Version="2.1.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.YamlReader;
using RamlToOpenApiConverter;

View File

@@ -96,14 +96,14 @@ public class ProtoBufMatcher : IProtoBufMatcher
}
var protoDefinitions = ProtoDefinition().Texts;
var resolver = new WireMockProtoFileResolver(protoDefinitions);
var request = new ConvertToObjectRequest(protoDefinitions[0], MessageType, input)
.WithProtoFileResolver(resolver);
try
{
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken);
}
catch
{

View File

@@ -26,7 +26,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" />
<PackageReference Include="ProtoBufJsonConverter" Version="0.11.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,18 @@
// Copyright © WireMock.Net
using WireMock.Net.Testcontainers;
namespace DotNet.Testcontainers.Configurations;
internal static class HttpWaitStrategyExtensions
{
internal static HttpWaitStrategy WithBasicAuthentication(this HttpWaitStrategy strategy, WireMockConfiguration configuration)
{
if (configuration.HasBasicAuthentication)
{
return strategy.WithBasicAuthentication(configuration.Username, configuration.Password);
}
return strategy;
}
}

View File

@@ -0,0 +1,20 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Net.Testcontainers.Utils;
internal static class CombineUtils
{
internal static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
{
return oldValue.Union(newValue).ToList();
}
internal static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
where TKey : notnull
{
return oldValue.Union(newValue).ToDictionary(item => item.Key, item => item.Value);
}
}

View File

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

View File

@@ -1,12 +1,12 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Net.Testcontainers.Utils;
namespace WireMock.Net.Testcontainers;
@@ -77,8 +77,8 @@ public sealed class WireMockConfiguration : ContainerConfiguration
StaticMappingsPath = BuildConfiguration.Combine(oldValue.StaticMappingsPath, newValue.StaticMappingsPath);
WatchStaticMappings = BuildConfiguration.Combine(oldValue.WatchStaticMappings, newValue.WatchStaticMappings);
WatchStaticMappingsInSubdirectories = BuildConfiguration.Combine(oldValue.WatchStaticMappingsInSubdirectories, newValue.WatchStaticMappingsInSubdirectories);
AdditionalUrls = Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls);
ProtoDefinitions = Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions);
AdditionalUrls = CombineUtils.Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls);
ProtoDefinitions = CombineUtils.Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions);
}
/// <summary>
@@ -130,16 +130,4 @@ public sealed class WireMockConfiguration : ContainerConfiguration
return this;
}
private static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
{
return oldValue.Concat(newValue).ToList();
}
private static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
{
return newValue
.Concat(oldValue.Where(item => !newValue.Keys.Contains(item.Key)))
.ToDictionary(item => item.Key, item => item.Value);
}
}

View File

@@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers;
/// <summary>
/// A container for running WireMock in a docker environment.
/// </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;
internal const int ContainerPort = 80;
private readonly WireMockConfiguration _configuration;
private readonly WireMockConfiguration _configuration = Guard.NotNull(configuration);
private IWireMockAdminApi? _adminApi;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
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>
/// Gets the public Url.
/// </summary>
@@ -157,14 +150,28 @@ public sealed class WireMockContainer : DockerContainer
try
{
var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
Logger.LogInformation("ReloadStaticMappings result: {Result}", result);
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings result: {Result}", result);
}
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 />
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()
{
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
@@ -223,21 +221,22 @@ public sealed class WireMockContainer : DockerContainer
_enhancedFileSystemWatcher.EnableRaisingEvents = true;
}
private async Task CallAdditionalActionsAfterStartedAsync()
private async Task AddProtoDefinitionsAsync()
{
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)
{
try
{
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)
{
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key);
Logger.LogWarning(ex, "WireMock.Net -> Error adding ProtoDefinition '{Id}'.", kvp.Key);
}
}
}
@@ -254,17 +253,17 @@ public sealed class WireMockContainer : DockerContainer
try
{
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)
{
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)
{
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);
}

View File

@@ -2,6 +2,8 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
@@ -62,6 +64,29 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
return WithImage(OSPlatform.Windows);
}
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public new WireMockContainerBuilder WithImage(string image)
{
return WithCustomImage(image);
}
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder WithCustomImage(string image)
{
_imageOS ??= TestcontainersUtils.GetImageOSAsync.Value.GetAwaiter().GetResult();
return base.WithImage(image);
}
/// <summary>
/// Set the admin username and password for the container (basic authentication).
/// </summary>
@@ -207,9 +232,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
// In case the _imageOS is not set, determine it from the Image FullName.
if (_imageOS == null)
{
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock.net", StringComparison.OrdinalIgnoreCase) < 0)
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock", StringComparison.OrdinalIgnoreCase) < 0)
{
throw new InvalidOperationException();
throw new InvalidOperationException("It's only possible to use a wiremock docker image.");
}
_imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
@@ -227,6 +252,25 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
builder.Validate();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
builder = builder
.WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
.UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy
.ForPort(WireMockContainer.ContainerPort)
.WithMethod(HttpMethod.Get)
.WithBasicAuthentication(DockerResourceConfiguration)
.ForPath("/__admin/health")
.ForStatusCode(HttpStatusCode.OK)
.ForResponseMessageMatching(async httpResponseMessage =>
{
var content = await httpResponseMessage.Content.ReadAsStringAsync();
return content?.Contains("Healthy") == true;
})
)
.AddCustomWaitStrategy(new WireMockWaitStrategy())
);
return new WireMockContainer(builder.DockerResourceConfiguration);
}
@@ -235,11 +279,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
{
var builder = base.Init();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
return builder
.WithPortBinding(WireMockContainer.ContainerPort, true)
.WithCommand($"--WireMockLogger {DefaultLogger}")
.WithWaitStrategy(waitForContainerOS.UntilMessageIsLogged("WireMock.Net server running"));
.WithCommand($"--WireMockLogger {DefaultLogger}");
}
/// <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

@@ -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

@@ -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

@@ -0,0 +1,161 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using FluentAssertions;
using WireMock.Net.Testcontainers.Utils;
using Xunit;
namespace WireMock.Net.Tests.Testcontainers;
public class CombineUtilsTests
{
[Fact]
public void Combine_Lists_WithBothEmpty_ReturnsEmptyList()
{
// Arrange
var oldValue = new List<string>();
var newValue = new List<string>();
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().BeEmpty();
}
[Fact]
public void Combine_Lists_WithEmptyOldValue_ReturnsNewValue()
{
// Arrange
var oldValue = new List<string>();
var newValue = new List<string> { "item1", "item2" };
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().Equal("item1", "item2");
}
[Fact]
public void Combine_Lists_WithEmptyNewValue_ReturnsOldValue()
{
// Arrange
var oldValue = new List<string> { "item1", "item2" };
var newValue = new List<string>();
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().Equal("item1", "item2");
}
[Fact]
public void Combine_Lists_WithBothPopulated_ReturnsConcatenatedList()
{
// Arrange
var oldValue = new List<int> { 1, 2, 3 };
var newValue = new List<int> { 4, 5, 6 };
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().Equal(1, 2, 3, 4, 5, 6);
}
[Fact]
public void Combine_Lists_WithDuplicates_RemovesDuplicates()
{
// Arrange
var oldValue = new List<string> { "a", "b", "c" };
var newValue = new List<string> { "b", "c", "d" };
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().Equal("a", "b", "c", "d");
}
[Fact]
public void Combine_Dictionaries_WithBothEmpty_ReturnsEmptyDictionary()
{
// Arrange
var oldValue = new Dictionary<string, int>();
var newValue = new Dictionary<string, int>();
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().BeEmpty();
}
[Fact]
public void Combine_Dictionaries_WithEmptyOldValue_ReturnsNewValue()
{
// Arrange
var oldValue = new Dictionary<string, int>();
var newValue = new Dictionary<string, int>
{
{ "key1", 1 },
{ "key2", 2 }
};
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().HaveCount(2);
result["key1"].Should().Be(1);
result["key2"].Should().Be(2);
}
[Fact]
public void Combine_Dictionaries_WithEmptyNewValue_ReturnsOldValue()
{
// Arrange
var oldValue = new Dictionary<string, int>
{
{ "key1", 1 },
{ "key2", 2 }
};
var newValue = new Dictionary<string, int>();
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().HaveCount(2);
result["key1"].Should().Be(1);
result["key2"].Should().Be(2);
}
[Fact]
public void Combine_Dictionaries_WithNoOverlappingKeys_ReturnsMergedDictionary()
{
// Arrange
var oldValue = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var newValue = new Dictionary<string, string>
{
{ "key3", "value3" },
{ "key4", "value4" }
};
// Act
var result = CombineUtils.Combine(oldValue, newValue);
// Assert
result.Should().HaveCount(4);
result["key1"].Should().Be("value1");
result["key2"].Should().Be("value2");
result["key3"].Should().Be("value3");
result["key4"].Should().Be("value4");
}
}

View File

@@ -7,6 +7,8 @@ using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using FluentAssertions;
using FluentAssertions.Execution;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Net.Testcontainers;
using WireMock.Net.Testcontainers.Utils;
using WireMock.Net.Tests.Facts;
@@ -15,8 +17,14 @@ using Xunit.Abstractions;
namespace WireMock.Net.Tests.Testcontainers;
public partial 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]
public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync()
{
@@ -24,6 +32,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithAutoRemove(true)
.WithCleanUp(true)
@@ -43,6 +52,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
.Build();
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithNetwork(dummyNetwork)
.WithWatchStaticMappings(true)
.Build();
@@ -58,6 +68,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
@@ -83,6 +94,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
@@ -92,7 +104,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
}
else
{
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net");
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net-alpine");
}
var wireMockContainer = wireMockContainerBuilder.Build();

View File

@@ -3,6 +3,7 @@
#if NET6_0_OR_GREATER
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
@@ -10,30 +11,45 @@ using FluentAssertions;
using FluentAssertions.Execution;
using Greet;
using Grpc.Net.Client;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Constants;
using WireMock.Net.Testcontainers;
using WireMock.Util;
using Xunit;
using Xunit.Abstractions;
namespace WireMock.Net.Tests.Testcontainers;
public partial class TestcontainersTests
[Collection("Grpc")]
public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
{
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()
{
// Act
// Arrange
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var port = PortUtils.FindFreeTcpPort();
// Act
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithCommand("--UseHttp2")
.WithCommand("--Urls", "http://*:80 grpc://*:9090")
.WithPortBinding(9090, true)
.WithCommand("--Urls", $"http://*:80 grpc://*:{port}")
.WithPortBinding(port, true)
.Build();
try
{
await wireMockContainer.StartAsync().ConfigureAwait(false);
await wireMockContainer.StartAsync();
// Assert
using (new AssertionScope())
@@ -53,10 +69,10 @@ public partial class TestcontainersTests
var httpUrl = wireMockContainer.GetMappedPublicUrl(80);
httpUrl.Should().StartWith("http://");
var grpcPort = wireMockContainer.GetMappedPublicPort(9090);
var grpcPort = wireMockContainer.GetMappedPublicPort(port);
grpcPort.Should().BeGreaterThan(0);
var grpcUrl = wireMockContainer.GetMappedPublicUrl(9090);
var grpcUrl = wireMockContainer.GetMappedPublicUrl(port);
grpcUrl.Should().StartWith("http://");
var adminClient = wireMockContainer.CreateWireMockAdminClient();
@@ -74,19 +90,23 @@ public partial class TestcontainersTests
[Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2()
{
// Act
// Arrange
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var ports = PortUtils.FindFreeTcpPorts(3);
// Act
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.AddUrl("http://*:8080")
.AddUrl("grpc://*:9090")
.AddUrl("grpc://*:9091")
.AddUrl($"http://*:{ports[0]}")
.AddUrl($"grpc://*:{ports[1]}")
.AddUrl($"grpc://*:{ports[2]}")
.Build();
try
{
await wireMockContainer.StartAsync().ConfigureAwait(false);
await wireMockContainer.StartAsync();
// Assert
using (new AssertionScope())
@@ -100,7 +120,7 @@ public partial class TestcontainersTests
var urls = wireMockContainer.GetPublicUrls();
urls.Should().HaveCount(4);
foreach (var internalPort in new[] { 80, 8080, 9090, 9091 })
foreach (var internalPort in new[] { ports[0], ports[1], ports[2], 80 })
{
var publicPort = wireMockContainer.GetMappedPublicPort(internalPort);
publicPort.Should().BeGreaterThan(0);
@@ -161,10 +181,63 @@ public partial class TestcontainersTests
await StopAsync(wireMockContainer);
}
private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
private async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(WireMockContainer wireMockContainer)
{
var address = wireMockContainer.GetPublicUrls().First(x => x.Key != 80).Value;
var channel = GrpcChannel.ForAddress(address);
var client = new Greeter.GreeterClient(channel);
try
{
return await client.SayHelloAsync(new HelloRequest { Name = "stef" });
}
catch (Exception ex)
{
testOutputHelper.WriteLine("Exception during GrpcClient Call to {0}. Exception = {1}.", address, ex);
testOutputHelper.WriteLine("Dumping WireMock.Net logs:");
var (stdOut, stdError) = await wireMockContainer.GetLogsAsync(DateTime.MinValue);
testOutputHelper.WriteLine("Out :\r\n{0}", stdOut);
testOutputHelper.WriteLine("Error:\r\n{0}", stdError);
testOutputHelper.WriteLine("Dumping WireMock.Net mappings:");
using var httpClient = wireMockContainer.CreateClient();
using var response = await httpClient.GetAsync("/__admin/mappings");
var mappings = await response.Content.ReadAsStringAsync();
testOutputHelper.WriteLine("Mappings:\r\n{0}", mappings);
throw;
}
}
private async Task StopAsync(WireMockContainer wireMockContainer)
{
try
{
await wireMockContainer.StopAsync();
}
catch (Exception ex)
{
// Sometimes we get this exception, so for now ignore it.
/*
Failed WireMock.Net.Tests.Testcontainers.TestcontainersTests.WireMockContainer_Build_WithImageAsText_And_StartAsync_and_StopAsync [9 s]
Error Message:
System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
at DotNet.Testcontainers.Containers.DockerContainer.UnsafeStopAsync(CancellationToken ct) in /_/src/Testcontainers/Containers/DockerContainer.cs:line 567
at DotNet.Testcontainers.Containers.DockerContainer.StopAsync(CancellationToken ct) in /_/src/Testcontainers/Containers/DockerContainer.cs:line 319
*/
testOutputHelper.WriteLine($"Exception during StopAsync: {ex}");
}
}
private async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.AddUrl("grpc://*:9090")
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.Build();
await wireMockContainer.StartAsync();
@@ -172,10 +245,12 @@ public partial class TestcontainersTests
return wireMockContainer;
}
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.AddUrl("grpc://*:9090")
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.Build();
@@ -184,10 +259,12 @@ public partial class TestcontainersTests
return wireMockContainer;
}
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.AddUrl("grpc://*:9090")
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.WithMappings(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings"))
.Build();
@@ -207,16 +284,6 @@ public partial class TestcontainersTests
result.EnsureSuccessStatusCode();
}
private static async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(WireMockContainer wireMockContainer)
{
var address = wireMockContainer.GetPublicUrls()[9090];
var channel = GrpcChannel.ForAddress(address);
var client = new Greeter.GreeterClient(channel);
return await client.SayHelloAsync(new HelloRequest { Name = "stef" });
}
private static void Then_ReplyMessage_Should_BeCorrect(HelloReply reply)
{
reply.Message.Should().Be("hello stef POST");

View File

@@ -38,6 +38,10 @@
<Compile Remove="Util\JsonUtilsTests.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\src\WireMock.Net.Testcontainers\Utils\CombineUtils.cs" Link="Testcontainers\CombineUtils.cs" />
</ItemGroup>
<ItemGroup>
<!-- https://stackoverflow.com/questions/59406201/filenesting-not-working-for-class-or-shared-library-projects -->
<ProjectCapability Include="ConfigurableFileNesting" />
@@ -117,6 +121,7 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<ProjectReference Include="..\..\src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj" />
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.21" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,21 +4,21 @@
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using FluentAssertions;
using System.Security.Cryptography.X509Certificates;
using WireMock.Net.Tests.Facts;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Types;
using Xunit;
namespace WireMock.Net.Tests;
public partial class WireMockServerTests
{
[Fact]
[IgnoreOnContinuousIntegrationFact]
public async Task WireMockServer_WithRequiredClientCertificates_Should_Work_Correct()
{
// Arrange
@@ -57,5 +57,4 @@ public partial class WireMockServerTests
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
#endif

View File

@@ -235,7 +235,7 @@ public partial class WireMockServerTests
}
#if NET8_0_OR_GREATER
[IgnoreOnContinuousIntegrationFact]
[Fact(Skip = "Does not work on local and pipeline")]
public async Task WireMockServer_WithUrl0000_Should_Listen_On_All_IPs_IPv6()
{
// Arrange