Compare commits

...

14 Commits
1.7.0 ... 1.7.4

Author SHA1 Message Date
Stef Heyenrath
be55022a2a 1.7.4 2025-02-27 07:15:02 +01:00
Stef Heyenrath
7c68fc1d94 Add ToArray() to ConcurrentObservableCollection (#1256)
* Add ToArray() to ConcurrentObservableCollection

* ---
2025-02-27 07:12:05 +01:00
Stef Heyenrath
e7d442e5ac 1.7.3 2025-02-24 23:14:50 +01:00
Stef Heyenrath
f977b3eb86 Update QueryStringParser to support param with equal but no value (#1253)
* RequestMessageParamMatcher

* ?

* .

* .
2025-02-13 18:09:18 +01:00
Stef
84e5ba6dce 1.7.2 2025-02-12 06:15:52 +01:00
Stef
e0d693c515 1.7.2 2025-02-12 06:14:39 +01:00
Stef Heyenrath
e8de5aa73c Add ProtoDefinition to WireMockContainer (#1250)
* AddProtoDefinitionAsync

* ...

* Body

* "

* .

* .

* .

* [Fact(Skip = "new docker is needed")]

* x
2025-02-12 06:08:55 +01:00
Stef Heyenrath
a02ff47db6 Update WireMockProtoFileResolver and add tests for ProtoBufUtils (#1252)
* Update WireMockProtoFileResolver and add tests for ProtoBufUtils

* .
2025-02-01 22:27:32 +01:00
JvE-iO
29bf9b42f8 Add exception message to logging when mapping fails due to an exception. (#1248)
* Add exception message to logging when mapping fails due to an exception.

* Revert "Add exception message to logging when mapping fails due to an exception."

This reverts commit eb7cf46c95.

* Fix loggers with improved exception logging.
2025-01-30 10:59:22 +01:00
Stef Heyenrath
52b00d74a9 Add "AddUrl" to WireMockContainerBuilder to support grpc (#1246)
* Add "AddUrl" to WireMockContainerBuilder to support grpc

* fix

* fix for windows

* wip

* fix !

* change some example code
2025-01-29 22:09:17 +01:00
Stef
f5fe51e227 1.7.1 2025-01-26 08:56:47 +01:00
Stef Heyenrath
fa8f45a7ac Use Handlebars.Net.Helpers version 2.4.10 (#1245) 2025-01-26 08:52:37 +01:00
Stef Heyenrath
ed07da7d18 Fix ProtoBuf mapping.json (#1236)
* Fix ProtoBuf Mappings

* [Fact(Skip = "#1233")]

* fix?

* PortUtils
2025-01-26 08:37:17 +01:00
Stef Heyenrath
442d8a715c Update README.md [Breaking changes] 2025-01-25 11:53:56 +01:00
61 changed files with 1615 additions and 318 deletions

View File

@@ -1,3 +1,23 @@
# 1.7.4 (27 February 2025)
- [#1256](https://github.com/WireMock-Net/WireMock.Net/pull/1256) - Add ToArray() to ConcurrentObservableCollection [bug] contributed by [StefH](https://github.com/StefH)
- [#1254](https://github.com/WireMock-Net/WireMock.Net/issues/1254) - FindLogEntries exception 'Destination array was not long enough' [bug]
# 1.7.3 (24 February 2025)
- [#1253](https://github.com/WireMock-Net/WireMock.Net/pull/1253) - Update QueryStringParser to support param with equal but no value [bug] contributed by [StefH](https://github.com/StefH)
- [#1247](https://github.com/WireMock-Net/WireMock.Net/issues/1247) - API call isn't matched when using an empty query string parameter [bug]
# 1.7.2 (12 February 2025)
- [#1246](https://github.com/WireMock-Net/WireMock.Net/pull/1246) - Add "AddUrl" to WireMockContainerBuilder to support grpc [feature] contributed by [StefH](https://github.com/StefH)
- [#1248](https://github.com/WireMock-Net/WireMock.Net/pull/1248) - Add exception message to logging when mapping fails due to an exception. contributed by [JvE-iO](https://github.com/JvE-iO)
- [#1250](https://github.com/WireMock-Net/WireMock.Net/pull/1250) - Add ProtoDefinition to WireMockContainer [feature] contributed by [StefH](https://github.com/StefH)
- [#1239](https://github.com/WireMock-Net/WireMock.Net/issues/1239) - How to use WiremockContainerBuilder for grpc using http2 [feature]
- [#1249](https://github.com/WireMock-Net/WireMock.Net/issues/1249) - Add protodefinition and refer it from mapping [feature]
# 1.7.1 (26 January 2025)
- [#1236](https://github.com/WireMock-Net/WireMock.Net/pull/1236) - Fix ProtoBuf mapping.json [bug] contributed by [StefH](https://github.com/StefH)
- [#1245](https://github.com/WireMock-Net/WireMock.Net/pull/1245) - Use Handlebars.Net.Helpers to version 2.4.10 [feature] contributed by [StefH](https://github.com/StefH)
- [#1233](https://github.com/WireMock-Net/WireMock.Net/issues/1233) - GRPC mappings are not created correctly when created through Admin API [bug]
# 1.7.0 (22 January 2025)
- [#1242](https://github.com/WireMock-Net/WireMock.Net/pull/1242) - Disable DynamicLinq to fix CVE [bug] contributed by [StefH](https://github.com/StefH)

View File

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

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.7.0
SET version=1.7.4
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -1,4 +1,5 @@
# 1.7.0 (22 January 2025)
- #1242 Disable DynamicLinq to fix CVE [bug]
# 1.7.4 (27 February 2025)
- #1256 Add ToArray() to ConcurrentObservableCollection [bug]
- #1254 FindLogEntries exception 'Destination array was not long enough' [bug]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -21,7 +21,6 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
- [mstack.nl : gRPC / ProtoBuf Support](https://mstack.nl/blogs/wiremock-net-grpc)
- [mstack.nl : Build and test your own .NET Aspire component](https://mstack.nl/blogs/wiremock-net-aspire-component/)
## :computer: Project Info
| | |
| --- | --- |
@@ -56,6 +55,16 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
---
## :exclamation: Breaking changes
### 1.7.0
A breaking change is introduced which is related to System.Linq.Dynamic.Core DynamicLinq ([CVE](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/867)).
- The `LinqMatcher` is not allowed.
- The [Handlebars.Net.Helpers.DynamicLinq](https://www.nuget.org/packages/Handlebars.Net.Helpers.DynamicLinq) package is not included anymore.
---
## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.

View File

@@ -198,7 +198,7 @@ internal class Program
var dummyNetwork = new NetworkBuilder()
.WithName($"Dummy Network for {image ?? "null"}")
.WithReuse(true)
.WithCleanUp(true)
// .WithCleanUp(true)
.Build();
var builder = new WireMockContainerBuilder()

View File

@@ -52,9 +52,9 @@ public class WireMockService : IWireMockService
_logger.LogDebug("Admin[{0}] {1}", isAdminrequest, message);
}
public void Error(string formatString, Exception exception)
public void Error(string message, Exception exception)
{
_logger.LogError(formatString, exception.Message);
_logger.LogError(exception, message);
}
}

View File

@@ -16,7 +16,7 @@ public readonly struct IdOrTexts
public string? Id { get; }
/// <summary>
/// The Text.
/// The Texts.
/// </summary>
public IReadOnlyList<string> Texts { get; }
@@ -41,7 +41,7 @@ public readonly struct IdOrTexts
}
/// <summary>
/// When Id is defined, return process the Id, else process the Texts.
/// When Id is defined, process the Id, else process the Texts.
/// </summary>
/// <param name="id">Callback to process the id.</param>
/// <param name="texts">Callback to process the texts.</param>

View File

@@ -215,14 +215,16 @@ public interface IWireMockServer : IDisposable
/// This can be used if you have 1 or more <see cref="MappingModel"/> defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The MappingModels</param>
/// <returns><see cref="IWireMockServer"/></returns>
IWireMockServer WithMapping(params MappingModel[] mappings);
/// <summary>
/// Register the mappings (via json string).
///
/// This can be used if you the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// This can be used if you've the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The mapping(s) as json string.</param>
/// <returns><see cref="IWireMockServer"/></returns>
IWireMockServer WithMapping(string mappings);
/// <summary>
@@ -238,5 +240,5 @@ public interface IWireMockServer : IDisposable
/// </summary>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code</returns>
public string MappingsToCSharpCode(MappingConverterType converterType);
string MappingsToCSharpCode(MappingConverterType converterType);
}

View File

@@ -130,7 +130,7 @@ public interface IWireMockAdminApi
Task<StatusModel> ReloadStaticMappingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Get a mapping based on the guid
/// Get a mapping based on the guid.
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
@@ -138,6 +138,15 @@ public interface IWireMockAdminApi
[Get("mappings/{guid}")]
Task<MappingModel> GetMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Get a mapping based on the guid.
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/{guid}")]
Task<MappingModel> GetMappingAsync([Path] string guid, CancellationToken cancellationToken = default);
/// <summary>
/// Get the C# code from a mapping based on the guid
/// </summary>
@@ -302,6 +311,15 @@ public interface IWireMockAdminApi
[Delete("files/{filename}")]
Task<StatusModel> DeleteFileAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinition">The ProtoDefinition as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("protodefinitions/{id}")]
Task<StatusModel> AddProtoDefinitionAsync([Path] string id, [Body] string body, CancellationToken cancellationToken = default);
/// <summary>
/// Check if a file exists
/// </summary>

View File

@@ -51,9 +51,9 @@ public sealed class TUnitWireMockLogger : IWireMockLogger
}
/// <inheritdoc />
public void Error(string formatString, Exception exception)
public void Error(string message, Exception exception)
{
_tUnitLogger.LogError(Format("Error", formatString, exception.Message), exception);
_tUnitLogger.LogError(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{

View File

@@ -21,6 +21,8 @@
<ItemGroup>
<Compile Include="..\WireMock.Net\Http\HttpClientFactory2.cs" Link="Http\HttpClientFactory2.cs" />
<Compile Include="..\WireMock.Net\Util\EnhancedFileSystemWatcher.cs" Link="Utils\EnhancedFileSystemWatcher.cs" />
<Compile Include="..\WireMock.Net\Util\PortUtils.cs" Link="Util\PortUtils.cs" />
<Compile Include="..\WireMock.Net\Constants\WireMockConstants.cs" Link="Constants\WireMockConstants.cs" />
</ItemGroup>
<ItemGroup>
@@ -28,6 +30,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Testcontainers" Version="4.0.0" />
</ItemGroup>

View File

@@ -1,9 +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;
namespace WireMock.Net.Testcontainers;
@@ -24,6 +27,10 @@ public sealed class WireMockConfiguration : ContainerConfiguration
public bool HasBasicAuthentication => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password);
public List<string> AdditionalUrls { get; private set; } = [];
public Dictionary<string, string[]> ProtoDefinitions { get; set; } = new();
public WireMockConfiguration(string? username = null, string? password = null)
{
Username = username;
@@ -70,6 +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);
}
/// <summary>
@@ -94,4 +103,43 @@ public sealed class WireMockConfiguration : ContainerConfiguration
WatchStaticMappingsInSubdirectories = includeSubDirectories;
return this;
}
/// <summary>
/// An additional Url on which WireMock listens.
/// </summary>
/// <param name="url">The url to add.</param>
/// <returns><see cref="WireMockConfiguration"/></returns>
public WireMockConfiguration WithAdditionalUrl(string url)
{
AdditionalUrls.Add(Guard.NotNullOrWhiteSpace(url));
return this;
}
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinition">The ProtoDefinition as text.</param>
/// <returns><see cref="WireMockConfiguration"/></returns>
public WireMockConfiguration AddProtoDefinition(string id, params string[] protoDefinition)
{
Guard.NotNullOrWhiteSpace(id);
Guard.NotNullOrEmpty(protoDefinition);
ProtoDefinitions[id] = protoDefinition;
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

@@ -1,7 +1,9 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +12,7 @@ using DotNet.Testcontainers.Containers;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using RestEase;
using Stef.Validation;
using WireMock.Client;
using WireMock.Client.Extensions;
using WireMock.Http;
@@ -30,6 +33,7 @@ public sealed class WireMockContainer : DockerContainer
private IWireMockAdminApi? _adminApi;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private IDictionary<int, Uri>? _publicUris;
/// <summary>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
@@ -37,9 +41,9 @@ public sealed class WireMockContainer : DockerContainer
/// <param name="configuration">The container configuration.</param>
public WireMockContainer(WireMockConfiguration configuration) : base(configuration)
{
_configuration = Stef.Validation.Guard.NotNull(configuration);
_configuration = Guard.NotNull(configuration);
Started += WireMockContainer_Started;
Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs);
}
/// <summary>
@@ -48,6 +52,21 @@ public sealed class WireMockContainer : DockerContainer
[PublicAPI]
public string GetPublicUrl() => GetPublicUri().ToString();
/// <summary>
/// Gets the public Urls as a dictionary with the internal port as the key.
/// </summary>
[PublicAPI]
public IDictionary<int, string> GetPublicUrls() => GetPublicUris().ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString());
/// <summary>
/// Gets the mapped public port for the given container port.
/// </summary>
[PublicAPI]
public string GetMappedPublicUrl(int containerPort)
{
return GetPublicUris()[containerPort].ToString();
}
/// <summary>
/// Create a RestEase Admin client which can be used to call the admin REST endpoint.
/// </summary>
@@ -121,7 +140,7 @@ public sealed class WireMockContainer : DockerContainer
await ReloadStaticMappingsAsync(target, ct);
}
}
/// <summary>
/// Reload the static mappings.
/// </summary>
@@ -157,8 +176,6 @@ public sealed class WireMockContainer : DockerContainer
_enhancedFileSystemWatcher = null;
}
Started -= WireMockContainer_Started;
return base.DisposeAsyncCore();
}
@@ -177,10 +194,17 @@ public sealed class WireMockContainer : DockerContainer
}
}
private void WireMockContainer_Started(object sender, EventArgs e)
private async Task WireMockContainerStartedAsync(object sender, EventArgs e)
{
_adminApi = CreateWireMockAdminClient();
RegisterEnhancedFileSystemWatcher();
await CallAdditionalActionsAfterStartedAsync();
}
private void RegisterEnhancedFileSystemWatcher()
{
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
{
return;
@@ -196,9 +220,35 @@ public sealed class WireMockContainer : DockerContainer
_enhancedFileSystemWatcher.EnableRaisingEvents = true;
}
private async Task CallAdditionalActionsAfterStartedAsync()
{
foreach (var kvp in _configuration.ProtoDefinitions)
{
Logger.LogInformation("Adding ProtoDefinition {Id}", kvp.Key);
foreach (var protoDefinition in kvp.Value)
{
try
{
await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key);
}
}
}
}
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
{
await ReloadStaticMappingsAsync(args.FullPath);
try
{
await ReloadStaticMappingsAsync(args.FullPath);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error reloading static mappings from '{FullPath}'.", args.FullPath);
}
}
private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default)
@@ -207,5 +257,27 @@ public sealed class WireMockContainer : DockerContainer
await ReloadStaticMappingsAsync(cancellationToken);
}
private Uri GetPublicUri() => new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(ContainerPort)).Uri;
private Uri GetPublicUri() => GetPublicUris()[ContainerPort];
private IDictionary<int, Uri> GetPublicUris()
{
if (_publicUris != null)
{
return _publicUris;
}
_publicUris = _configuration.ExposedPorts.Keys
.Select(int.Parse)
.ToDictionary(port => port, port => new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(port)).Uri);
foreach (var url in _configuration.AdditionalUrls)
{
if (PortUtils.TryExtract(url, out _, out _, out _, out _, out var port))
{
_publicUris[port] = new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(port)).Uri;
}
}
return _publicUris;
}
}

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Runtime.InteropServices;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
@@ -8,6 +9,7 @@ using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Net.Testcontainers.Utils;
using WireMock.Util;
namespace WireMock.Net.Testcontainers;
@@ -132,6 +134,53 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
}
/// <summary>
/// Use Http version 2.
/// </summary>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder WithHttp2()
{
return WithCommand("--UseHttp2 true");
}
/// <summary>
/// Adds another URL to the WireMock container. By default, the WireMock container will listen on <c>http://*:80</c>.
///
/// This method can be used to also host the WireMock container on another port or protocol (like grpc).
/// </summary>
/// <example>grpc://*:9090</example>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder AddUrl(string url)
{
if (!PortUtils.TryExtract(Guard.NotNullOrEmpty(url), out _, out _, out _, out _, out var port))
{
throw new ArgumentException("The URL is not valid.", nameof(url));
}
DockerResourceConfiguration.WithAdditionalUrl(url);
return WithPortBinding(port, true);
}
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinition">The ProtoDefinition as text.</param>
/// <returns><see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder AddProtoDefinition(string id, params string[] protoDefinition)
{
Guard.NotNullOrWhiteSpace(id);
Guard.NotNullOrEmpty(protoDefinition);
DockerResourceConfiguration.AddProtoDefinition(id, protoDefinition);
return this;
}
private WireMockContainerBuilder WithCommand(string param, bool value)
{
return !value ? this : WithCommand($"{param} true");
@@ -172,6 +221,11 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
builder = builder.WithBindMount(builder.DockerResourceConfiguration.StaticMappingsPath, ContainerInfoProvider.Info[_imageOS.Value].MappingsPath);
}
if (builder.DockerResourceConfiguration.AdditionalUrls.Any())
{
builder = builder.WithCommand($"--Urls http://*:80 {string.Join(" ", builder.DockerResourceConfiguration.AdditionalUrls)}");
}
builder.Validate();
return new WireMockContainer(builder.DockerResourceConfiguration);

View File

@@ -50,15 +50,15 @@ public sealed class TestOutputHelperWireMockLogger : IWireMockLogger
}
/// <inheritdoc />
public void Error(string formatString, Exception exception)
public void Error(string message, Exception exception)
{
_testOutputHelper.WriteLine(Format("Error", formatString, exception.Message));
_testOutputHelper.WriteLine(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{
ae.Handle(ex =>
{
_testOutputHelper.WriteLine(Format("Error", "Exception {0}", ex.Message));
_testOutputHelper.WriteLine(Format("Error", "Exception {0}", ex));
return true;
});
}

View File

@@ -66,6 +66,12 @@ internal static class BodyDataMatchScoreCalculator
return stringMatcher.IsMatch(requestMessage.BodyAsString);
}
// In case the matcher is a IProtoBufMatcher, use the BodyAsBytes to match on.
if (matcher is IProtoBufMatcher protoBufMatcher)
{
return protoBufMatcher.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult();
}
return default;
}
}

View File

@@ -2,7 +2,6 @@
#if PROTOBUF
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ProtoBufJsonConverter;
@@ -28,7 +27,7 @@ public class ProtoBufMatcher : IProtoBufMatcher
/// <summary>
/// The Func to define the proto definition as id or texts.
/// </summary>
public Func<IdOrTexts> ProtoDefinition { get; }
public Func<IdOrTexts> ProtoDefinition { get; internal set; }
/// <summary>
/// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".

View File

@@ -142,6 +142,6 @@ public class RequestMessageParamMatcher : IRequestMatcher
}
}
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : default;
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : 0;
}
}

View File

@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using JsonConverter.Abstractions;
using WireMock.Matchers;
using WireMock.Util;

View File

@@ -5,8 +5,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
@@ -71,6 +73,19 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
return _requestMatchers.OfType<T>().FirstOrDefault(func);
}
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
{
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
if (protoBufMatcher != null)
{
return true;
}
var bodyMatcher = GetRequestMessageMatcher<RequestMessageBodyMatcher>();
protoBufMatcher = bodyMatcher?.Matchers?.OfType<IProtoBufMatcher>().FirstOrDefault();
return protoBufMatcher != null;
}
private IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
{
foreach (var existing in _requestMatchers.OfType<T>().ToArray())

View File

@@ -49,7 +49,7 @@ public partial class Response
public IResponseBuilder WithTrailingHeader(string name, params string[] values)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
throw new System.NotSupportedException("The WithTrailingHeader method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(name);
@@ -63,7 +63,7 @@ public partial class Response
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
throw new System.NotSupportedException("The WithTrailingHeaders method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
@@ -77,7 +77,7 @@ public partial class Response
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string[]> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
throw new System.NotSupportedException("The WithTrailingHeaders method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);

View File

@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Proxy;
using WireMock.RequestBuilders;
using WireMock.Settings;
@@ -264,16 +263,15 @@ public partial class Response : IResponseBuilder
if (UseTransformer)
{
// Check if the body matcher is a RequestMessageProtoBufMatcher and try to decode the byte-array to a BodyAsJson.
if (mapping.RequestMatcher is Request requestMatcher && requestMessage is RequestMessage request)
// If the body matcher is a RequestMessageProtoBufMatcher or BodyMatcher with a ProtoBufMatcher then try to decode the byte-array to a BodyAsJson.
if (mapping.RequestMatcher is Request request && requestMessage is RequestMessage requestMessageImplementation)
{
var protoBufMatcher = requestMatcher.GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
if (protoBufMatcher != null)
if (request.TryGetProtoBufMatcher(out var protoBufMatcher))
{
var decoded = await protoBufMatcher.DecodeAsync(request.BodyData?.BodyAsBytes).ConfigureAwait(false);
var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null)
{
request.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
}
}
}

View File

@@ -379,7 +379,7 @@ internal class MappingConverter(MatcherMapper mapper)
}
var bodyMatchers =
protoBufMatcher?.Matcher != null ? new[] { protoBufMatcher.Matcher } : null ??
protoBufMatcher?.Matcher != null ? [protoBufMatcher.Matcher] : null ??
multiPartMatcher?.Matchers ??
graphQLMatcher?.Matchers ??
bodyMatcher?.Matchers;

View File

@@ -220,7 +220,7 @@ internal class MatcherMapper
{
model.Pattern = texts[0];
}
else
else if (texts.Count > 1)
{
model.Patterns = texts.Cast<object>().ToArray();
}
@@ -296,27 +296,9 @@ internal class MatcherMapper
{
var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher;
IdOrTexts protoDefinitionAsIdOrTexts;
if (protoDefinitions.Count == 1)
{
var idOrText = protoDefinitions[0];
if (_settings.ProtoDefinitions?.TryGetValue(idOrText, out var protoDefinitionFromSettings) == true)
{
protoDefinitionAsIdOrTexts = new(idOrText, protoDefinitionFromSettings);
}
else
{
protoDefinitionAsIdOrTexts = new(null, protoDefinitions);
}
}
else
{
protoDefinitionAsIdOrTexts = new(null, protoDefinitions);
}
return new ProtoBufMatcher(
() => protoDefinitionAsIdOrTexts,
matcher!.ProtoBufMessageType!,
() => ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitions.ToArray()),
matcher.ProtoBufMessageType!,
matchBehaviour ?? MatchBehaviour.AcceptOnMatch,
objectMatcher
);

View File

@@ -123,14 +123,14 @@ public interface IRespondWithAProvider
void ThenRespondWithStatusCode(HttpStatusCode code);
/// <summary>
/// Sets the the scenario.
/// Sets the scenario.
/// </summary>
/// <param name="scenario">The scenario.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider InScenario(string scenario);
/// <summary>
/// Sets the the scenario with an integer value.
/// Sets the scenario with an integer value.
/// </summary>
/// <param name="scenario">The scenario.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
@@ -220,7 +220,7 @@ public interface IRespondWithAProvider
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// e.g. lookup a path in this object using
/// <param name="data">The data dictionary object.</param>
/// <example>
/// lookup data "1"

View File

@@ -17,7 +17,7 @@ using WireMock.Util;
namespace WireMock.Server;
/// <summary>
/// The respond with a provider.
/// The RespondWithAProvider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
@@ -37,7 +37,6 @@ internal class RespondWithAProvider : IRespondWithAProvider
private int _timesInSameState = 1;
private bool? _useWebhookFireAndForget;
private double? _probability;
private IdOrTexts? _protoDefinition;
private GraphQLSchemaDetails? _graphQLSchemaDetails;
public Guid Guid { get; private set; }
@@ -48,6 +47,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
public object? Data { get; private set; }
public IdOrTexts? ProtoDefinition { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
@@ -104,9 +105,9 @@ internal class RespondWithAProvider : IRespondWithAProvider
mapping.WithProbability(_probability.Value);
}
if (_protoDefinition != null)
if (ProtoDefinition != null)
{
mapping.WithProtoDefinition(_protoDefinition.Value);
mapping.WithProtoDefinition(ProtoDefinition.Value);
}
_registrationCallback(mapping, _saveToFile);
@@ -296,7 +297,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
Guard.NotNull(url);
Guard.NotNull(method);
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)];
if (body != null)
{
@@ -323,7 +324,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
Guard.NotNull(url);
Guard.NotNull(method);
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)];
if (body != null)
{
@@ -355,23 +356,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
{
Guard.NotNull(protoDefinitionOrId);
if (protoDefinitionOrId.Length == 1)
{
var idOrText = protoDefinitionOrId[0];
if (_settings.ProtoDefinitions?.TryGetValue(idOrText, out var protoDefinitions) == true)
{
_protoDefinition = new(idOrText, protoDefinitions);
}
else
{
_protoDefinition = new(null, protoDefinitionOrId);
}
}
else
{
_protoDefinition = new(null, protoDefinitionOrId);
}
ProtoDefinition = ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitionOrId);
return this;
}

View File

@@ -68,6 +68,7 @@ public partial class WireMockServer
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
public RegexMatcher ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$");
public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$");
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$");
}
#region InitAdmin
@@ -147,6 +148,9 @@ public partial class WireMockServer
// __admin/openapi
Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings));
Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings));
// __admin/protodefinitions/{id}
Given(Request.Create().WithPath(_adminPaths.ProtoDefinitionsIdPathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ProtoDefinitionAdd));
}
#endregion
@@ -369,7 +373,7 @@ public partial class WireMockServer
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var code = _mappingBuilder.ToCSharpCode(guid, GetMappingConverterType(requestMessage));
var code = _mappingBuilder.ToCSharpCode(guid, GetEnumFromQuery(requestMessage, MappingConverterType.Server));
if (code is null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
@@ -383,15 +387,16 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing");
}
private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage)
private static TEnum GetEnumFromQuery<TEnum>(IRequestMessage requestMessage, TEnum defaultValue)
where TEnum : struct
{
if (requestMessage.QueryIgnoreCase?.TryGetValue(nameof(MappingConverterType), out var values) == true &&
Enum.TryParse(values.FirstOrDefault(), true, out MappingConverterType parsed))
if (requestMessage.QueryIgnoreCase?.TryGetValue(typeof(TEnum).Name, out var values) == true &&
Enum.TryParse<TEnum>(values.FirstOrDefault(), true, out var parsed))
{
return parsed;
}
return MappingConverterType.Server;
return defaultValue;
}
private IMapping? FindMappingByGuid(IRequestMessage requestMessage)
@@ -465,7 +470,7 @@ public partial class WireMockServer
private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage)
{
var converterType = GetMappingConverterType(requestMessage);
var converterType = GetEnumFromQuery(requestMessage, MappingConverterType.Server);
var code = _mappingBuilder.ToCSharpCode(converterType);

View File

@@ -13,6 +13,22 @@ public partial class WireMockServer
{
private static readonly Encoding[] FileBodyIsString = [Encoding.UTF8, Encoding.ASCII];
#region ProtoDefinitions/{id}
private IResponseMessage ProtoDefinitionAdd(IRequestMessage requestMessage)
{
if (requestMessage.Body is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var id = requestMessage.Path.Split('/').Last();
AddProtoDefinition(id, requestMessage.Body);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added");
}
#endregion
#region Files/{filename}
private IResponseMessage FilePost(IRequestMessage requestMessage)
{

View File

@@ -42,9 +42,9 @@ public partial class WireMockServer
Guard.NotNull(mappingModel.Request);
Guard.NotNull(mappingModel.Response);
var requestBuilder = InitRequestBuilder(mappingModel.Request);
var request = (Request)InitRequestBuilder(mappingModel.Request, mappingModel);
var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true);
var respondProvider = Given(request, mappingModel.SaveToFile == true);
if (guid != null)
{
@@ -116,13 +116,23 @@ public partial class WireMockServer
respondProvider.WithProbability(mappingModel.Probability.Value);
}
// ProtoDefinition is defined at Mapping level
if (mappingModel.ProtoDefinition != null)
{
respondProvider.WithProtoDefinition(mappingModel.ProtoDefinition);
}
else if (mappingModel.ProtoDefinitions != null)
{
respondProvider.WithProtoDefinition(mappingModel.ProtoDefinitions);
}
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);
return respondProvider.Guid;
}
private IRequestBuilder InitRequestBuilder(RequestModel requestModel)
private IRequestBuilder InitRequestBuilder(RequestModel requestModel, MappingModel? mappingModel = null)
{
var requestBuilder = Request.Create();
@@ -216,7 +226,7 @@ public partial class WireMockServer
if (requestModel.Params != null)
{
foreach (var paramModel in requestModel.Params.Where(p => p is { Matchers: { } }))
foreach (var paramModel in requestModel.Params.Where(p => p is { Matchers: not null }))
{
var ignoreCase = paramModel.IgnoreCase == true;
requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
@@ -225,7 +235,15 @@ public partial class WireMockServer
if (requestModel.Body?.Matcher != null)
{
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)!);
var bodyMatcher = _matcherMapper.Map(requestModel.Body.Matcher)!;
#if PROTOBUF
// If the BodyMatcher is a ProtoBufMatcher, and if ProtoDefinition is defined on Mapping-level, set the ProtoDefinition from that Mapping.
if (bodyMatcher is ProtoBufMatcher protoBufMatcher && mappingModel?.ProtoDefinition != null)
{
protoBufMatcher.ProtoDefinition = () => ProtoDefinitionHelper.GetIdOrTexts(_settings, mappingModel.ProtoDefinition);
}
#endif
requestBuilder = requestBuilder.WithBody(bodyMatcher);
}
else if (requestModel.Body?.Matchers != null)
{
@@ -308,7 +326,7 @@ public partial class WireMockServer
}
else if (responseModel.HeadersRaw != null)
{
foreach (string headerLine in responseModel.HeadersRaw.Split(["\n", "\r\n"], StringSplitOptions.RemoveEmptyEntries))
foreach (var headerLine in responseModel.HeadersRaw.Split(["\n", "\r\n"], StringSplitOptions.RemoveEmptyEntries))
{
int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal);
string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t');
@@ -317,6 +335,22 @@ public partial class WireMockServer
}
}
if (responseModel.TrailingHeaders != null)
{
foreach (var entry in responseModel.TrailingHeaders)
{
if (entry.Value is string value)
{
responseBuilder.WithTrailingHeader(entry.Key, value);
}
else
{
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
responseBuilder.WithTrailingHeader(entry.Key, headers);
}
}
}
if (responseModel.BodyAsBytes != null)
{
responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding));
@@ -327,7 +361,26 @@ public partial class WireMockServer
}
else if (responseModel.BodyAsJson != null)
{
responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true);
if (responseModel.ProtoBufMessageType != null)
{
if (responseModel.ProtoDefinition != null)
{
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoDefinition, responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
else if (responseModel.ProtoDefinitions != null)
{
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoDefinitions, responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
else
{
// ProtoDefinition(s) is/are defined at Mapping/Server level
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
}
else
{
responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true);
}
}
else if (responseModel.BodyAsFile != null)
{

View File

@@ -26,7 +26,6 @@ public partial class WireMockServer
[PublicAPI]
public IReadOnlyList<ILogEntry> LogEntries => _options.LogEntries.ToArray();
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers)

View File

@@ -602,7 +602,14 @@ public partial class WireMockServer : IWireMockServer
_settings.ProtoDefinitions ??= new Dictionary<string, string[]>();
_settings.ProtoDefinitions[id] = protoDefinition;
if (_settings.ProtoDefinitions.TryGetValue(id, out var existingProtoDefinitions))
{
_settings.ProtoDefinitions[id] = existingProtoDefinitions.Union(protoDefinition).ToArray();
}
else
{
_settings.ProtoDefinitions[id] = protoDefinition;
}
return this;
}

View File

@@ -84,4 +84,12 @@ internal class ConcurrentObservableCollection<T> : ObservableCollection<T>
return Items.ToList();
}
}
public T[] ToArray()
{
lock (_lockObject)
{
return Items.ToArray();
}
}
}

View File

@@ -1,7 +1,9 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
@@ -17,22 +19,67 @@ internal static class PortUtils
private static readonly Regex UrlDetailsRegex = new(@"^((?<proto>\w+)://)(?<host>[^/]+?):(?<port>\d+)\/?$", RegexOptions.Compiled, WireMockConstants.DefaultRegexTimeout);
/// <summary>
/// Finds a free TCP port.
/// Finds a random, free port to be listened on.
/// </summary>
/// <remarks>see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net.</remarks>
/// <returns>A random, free port to be listened on.</returns>
/// <remarks>https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/src/webdriver/Internal/PortUtilities.cs</remarks>
public static int FindFreeTcpPort()
{
TcpListener? tcpListener = null;
// Locate a free port on the local machine by binding a socket to an IPEndPoint using IPAddress.Any and port 0.
// The socket will select a free port.
var portSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
return ((IPEndPoint)tcpListener.LocalEndpoint).Port;
var socketEndPoint = new IPEndPoint(IPAddress.Any, 0);
portSocket.Bind(socketEndPoint);
socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint!;
return socketEndPoint.Port;
}
finally
{
tcpListener?.Stop();
#if !NETSTANDARD1_3
portSocket.Close();
#endif
portSocket.Dispose();
}
}
/// <summary>
/// Finds a specified number of random, free ports to be listened on.
/// </summary>
/// <param name="count">The number of free ports to find.</param>
/// <returns>A list of random, free ports to be listened on.</returns>
public static IReadOnlyList<int> FindFreeTcpPorts(int count)
{
var sockets = Enumerable
.Range(0, count)
.Select(_ => new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
.ToArray();
var freePorts = new List<int>();
try
{
foreach (var socket in sockets)
{
var socketEndPoint = new IPEndPoint(IPAddress.Any, 0);
socket.Bind(socketEndPoint);
socketEndPoint = (IPEndPoint)socket.LocalEndPoint!;
freePorts.Add(socketEndPoint.Port);
}
return freePorts;
}
finally
{
foreach (var socket in sockets)
{
#if !NETSTANDARD1_3
socket.Close();
#endif
socket.Dispose();
}
}
}
@@ -45,7 +92,7 @@ internal static class PortUtils
isHttp2 = false;
protocol = null;
host = null;
port = default;
port = 0;
var match = UrlDetailsRegex.Match(url);
if (match.Success)

View File

@@ -0,0 +1,27 @@
// Copyright © WireMock.Net
using WireMock.Models;
using WireMock.Settings;
namespace WireMock.Util;
internal static class ProtoDefinitionHelper
{
internal static IdOrTexts GetIdOrTexts(WireMockServerSettings settings, params string[] protoDefinitionOrId)
{
switch (protoDefinitionOrId.Length)
{
case 1:
var idOrText = protoDefinitionOrId[0];
if (settings.ProtoDefinitions?.TryGetValue(idOrText, out var protoDefinitions) == true)
{
return new(idOrText, protoDefinitions);
}
return new(null, protoDefinitionOrId);
default:
return new(null, protoDefinitionOrId);
}
}
}

View File

@@ -20,12 +20,12 @@ internal static class QueryStringParser
{
if (queryString is null)
{
nameValueCollection = default;
nameValueCollection = null;
return false;
}
var parts = queryString!
.Split(new[] { "&" }, StringSplitOptions.RemoveEmptyEntries)
.Split(["&"], StringSplitOptions.RemoveEmptyEntries)
.Select(parameter => parameter.Split('='))
.Distinct();
@@ -50,18 +50,6 @@ internal static class QueryStringParser
var queryParameterMultipleValueSupport = support ?? QueryParameterMultipleValueSupport.All;
string[] JoinParts(string[] parts)
{
if (parts.Length > 1)
{
return queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Comma) ?
parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) : // Support "?key=1,2"
new[] { parts[1] };
}
return new string[0];
}
var splitOn = new List<string>();
if (queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Ampersand))
{
@@ -74,8 +62,24 @@ internal static class QueryStringParser
return queryString!.TrimStart('?')
.Split(splitOn.ToArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
.GroupBy(parts => parts[0], JoinParts)
.ToDictionary(grouping => grouping.Key, grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode)));
.Select(parameter => new { hasEqualSign = parameter.Contains('='), parts = parameter.Split(['='], 2, StringSplitOptions.RemoveEmptyEntries) })
.GroupBy(x => x.parts[0], y => JoinParts(y.hasEqualSign, y.parts))
.ToDictionary
(
grouping => grouping.Key,
grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode).OfType<string>())
);
string[] JoinParts(bool hasEqualSign, string[] parts)
{
if (parts.Length > 1)
{
return queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Comma) ?
parts[1].Split([","], StringSplitOptions.RemoveEmptyEntries) : // Support "?key=1,2"
[parts[1]];
}
return hasEqualSign ? [string.Empty] : []; // Return empty string if equal sign with no value (#1247)
}
}
}

View File

@@ -2,6 +2,7 @@
#if PROTOBUF
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using ProtoBufJsonConverter;
@@ -9,21 +10,29 @@ using Stef.Validation;
namespace WireMock.Util;
/// <summary>
/// This resolver is used to resolve the extra ProtoDefinition files.
/// It assumes that:
/// - the first ProtoDefinition file is the main ProtoDefinition file.
/// - the first commented line of each extra ProtoDefinition file is the filename which is used in the import of the other ProtoDefinition file(s).
/// </summary>
internal class WireMockProtoFileResolver : IProtoFileResolver
{
private readonly Dictionary<string, string> _files = new();
public WireMockProtoFileResolver(IReadOnlyCollection<string> protoDefinitions)
{
if (Guard.NotNullOrEmpty(protoDefinitions).Count() > 1)
if (Guard.NotNullOrEmpty(protoDefinitions).Count() <= 1)
{
foreach (var extraProtoDefinition in protoDefinitions.Skip(1))
return;
}
foreach (var extraProtoDefinition in protoDefinitions.Skip(1))
{
var firstNonEmptyLine = extraProtoDefinition.Split(['\r', '\n']).FirstOrDefault(l => !string.IsNullOrEmpty(l));
if (firstNonEmptyLine != null && TryGetValidFileName(firstNonEmptyLine.TrimStart(['/', ' ']), out var validFileName))
{
var firstNonEmptyLine = extraProtoDefinition.Split(['\r', '\n']).FirstOrDefault(l => !string.IsNullOrEmpty(l));
if (firstNonEmptyLine != null)
{
_files.Add(firstNonEmptyLine.TrimStart(['\r', '\n', '/', ' ']), extraProtoDefinition);
}
_files.Add(validFileName, extraProtoDefinition);
}
}
}
@@ -42,5 +51,17 @@ internal class WireMockProtoFileResolver : IProtoFileResolver
throw new FileNotFoundException($"The ProtoDefinition '{path}' was not found.");
}
private static bool TryGetValidFileName(string fileName, [NotNullWhen(true)] out string? validFileName)
{
if (!fileName.Any(c => Path.GetInvalidFileNameChars().Contains(c)))
{
validFileName = fileName;
return true;
}
validFileName = null;
return false;
}
}
#endif

View File

@@ -182,13 +182,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Handlebars.Net.Helpers" Version="2.4.9" />
<!--<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.4.9" />-->
<PackageReference Include="Handlebars.Net.Helpers.Humanizer" Version="2.4.9" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.4.9" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.4.9" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.4.9" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.4.9" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.4.10" />
<!--<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.4.10" />-->
<PackageReference Include="Handlebars.Net.Helpers.Humanizer" Version="2.4.10" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.4.10" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.4.10" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.4.10" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.4.10" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' ">

View File

@@ -37,7 +37,32 @@ message HelloReply {
public async Task IWireMockAdminApi_GetMappingsAsync_WithBodyAsProtoBuf_ShouldReturnCorrectMappingModels()
{
// Arrange
using var server = WireMockServer.StartWithAdminInterface();
using var server = Given_WithBodyAsProtoBuf_AddedToServer();
// Act
var api = RestClient.For<IWireMockAdminApi>(server.Url);
var getMappingsResult = await api.GetMappingsAsync().ConfigureAwait(false);
await Verifier.Verify(getMappingsResult, VerifySettings);
}
[Fact]
public async Task HttpClient_GetMappingsAsync_WithBodyAsProtoBuf_ShouldReturnCorrectMappingModels()
{
// Arrange
using var server = Given_WithBodyAsProtoBuf_AddedToServer();
// Act
var client = server.CreateClient();
var getMappingsResult = await client.GetStringAsync("/__admin/mappings").ConfigureAwait(false);
await Verifier.VerifyJson(getMappingsResult, VerifySettings);
}
public WireMockServer Given_WithBodyAsProtoBuf_AddedToServer()
{
// Arrange
var server = WireMockServer.StartWithAdminInterface();
var protoBufJsonMatcher = new JsonPartialWildcardMatcher(new { name = "*" });
@@ -122,13 +147,7 @@ message HelloReply {
.WithTransformer()
);
// Act
var api = RestClient.For<IWireMockAdminApi>(server.Url);
var getMappingsResult = await api.GetMappingsAsync().ConfigureAwait(false);
await Verifier.Verify(getMappingsResult, VerifySettings);
server.Stop();
return server;
}
}
#endif

View File

@@ -0,0 +1,235 @@
[
{
Guid: Guid_1,
UpdatedAt: DateTimeOffset_1,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /grpc/greet.Greeter/SayHello,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: ProtoBufMatcher,
Pattern:
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
,
ContentMatcher: {
Name: JsonPartialWildcardMatcher,
Pattern: {
name: *
},
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}
}
},
Response: {
BodyAsJson: {
message: hello {{request.BodyAsJson.name}}
},
UseTransformer: true,
TransformerType: Handlebars,
TransformerReplaceNodeOptions: EvaluateAndTryToConvert,
Headers: {
Content-Type: application/grpc
},
TrailingHeaders: {
grpc-status: 0
},
ProtoDefinition:
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
,
ProtoBufMessageType: greet.HelloReply
}
},
{
Guid: Guid_2,
UpdatedAt: DateTimeOffset_2,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /grpc2/greet.Greeter/SayHello,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: ProtoBufMatcher,
ContentMatcher: {
Name: JsonPartialWildcardMatcher,
Pattern: {
name: *
},
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}
}
},
Response: {
BodyAsJson: {
message: hello {{request.BodyAsJson.name}}
},
UseTransformer: true,
TransformerType: Handlebars,
TransformerReplaceNodeOptions: EvaluateAndTryToConvert,
Headers: {
Content-Type: application/grpc
},
TrailingHeaders: {
grpc-status: 0
},
ProtoBufMessageType: greet.HelloReply
},
ProtoDefinition:
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
},
{
Guid: Guid_3,
UpdatedAt: DateTimeOffset_3,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /grpc3/greet.Greeter/SayHello,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: ProtoBufMatcher,
ContentMatcher: {
Name: JsonPartialWildcardMatcher,
Pattern: {
name: *
},
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}
}
},
Response: {
BodyAsJson: {
message: hello {{request.BodyAsJson.name}}
},
UseTransformer: true,
TransformerType: Handlebars,
TransformerReplaceNodeOptions: EvaluateAndTryToConvert,
Headers: {
Content-Type: application/grpc
},
TrailingHeaders: {
grpc-status: 0
},
ProtoBufMessageType: greet.HelloReply
},
ProtoDefinition: my-greeter
},
{
Guid: Guid_4,
UpdatedAt: DateTimeOffset_4,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /grpc4/greet.Greeter/SayHello,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: ProtoBufMatcher,
ProtoBufMessageType: greet.HelloRequest
}
}
},
Response: {
BodyAsJson: {
message: hello {{request.BodyAsJson.name}}
},
UseTransformer: true,
TransformerType: Handlebars,
TransformerReplaceNodeOptions: EvaluateAndTryToConvert,
Headers: {
Content-Type: application/grpc
},
TrailingHeaders: {
grpc-status: 0
},
ProtoBufMessageType: greet.HelloReply
},
ProtoDefinition: my-greeter
}
]

View File

@@ -0,0 +1,172 @@
// Copyright © WireMock.Net
#if !(NET452 || NET461 || NETCOREAPP3_1)
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using NFluent;
using RestEase;
using WireMock.Admin.Mappings;
using WireMock.Client;
using WireMock.Constants;
using WireMock.Models;
using WireMock.Server;
using Xunit;
namespace WireMock.Net.Tests.AdminApi;
public partial class WireMockAdminApiTests
{
public static string RemoveLineContainingUpdatedAt(string text)
{
var lines = text.Split([Environment.NewLine], StringSplitOptions.None);
var filteredLines = lines.Where(line => !line.Contains("\"UpdatedAt\": "));
return string.Join(Environment.NewLine, filteredLines);
}
[Theory]
[InlineData("protobuf-mapping-1.json", "351f0240-bba0-4bcb-93c6-1feba0fe0001")]
[InlineData("protobuf-mapping-2.json", "351f0240-bba0-4bcb-93c6-1feba0fe0002")]
[InlineData("protobuf-mapping-3.json", "351f0240-bba0-4bcb-93c6-1feba0fe0003")]
[InlineData("protobuf-mapping-4.json", "351f0240-bba0-4bcb-93c6-1feba0fe0004")]
public async Task HttpClient_PostMappingsAsync_ForProtoBufMapping(string mappingFile, string guid)
{
// Arrange
var mappingsJson = ReadMappingFile(mappingFile);
using var server = WireMockServer.StartWithAdminInterface();
var httpClient = server.CreateClient();
// Act
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
result.EnsureSuccessStatusCode();
// Assert
var mapping = await httpClient.GetStringAsync($"/__admin/mappings/{guid}");
mapping = RemoveLineContainingUpdatedAt(mapping);
mapping.Should().Be(mappingsJson);
}
[Fact]
public async Task IWireMockAdminApi_PostMappingsAsync()
{
// Arrange
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model1 = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt 1" },
Title = "test 1"
};
var model2 = new MappingModel
{
Request = new RequestModel { Path = "/2" },
Response = new ResponseModel { Body = "txt 2" },
Title = "test 2"
};
var result = await api.PostMappingsAsync(new[] { model1, model2 }).ConfigureAwait(false);
// Assert
Check.That(result).IsNotNull();
Check.That(result.Status).IsNotNull();
Check.That(result.Guid).IsNull();
Check.That(server.Mappings.Where(m => !m.IsAdminInterface)).HasSize(2);
server.Stop();
}
[Theory]
[InlineData(null, null)]
[InlineData(-1, -1)]
[InlineData(0, 0)]
[InlineData(200, 200)]
[InlineData("200", "200")]
public async Task IWireMockAdminApi_PostMappingAsync_WithStatusCode(object statusCode, object expectedStatusCode)
{
// Arrange
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt", StatusCode = statusCode },
Priority = 500,
Title = "test"
};
var result = await api.PostMappingAsync(model).ConfigureAwait(false);
// Assert
Check.That(result).IsNotNull();
Check.That(result.Status).IsNotNull();
Check.That(result.Guid).IsNotNull();
var mapping = server.Mappings.Single(m => m.Priority == 500);
Check.That(mapping).IsNotNull();
Check.That(mapping.Title).Equals("test");
var response = await mapping.ProvideResponseAsync(new RequestMessage(new UrlDetails("http://localhost/1"), "GET", "")).ConfigureAwait(false);
Check.That(response.Message.StatusCode).Equals(expectedStatusCode);
server.Stop();
}
[Fact]
public async Task IWireMockAdminApi_PostMappingsAsync_WithDuplicateGuids_Should_Return_400()
{
// Arrange
var guid = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f");
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model1WithGuid = new MappingModel
{
Guid = guid,
Request = new RequestModel { Path = "/1g" },
Response = new ResponseModel { Body = "txt 1g" },
Title = "test 1g"
};
var model2WithGuid = new MappingModel
{
Guid = guid,
Request = new RequestModel { Path = "/2g" },
Response = new ResponseModel { Body = "txt 2g" },
Title = "test 2g"
};
var model1 = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt 1" },
Title = "test 1"
};
var model2 = new MappingModel
{
Request = new RequestModel { Path = "/2" },
Response = new ResponseModel { Body = "txt 2" },
Title = "test 2"
};
var models = new[]
{
model1WithGuid,
model2WithGuid,
model1,
model2
};
var sutMethod = async () => await api.PostMappingsAsync(models);
var exceptionAssertions = await sutMethod.Should().ThrowAsync<ApiException>();
exceptionAssertions.Which.Content.Should().Be(@"{""Status"":""The following Guids are duplicate : '1b731398-4a5b-457f-a6e3-d65e541c428f' (Parameter 'mappingModels')""}");
server.Stop();
}
}
#endif

View File

@@ -184,124 +184,7 @@ public partial class WireMockAdminApiTests
server.Stop();
}
[Theory]
[InlineData(null, null)]
[InlineData(-1, -1)]
[InlineData(0, 0)]
[InlineData(200, 200)]
[InlineData("200", "200")]
public async Task IWireMockAdminApi_PostMappingAsync_WithStatusCode(object statusCode, object expectedStatusCode)
{
// Arrange
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt", StatusCode = statusCode },
Priority = 500,
Title = "test"
};
var result = await api.PostMappingAsync(model).ConfigureAwait(false);
// Assert
Check.That(result).IsNotNull();
Check.That(result.Status).IsNotNull();
Check.That(result.Guid).IsNotNull();
var mapping = server.Mappings.Single(m => m.Priority == 500);
Check.That(mapping).IsNotNull();
Check.That(mapping.Title).Equals("test");
var response = await mapping.ProvideResponseAsync(new RequestMessage(new UrlDetails("http://localhost/1"), "GET", "")).ConfigureAwait(false);
Check.That(response.Message.StatusCode).Equals(expectedStatusCode);
server.Stop();
}
[Fact]
public async Task IWireMockAdminApi_PostMappingsAsync()
{
// Arrange
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model1 = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt 1" },
Title = "test 1"
};
var model2 = new MappingModel
{
Request = new RequestModel { Path = "/2" },
Response = new ResponseModel { Body = "txt 2" },
Title = "test 2"
};
var result = await api.PostMappingsAsync(new[] { model1, model2 }).ConfigureAwait(false);
// Assert
Check.That(result).IsNotNull();
Check.That(result.Status).IsNotNull();
Check.That(result.Guid).IsNull();
Check.That(server.Mappings.Where(m => !m.IsAdminInterface)).HasSize(2);
server.Stop();
}
[Fact]
public async Task IWireMockAdminApi_PostMappingsAsync_WithDuplicateGuids_Should_Return_400()
{
// Arrange
var guid = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f");
var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
// Act
var model1WithGuid = new MappingModel
{
Guid = guid,
Request = new RequestModel { Path = "/1g" },
Response = new ResponseModel { Body = "txt 1g" },
Title = "test 1g"
};
var model2WithGuid = new MappingModel
{
Guid = guid,
Request = new RequestModel { Path = "/2g" },
Response = new ResponseModel { Body = "txt 2g" },
Title = "test 2g"
};
var model1 = new MappingModel
{
Request = new RequestModel { Path = "/1" },
Response = new ResponseModel { Body = "txt 1" },
Title = "test 1"
};
var model2 = new MappingModel
{
Request = new RequestModel { Path = "/2" },
Response = new ResponseModel { Body = "txt 2" },
Title = "test 2"
};
var models = new[]
{
model1WithGuid,
model2WithGuid,
model1,
model2
};
var sutMethod = async () => await api.PostMappingsAsync(models);
var exceptionAssertions = await sutMethod.Should().ThrowAsync<ApiException>();
exceptionAssertions.Which.Content.Should().Be(@"{""Status"":""The following Guids are duplicate : '1b731398-4a5b-457f-a6e3-d65e541c428f' (Parameter 'mappingModels')""}");
server.Stop();
}
[Fact]
public async Task IWireMockAdminApi_FindRequestsAsync()
@@ -1140,5 +1023,10 @@ text
// Assert
status.Status.Should().Be("Static Mappings reloaded");
}
private static string ReadMappingFile(string filename)
{
return File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings", filename));
}
}
#endif

View File

@@ -0,0 +1,10 @@
// Copyright © WireMock.Net
namespace WireMock.Net.Tests;
internal static class Constants
{
internal const int NumStaticMappings = 10;
internal const int NumAdminMappings = 36;
}

View File

@@ -0,0 +1,41 @@
// Copyright © WireMock.Net
#if PROTOBUF
using System;
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Util;
using Xunit;
namespace WireMock.Net.Tests.Grpc;
public class ProtoBufUtilsTests
{
[Fact]
public async Task GetProtoBufMessageWithHeader_MultipleProtoFiles()
{
// Arrange
var greet = await ReadProtoFileAsync("greet1.proto");
var request = await ReadProtoFileAsync("request.proto");
// Act
var responseBytes = await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(
[greet, request],
"greet.HelloRequest",
new
{
name = "hello"
}
);
// Assert
Convert.ToBase64String(responseBytes).Should().Be("AAAAAAcKBWhlbGxv");
}
private static Task<string> ReadProtoFileAsync(string filename)
{
return File.ReadAllTextAsync(Path.Combine(Directory.GetCurrentDirectory(), "Grpc", filename));
}
}
#endif

View File

@@ -2,19 +2,24 @@
#if PROTOBUF
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Google.Protobuf.WellKnownTypes;
using Greet;
using Grpc.Net.Client;
using NarrowIntegrationTest.Lookup;
using ExampleIntegrationTest.Lookup;
using WireMock.Constants;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Util;
using Xunit;
// ReSharper disable once CheckNamespace
@@ -486,20 +491,17 @@ message Other {
);
// Act
var channel = GrpcChannel.ForAddress(server.Url!);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "stef" });
var reply = await When_GrpcClient_Calls_SayHelloAsync(server.Url!);
// Assert
reply.Message.Should().Be("hello stef POST");
Then_ReplyMessage_Should_BeCorrect(reply);
}
[Fact]
public async Task WireMockServer_WithBodyAsProtoBuf_WithWellKnownTypes_Empty_UsingGrpcGeneratedClient()
{
// Arrange
var definition = await System.IO.File.ReadAllTextAsync("./Grpc/greet.proto");
var definition = await File.ReadAllTextAsync("./Grpc/greet.proto");
using var server = WireMockServer.Start(useHttp2: true);
@@ -532,7 +534,7 @@ message Other {
// Arrange
const int seconds = 1722301323;
const int nanos = 12300;
var definition = await System.IO.File.ReadAllTextAsync("./Grpc/greet.proto");
var definition = await File.ReadAllTextAsync("./Grpc/greet.proto");
using var server = WireMockServer.Start(useHttp2: true);
@@ -573,7 +575,7 @@ message Other {
// Arrange
const int seconds = 1722301323;
const int nanos = 12300;
var definition = await System.IO.File.ReadAllTextAsync("./Grpc/greet.proto");
var definition = await File.ReadAllTextAsync("./Grpc/greet.proto");
using var server = WireMockServer.Start(useHttp2: true);
@@ -612,7 +614,7 @@ message Other {
public async Task WireMockServer_WithBodyAsProtoBuf_Enum_UsingGrpcGeneratedClient()
{
// Arrange
var definition = await System.IO.File.ReadAllTextAsync("./Grpc/greet.proto");
var definition = await File.ReadAllTextAsync("./Grpc/greet.proto");
using var server = WireMockServer.Start(useHttp2: true);
@@ -653,7 +655,7 @@ message Other {
const int nanos = 12300;
const string version = "test";
const string correlationId = "correlation";
var definition = await System.IO.File.ReadAllTextAsync("./Grpc/policy.proto");
var definition = await File.ReadAllTextAsync("./Grpc/policy.proto");
using var server = WireMockServer.Start(useHttp2: true);
@@ -666,7 +668,7 @@ message Other {
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/grpc")
.WithTrailingHeader("grpc-status", "0")
.WithBodyAsProtoBuf(definition, "NarrowIntegrationTest.Lookup.GetVersionResponse",
.WithBodyAsProtoBuf(definition, "ExampleIntegrationTest.Lookup.GetVersionResponse",
new GetVersionResponse
{
Version = version,
@@ -675,9 +677,9 @@ message Other {
Seconds = seconds,
Nanos = nanos
},
Client = new NarrowIntegrationTest.Lookup.Client
Client = new ExampleIntegrationTest.Lookup.Client
{
ClientName = NarrowIntegrationTest.Lookup.Client.Types.Clients.BillingCenter,
ClientName = ExampleIntegrationTest.Lookup.Client.Types.Clients.Test,
CorrelationId = correlationId
}
}
@@ -693,8 +695,82 @@ message Other {
// Assert
reply.Version.Should().Be(version);
reply.DateHired.Should().Be(new Timestamp { Seconds = seconds, Nanos = nanos });
reply.Client.ClientName.Should().Be(NarrowIntegrationTest.Lookup.Client.Types.Clients.BillingCenter);
reply.Client.ClientName.Should().Be(ExampleIntegrationTest.Lookup.Client.Types.Clients.Test);
reply.Client.CorrelationId.Should().Be(correlationId);
}
[Fact]
public async Task WireMockServer_WithBodyAsProtoBuf_FromJson_UsingGrpcGeneratedClient()
{
var server = Given_When_ServerStarted_And_RunningOnHttpAndGrpc();
await Given_When_ProtoBufMappingIsAddedViaAdminInterfaceAsync(server, "protobuf-mapping-1.json");
var reply = await When_GrpcClient_Calls_SayHelloAsync(server.Urls[1]);
Then_ReplyMessage_Should_BeCorrect(reply);
}
[Fact]
public async Task WireMockServer_WithBodyAsProtoBuf_ServerProtoDefinitionFromJson_UsingGrpcGeneratedClient()
{
var server = Given_When_ServerStarted_And_RunningOnHttpAndGrpc();
Given_ProtoDefinition_IsAddedOnServerLevel(server);
await Given_When_ProtoBufMappingIsAddedViaAdminInterfaceAsync(server, "protobuf-mapping-3.json");
var reply = await When_GrpcClient_Calls_SayHelloAsync(server.Urls[1]);
Then_ReplyMessage_Should_BeCorrect(reply);
}
private static WireMockServer Given_When_ServerStarted_And_RunningOnHttpAndGrpc()
{
var ports = PortUtils.FindFreeTcpPorts(2);
var settings = new WireMockServerSettings
{
Urls = [$"http://*:{ports[0]}/", $"grpc://*:{ports[1]}/"],
StartAdminInterface = true
};
return WireMockServer.Start(settings);
}
private static void Given_ProtoDefinition_IsAddedOnServerLevel(WireMockServer server)
{
server.AddProtoDefinition("my-greeter", ReadProtoFile("greet.proto"));
}
private static async Task Given_When_ProtoBufMappingIsAddedViaAdminInterfaceAsync(WireMockServer server, string filename)
{
var mappingsJson = ReadMappingFile(filename);
using var httpClient = server.CreateClient();
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
result.EnsureSuccessStatusCode();
}
private static async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(string address)
{
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");
}
private static string ReadMappingFile(string filename)
{
return File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings", filename));
}
private static string ReadProtoFile(string filename)
{
return File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "Grpc", filename));
}
}
#endif

View File

@@ -0,0 +1,13 @@
syntax = "proto3";
import "request.proto";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloReply {
string message = 1;
}

View File

@@ -1,6 +1,6 @@
syntax = "proto3";
option csharp_namespace = "NarrowIntegrationTest.Lookup";
option csharp_namespace = "ExampleIntegrationTest.Lookup";
import "google/protobuf/timestamp.proto";
@@ -24,17 +24,8 @@ message Client {
string CorrelationId = 1;
enum Clients {
Unknown = 0;
QMS = 1;
BillingCenter = 2;
PAS = 3;
Payroll = 4;
Portal = 5;
SFO = 6;
QuoteAndBind = 7;
LegacyConversion = 8;
BindNow = 9;
PaymentPortal = 10 ;
PricingEngine = 11;
Other = 1;
Test = 2;
}
Clients ClientName = 2;
}

View File

@@ -0,0 +1,8 @@
// request.proto
syntax = "proto3";
package greet;
message HelloRequest {
string name = 1;
}

View File

@@ -43,6 +43,21 @@ public class RequestMessageParamMatcherTests
Check.That(score).IsEqualTo(0.5d);
}
[Fact]
public void RequestMessageParamMatcher_GetMatchingScore_KeyWithNoValuePresentInUrl_Returns1_0()
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key="), "GET", "127.0.0.1");
var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, "");
// Act
var result = new RequestMatchResult();
var score = matcher.GetMatchingScore(requestMessage, result);
// Assert
Check.That(score).IsEqualTo(1.0d);
}
[Fact]
public void RequestMessageParamMatcher_GetMatchingScore_KeyWith3ValuesPresentInUrl_And_With1ExactStringWith2Patterns_Returns0_66()
{

View File

@@ -0,0 +1,213 @@
// Copyright © WireMock.Net
#if NET6_0_OR_GREATER
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Greet;
using Grpc.Net.Client;
using WireMock.Constants;
using WireMock.Net.Testcontainers;
using Xunit;
namespace WireMock.Net.Tests.Testcontainers;
public partial class TestcontainersTests
{
[Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1()
{
// Act
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainer = new WireMockContainerBuilder()
.WithAutoRemove(true)
.WithCleanUp(true)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithCommand("--UseHttp2")
.WithCommand("--Urls", "http://*:80 grpc://*:9090")
.WithPortBinding(9090, true)
.Build();
try
{
await wireMockContainer.StartAsync().ConfigureAwait(false);
// Assert
using (new AssertionScope())
{
var logs = await wireMockContainer.GetLogsAsync(DateTime.MinValue);
logs.Should().NotBeNull();
var url = wireMockContainer.GetPublicUrl();
url.Should().NotBeNullOrWhiteSpace();
var urls = wireMockContainer.GetPublicUrls();
urls.Should().HaveCount(2);
var httpPort = wireMockContainer.GetMappedPublicPort(80);
httpPort.Should().BeGreaterThan(0);
var httpUrl = wireMockContainer.GetMappedPublicUrl(80);
httpUrl.Should().StartWith("http://");
var grpcPort = wireMockContainer.GetMappedPublicPort(9090);
grpcPort.Should().BeGreaterThan(0);
var grpcUrl = wireMockContainer.GetMappedPublicUrl(80);
grpcUrl.Should().StartWith("http://");
var adminClient = wireMockContainer.CreateWireMockAdminClient();
var settings = await adminClient.GetSettingsAsync();
settings.Should().NotBeNull();
}
}
finally
{
await wireMockContainer.StopAsync();
}
}
[Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2()
{
// Act
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainer = new WireMockContainerBuilder()
.WithAutoRemove(true)
.WithCleanUp(true)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.AddUrl("http://*:8080")
.AddUrl("grpc://*:9090")
.AddUrl("grpc://*:9091")
.Build();
try
{
await wireMockContainer.StartAsync().ConfigureAwait(false);
// Assert
using (new AssertionScope())
{
var logs = await wireMockContainer.GetLogsAsync(DateTime.MinValue);
logs.Should().NotBeNull();
var url = wireMockContainer.GetPublicUrl();
url.Should().NotBeNullOrWhiteSpace();
var urls = wireMockContainer.GetPublicUrls();
urls.Should().HaveCount(4);
foreach (var internalPort in new[] { 80, 8080, 9090, 9091 })
{
var publicPort = wireMockContainer.GetMappedPublicPort(internalPort);
publicPort.Should().BeGreaterThan(0);
var publicUrl = wireMockContainer.GetMappedPublicUrl(internalPort);
publicUrl.Should().StartWith("http://");
}
var adminClient = wireMockContainer.CreateWireMockAdminClient();
var settings = await adminClient.GetSettingsAsync();
settings.Should().NotBeNull();
}
}
finally
{
await wireMockContainer.StopAsync();
}
}
[Fact]
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionFromJson_UsingGrpcGeneratedClient()
{
var wireMockContainer = await Given_WireMockContainerIsStartedForHttpAndGrpcAsync();
await Given_ProtoBufMappingIsAddedViaAdminInterfaceAsync(wireMockContainer, "protobuf-mapping-1.json");
var reply = await When_GrpcClient_Calls_SayHelloAsync(wireMockContainer);
Then_ReplyMessage_Should_BeCorrect(reply);
await wireMockContainer.StopAsync();
}
[Fact]
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient()
{
var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync();
await Given_ProtoBufMappingIsAddedViaAdminInterfaceAsync(wireMockContainer, "protobuf-mapping-4.json");
var reply = await When_GrpcClient_Calls_SayHelloAsync(wireMockContainer);
Then_ReplyMessage_Should_BeCorrect(reply);
await wireMockContainer.StopAsync();
}
private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
{
var wireMockContainer = new WireMockContainerBuilder()
.WithAutoRemove(true)
.WithCleanUp(true)
.AddUrl("grpc://*:9090")
.Build();
await wireMockContainer.StartAsync();
return wireMockContainer;
}
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
{
var wireMockContainer = new WireMockContainerBuilder()
.WithAutoRemove(true)
.WithCleanUp(true)
.AddUrl("grpc://*:9090")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.Build();
await wireMockContainer.StartAsync();
return wireMockContainer;
}
private static async Task Given_ProtoBufMappingIsAddedViaAdminInterfaceAsync(WireMockContainer wireMockContainer, string filename)
{
var mappingsJson = ReadFile(filename);
using var httpClient = wireMockContainer.CreateClient();
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
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");
}
private static string ReadFile(string filename)
{
return File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings", filename));
}
}
#endif

View File

@@ -8,15 +8,16 @@ using DotNet.Testcontainers.Builders;
using FluentAssertions;
using FluentAssertions.Execution;
using WireMock.Net.Testcontainers;
using WireMock.Net.Testcontainers.Utils;
using WireMock.Net.Tests.Facts;
using Xunit;
namespace WireMock.Net.Tests.Testcontainers;
public class TestcontainersTests
public partial class TestcontainersTests
{
[Fact]
public async Task WireMockContainer_Build_WithNoImage_And_StartAsync_and_StopAsync()
public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync()
{
// Act
var adminUsername = $"username_{Guid.NewGuid()}";
@@ -32,7 +33,7 @@ public class TestcontainersTests
// https://github.com/testcontainers/testcontainers-dotnet/issues/1322
[RunOnDockerPlatformFact("Linux")]
public async Task WireMockContainer_Build_WithNoImageAndNetwork_And_StartAsync_and_StopAsync()
public async Task WireMockContainer_Build_WithNetwork_And_StartAsync_and_StopAsync()
{
// Act
var dummyNetwork = new NetworkBuilder()
@@ -61,7 +62,8 @@ public class TestcontainersTests
.WithCleanUp(true)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
if (imageOS == OSPlatform.Windows)
{
wireMockContainerBuilder = wireMockContainerBuilder.WithWindowsImage();
}
@@ -86,7 +88,8 @@ public class TestcontainersTests
.WithCleanUp(true)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
if (imageOS == OSPlatform.Windows)
{
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net-windows");
}

View File

@@ -148,7 +148,7 @@ public class QueryStringParserTests
// Assert
result.Count.Should().Be(1);
result["empty"].Should().Equal(new WireMockList<string>());
result["empty"].Should().Equal(new WireMockList<string>(""));
}
[Fact]
@@ -354,18 +354,19 @@ public class QueryStringParserTests
public void Parse_WithComplex()
{
// Assign
string query = "?q=energy+edge&rls=com.microsoft:en-au&ie=UTF-8&oe=UTF-8&startIndex=&startPage=1%22";
string query = "?q=energy+edge&rls=com.microsoft:en-au&ie=UTF-8&oe=UTF-8&startIndex=&startPage=1%22&x";
// Act
var result = QueryStringParser.Parse(query);
// Assert
result.Count.Should().Be(6);
result.Count.Should().Be(7);
result["q"].Should().Equal(new WireMockList<string>("energy edge"));
result["rls"].Should().Equal(new WireMockList<string>("com.microsoft:en-au"));
result["ie"].Should().Equal(new WireMockList<string>("UTF-8"));
result["oe"].Should().Equal(new WireMockList<string>("UTF-8"));
result["startIndex"].Should().Equal(new WireMockList<string>());
result["startIndex"].Should().Equal(new WireMockList<string>(""));
result["startPage"].Should().Equal(new WireMockList<string>("1\""));
result["x"].Should().Equal(new WireMockList<string>());
}
}

View File

@@ -134,6 +134,14 @@
<None Update="cert.pem">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Grpc\request.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<!--<GrpcServices>Client</GrpcServices>-->
</None>
<None Update="Grpc\greet1.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<!--<GrpcServices>Client</GrpcServices>-->
</None>
<None Update="Grpc\policy.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<GrpcServices>Client</GrpcServices>
@@ -154,6 +162,9 @@
<None Update="__admin\mappings\*.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\*.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\subdirectory\*.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@@ -15,7 +15,6 @@ using RestEase;
using WireMock.Client;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -27,7 +26,6 @@ namespace WireMock.Net.Tests;
public class WireMockServerAdminTests
{
// For for AppVeyor + OpenCover
private static string GetCurrentFolder()
{
return Directory.GetCurrentDirectory();
@@ -40,8 +38,8 @@ public class WireMockServerAdminTests
string folder = Path.Combine(GetCurrentFolder(), "__admin", "mappings");
server.ReadStaticMappings(folder);
Check.That(server.Mappings).HasSize(6);
Check.That(server.MappingModels).HasSize(6);
Check.That(server.Mappings).HasSize(Constants.NumStaticMappings);
Check.That(server.MappingModels).HasSize(Constants.NumStaticMappings);
// Act
server.ResetMappings();
@@ -220,7 +218,7 @@ public class WireMockServerAdminTests
server.ReadStaticMappings(folder);
var mappings = server.Mappings.ToArray();
Check.That(mappings).HasSize(6);
Check.That(mappings).HasSize(Constants.NumStaticMappings);
server.Stop();
}

View File

@@ -119,7 +119,7 @@ public class WireMockServerProxyTests
}
// Assert
server.Mappings.Should().HaveCount(37);
server.Mappings.Should().HaveCount(Constants.NumAdminMappings + 2);
}
[Fact]

View File

@@ -75,8 +75,6 @@ public class WireMockServerSettingsTests
[Fact]
public void WireMockServer_WireMockServerSettings_PriorityFromAllAdminMappingsIsLow_When_StartAdminInterface_IsTrue()
{
const int count = 35;
// Assign and Act
var server = WireMockServer.Start(new WireMockServerSettings
{
@@ -85,15 +83,13 @@ public class WireMockServerSettingsTests
// Assert
server.Mappings.Should().NotBeNull();
server.Mappings.Should().HaveCount(count);
server.Mappings.Should().HaveCount(Constants.NumAdminMappings);
server.Mappings.All(m => m.Priority == WireMockConstants.AdminPriority).Should().BeTrue();
}
[Fact]
public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPriority_IsMinus2000000_When_StartAdminInterface_IsTrue()
{
const int count = 36;
// Assign and Act
var server = WireMockServer.Start(new WireMockServerSettings
{
@@ -106,9 +102,9 @@ public class WireMockServerSettingsTests
// Assert
server.Mappings.Should().NotBeNull();
server.Mappings.Should().HaveCount(count);
server.Mappings.Should().HaveCount(Constants.NumAdminMappings + 1);
server.Mappings.Count(m => m.Priority == WireMockConstants.AdminPriority).Should().Be(count - 1);
server.Mappings.Count(m => m.Priority == WireMockConstants.AdminPriority).Should().Be(Constants.NumAdminMappings);
server.Mappings.Count(m => m.Priority == WireMockConstants.ProxyPriority).Should().Be(1);
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
enum PhoneType {
none = 0;
mobile = 1;
home = 2;
}
PhoneType phoneType = 2;
}

View File

@@ -0,0 +1,49 @@
{
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0001",
"Title": "ProtoBuf Mapping 1",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/greet.Greeter/SayHello",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matcher": {
"Name": "ProtoBufMatcher",
"Pattern": "\r\nsyntax = \"proto3\";\r\n\r\npackage greet;\r\n\r\nservice Greeter {\r\n rpc SayHello (HelloRequest) returns (HelloReply);\r\n}\r\n\r\nmessage HelloRequest {\r\n string name = 1;\r\n}\r\n\r\nmessage HelloReply {\r\n string message = 1;\r\n}\r\n",
"ContentMatcher": {
"Name": "JsonPartialWildcardMatcher",
"Pattern": {
"name": "*"
},
"IgnoreCase": false,
"Regex": false
},
"ProtoBufMessageType": "greet.HelloRequest"
}
}
},
"Response": {
"BodyAsJson": {
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
},
"UseTransformer": true,
"TransformerType": "Handlebars",
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
"Headers": {
"Content-Type": "application/grpc"
},
"TrailingHeaders": {
"grpc-status": "0"
},
"ProtoDefinition": "\r\nsyntax = \"proto3\";\r\n\r\npackage greet;\r\n\r\nservice Greeter {\r\n rpc SayHello (HelloRequest) returns (HelloReply);\r\n}\r\n\r\nmessage HelloRequest {\r\n string name = 1;\r\n}\r\n\r\nmessage HelloReply {\r\n string message = 1;\r\n}\r\n",
"ProtoBufMessageType": "greet.HelloReply"
}
}

View File

@@ -0,0 +1,48 @@
{
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0002",
"Title": "ProtoBuf Mapping 2",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/greet.Greeter/SayHello",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matcher": {
"Name": "ProtoBufMatcher",
"ContentMatcher": {
"Name": "JsonPartialWildcardMatcher",
"Pattern": {
"name": "*"
},
"IgnoreCase": false,
"Regex": false
},
"ProtoBufMessageType": "greet.HelloRequest"
}
}
},
"Response": {
"BodyAsJson": {
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
},
"UseTransformer": true,
"TransformerType": "Handlebars",
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
"Headers": {
"Content-Type": "application/grpc"
},
"TrailingHeaders": {
"grpc-status": "0"
},
"ProtoBufMessageType": "greet.HelloReply"
},
"ProtoDefinition": "\r\nsyntax = \"proto3\";\r\n\r\npackage greet;\r\n\r\nservice Greeter {\r\n rpc SayHello (HelloRequest) returns (HelloReply);\r\n}\r\n\r\nmessage HelloRequest {\r\n string name = 1;\r\n}\r\n\r\nmessage HelloReply {\r\n string message = 1;\r\n}\r\n"
}

View File

@@ -0,0 +1,48 @@
{
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0003",
"Title": "ProtoBuf Mapping 3",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/greet.Greeter/SayHello",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matcher": {
"Name": "ProtoBufMatcher",
"ContentMatcher": {
"Name": "JsonPartialWildcardMatcher",
"Pattern": {
"name": "*"
},
"IgnoreCase": true,
"Regex": false
},
"ProtoBufMessageType": "greet.HelloRequest"
}
}
},
"Response": {
"BodyAsJson": {
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
},
"UseTransformer": true,
"TransformerType": "Handlebars",
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
"Headers": {
"Content-Type": "application/grpc"
},
"TrailingHeaders": {
"grpc-status": "0"
},
"ProtoBufMessageType": "greet.HelloReply"
},
"ProtoDefinition": "my-greeter"
}

View File

@@ -0,0 +1,40 @@
{
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0004",
"Title": "ProtoBuf Mapping 4",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/greet.Greeter/SayHello",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matcher": {
"Name": "ProtoBufMatcher",
"ProtoBufMessageType": "greet.HelloRequest"
}
}
},
"Response": {
"BodyAsJson": {
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
},
"UseTransformer": true,
"TransformerType": "Handlebars",
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
"Headers": {
"Content-Type": "application/grpc"
},
"TrailingHeaders": {
"grpc-status": "0"
},
"ProtoBufMessageType": "greet.HelloReply"
},
"ProtoDefinition": "my-greeter"
}