diff --git a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs index 013244de9..f296550e7 100644 --- a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs +++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs @@ -215,14 +215,16 @@ public interface IWireMockServer : IDisposable /// This can be used if you have 1 or more defined and want to register these in WireMock.Net directly instead of using the fluent syntax. /// /// The MappingModels + /// IWireMockServer WithMapping(params MappingModel[] mappings); /// /// 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. /// /// The mapping(s) as json string. + /// IWireMockServer WithMapping(string mappings); /// @@ -238,5 +240,5 @@ public interface IWireMockServer : IDisposable /// /// The /// C# code - public string MappingsToCSharpCode(MappingConverterType converterType); + string MappingsToCSharpCode(MappingConverterType converterType); } \ No newline at end of file diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 53bedebba..fc4e4d726 100644 --- a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs +++ b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs @@ -130,7 +130,7 @@ public interface IWireMockAdminApi Task ReloadStaticMappingsAsync(CancellationToken cancellationToken = default); /// - /// Get a mapping based on the guid + /// Get a mapping based on the guid. /// /// The Guid /// MappingModel @@ -138,6 +138,15 @@ public interface IWireMockAdminApi [Get("mappings/{guid}")] Task GetMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default); + /// + /// Get a mapping based on the guid. + /// + /// The Guid + /// MappingModel + /// The optional cancellationToken. + [Get("mappings/{guid}")] + Task GetMappingAsync([Path] string guid, CancellationToken cancellationToken = default); + /// /// Get the C# code from a mapping based on the guid /// diff --git a/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs b/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs index bd660cf6a..cb3bbb610 100644 --- a/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs +++ b/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs @@ -66,6 +66,12 @@ public static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatche 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; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/ProtoBufMatcher.cs b/src/WireMock.Net/Matchers/ProtoBufMatcher.cs index b29dcc8d5..12e0c61b1 100644 --- a/src/WireMock.Net/Matchers/ProtoBufMatcher.cs +++ b/src/WireMock.Net/Matchers/ProtoBufMatcher.cs @@ -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 /// /// The Func to define the proto definition as id or texts. /// - public Func ProtoDefinition { get; } + public Func ProtoDefinition { get; internal set; } /// /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". diff --git a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs index 8e046b4ac..ac01e3ea2 100644 --- a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using JsonConverter.Abstractions; using WireMock.Matchers; using WireMock.Util; diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net/RequestBuilders/Request.cs index 2d5d90f0e..b37e92ab5 100644 --- a/src/WireMock.Net/RequestBuilders/Request.cs +++ b/src/WireMock.Net/RequestBuilders/Request.cs @@ -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 IList GetRequestMessageMatchers() where T : IRequestMatcher return _requestMatchers.OfType().FirstOrDefault(func); } + internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher) + { + protoBufMatcher = GetRequestMessageMatcher()?.Matcher; + if (protoBufMatcher != null) + { + return true; + } + + var bodyMatcher = GetRequestMessageMatcher(); + protoBufMatcher = bodyMatcher?.Matchers?.OfType().FirstOrDefault(); + return protoBufMatcher != null; + } + private IRequestBuilder Add(T requestMatcher) where T : IRequestMatcher { foreach (var existing in _requestMatchers.OfType().ToArray()) diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs b/src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs index 3f19d28d4..1b4579886 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs @@ -49,7 +49,7 @@ public IResponseBuilder WithHeaders(IDictionary> he 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 IResponseBuilder WithTrailingHeader(string name, params string[] values) public IResponseBuilder WithTrailingHeaders(IDictionary 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 IResponseBuilder WithTrailingHeaders(IDictionary headers) public IResponseBuilder WithTrailingHeaders(IDictionary 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); diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 017f38e9b..b04b7970e 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -8,7 +8,6 @@ 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 @@ string RemoveFirstOccurrence(string source, string find) 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()?.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); } } } diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 260fba537..1bb70ba08 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -379,7 +379,7 @@ public MappingModel ToMappingModel(IMapping mapping) } var bodyMatchers = - protoBufMatcher?.Matcher != null ? new[] { protoBufMatcher.Matcher } : null ?? + protoBufMatcher?.Matcher != null ? [protoBufMatcher.Matcher] : null ?? multiPartMatcher?.Matchers ?? graphQLMatcher?.Matchers ?? bodyMatcher?.Matchers; diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 8cadbb622..9cc4e36e6 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -217,7 +217,7 @@ public MatcherMapper(WireMockServerSettings settings) { model.Pattern = texts[0]; } - else + else if (texts.Count > 1) { model.Patterns = texts.Cast().ToArray(); } @@ -293,27 +293,9 @@ private ProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, IR { 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 ); diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net/Server/IRespondWithAProvider.cs index 963e3e10d..4d1ffa510 100644 --- a/src/WireMock.Net/Server/IRespondWithAProvider.cs +++ b/src/WireMock.Net/Server/IRespondWithAProvider.cs @@ -123,14 +123,14 @@ public interface IRespondWithAProvider void ThenRespondWithStatusCode(HttpStatusCode code); /// - /// Sets the the scenario. + /// Sets the scenario. /// /// The scenario. /// The . IRespondWithAProvider InScenario(string scenario); /// - /// Sets the the scenario with an integer value. + /// Sets the scenario with an integer value. /// /// The scenario. /// The . @@ -220,7 +220,7 @@ IRespondWithAProvider WithWebhook( /// /// 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 /// The data dictionary object. /// /// lookup data "1" diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index 0ea2332d7..3840c9c33 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -17,7 +17,7 @@ namespace WireMock.Server; /// -/// The respond with a provider. +/// The RespondWithAProvider. /// 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; } + /// /// Initializes a new instance of the class. /// @@ -104,9 +105,9 @@ public void RespondWith(IResponseProvider provider) 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 @@ public IRespondWithAProvider WithWebhook( 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 @@ public IRespondWithAProvider WithWebhook( 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 @@ public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinition { 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; } diff --git a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs index 9e3559e02..eaea8a1a5 100644 --- a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs @@ -42,9 +42,9 @@ private Guid ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingMode 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 @@ private Guid ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingMode 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 @@ private IRequestBuilder InitRequestBuilder(RequestModel requestModel) 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().ToArray()); @@ -225,7 +235,15 @@ private IRequestBuilder InitRequestBuilder(RequestModel requestModel) 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 @@ private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) } 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 @@ private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) } } + 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(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 @@ private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) } 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) { diff --git a/src/WireMock.Net/Util/PortUtils.cs b/src/WireMock.Net/Util/PortUtils.cs index 843ce8d71..f50a106d8 100644 --- a/src/WireMock.Net/Util/PortUtils.cs +++ b/src/WireMock.Net/Util/PortUtils.cs @@ -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,25 +19,96 @@ internal static class PortUtils private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled, WireMockConstants.DefaultRegexTimeout); /// - /// Finds a free TCP port. + /// Finds a random, free port to be listened on. /// - /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. + /// A random, free port to be listened on. + /// https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/src/webdriver/Internal/PortUtilities.cs 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(); + var socketEndPoint = new IPEndPoint(IPAddress.Any, 0); + portSocket.Bind(socketEndPoint); + socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint!; + return socketEndPoint.Port; + } + finally + { +#if !NETSTANDARD1_3 + portSocket.Close(); +#endif + portSocket.Dispose(); + } + } + + /// + /// Finds a specified number of random, free ports to be listened on. + /// + /// The number of free ports to find. + /// A list of random, free ports to be listened on. + public static IReadOnlyList FindFreeTcpPorts(int count) + { + var sockets = Enumerable + .Range(0, count) + .Select(_ => new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .ToArray(); - return ((IPEndPoint)tcpListener.LocalEndpoint).Port; + var freePorts = new List(); + + 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 { - tcpListener?.Stop(); + foreach (var socket in sockets) + { +#if !NETSTANDARD1_3 + socket.Close(); +#endif + socket.Dispose(); + } } } + ///// + ///// Finds free TCP ports. + ///// + //public static IReadOnlyList FindFreeTcpPorts(int numPorts) + //{ + // var freePorts = new List(); + + // TcpListener? tcpListener = null; + // try + // { + // for (var i = 0; i < numPorts; i++) + // { + // tcpListener = new TcpListener(IPAddress.Loopback, 0); + // tcpListener.Start(); + + // freePorts.Add(((IPEndPoint)tcpListener.LocalEndpoint).Port); + // } + // } + // finally + // { + // tcpListener?.Stop(); + // } + + // return freePorts; + //} + /// /// Extract the isHttps, isHttp2, protocol, host and port from a URL. /// diff --git a/src/WireMock.Net/Util/ProtoDefinitionHelper.cs b/src/WireMock.Net/Util/ProtoDefinitionHelper.cs new file mode 100644 index 000000000..91edc51eb --- /dev/null +++ b/src/WireMock.Net/Util/ProtoDefinitionHelper.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappingsAsync.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappings.cs similarity index 85% rename from test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappingsAsync.cs rename to test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappings.cs index 9db83fdca..a98b2cf54 100644 --- a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappingsAsync.cs +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.GetMappings.cs @@ -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(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 @@ public async Task IWireMockAdminApi_GetMappingsAsync_WithBodyAsProtoBuf_ShouldRe .WithTransformer() ); - // Act - var api = RestClient.For(server.Url); - var getMappingsResult = await api.GetMappingsAsync().ConfigureAwait(false); - - await Verifier.Verify(getMappingsResult, VerifySettings); - - server.Stop(); + return server; } } #endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.HttpClient_GetMappingsAsync_WithBodyAsProtoBuf_ShouldReturnCorrectMappingModels.verified.txt b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.HttpClient_GetMappingsAsync_WithBodyAsProtoBuf_ShouldReturnCorrectMappingModels.verified.txt new file mode 100644 index 000000000..6da885946 --- /dev/null +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.HttpClient_GetMappingsAsync_WithBodyAsProtoBuf_ShouldReturnCorrectMappingModels.verified.txt @@ -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 + } +] \ No newline at end of file diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.PostMappings.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.PostMappings.cs new file mode 100644 index 000000000..2dfad302f --- /dev/null +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.PostMappings.cs @@ -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(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(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(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(); + exceptionAssertions.Which.Content.Should().Be(@"{""Status"":""The following Guids are duplicate : '1b731398-4a5b-457f-a6e3-d65e541c428f' (Parameter 'mappingModels')""}"); + + server.Stop(); + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs index 8e19470dc..0ef68c824 100644 --- a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs @@ -184,124 +184,7 @@ public async Task IWireMockAdminApi_PutMappingAsync() 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(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(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(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(); - 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 @@ public async Task IWireMockAdminApi_ReadStaticMappingsAsync() // 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 \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs b/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs index f9eb3bc56..68acd4f8e 100644 --- a/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs +++ b/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs @@ -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 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 @@ public async Task WireMockServer_WithBodyAsProtoBuf_ServerProtoDefinition_UsingG ); // 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 @@ public async Task WireMockServer_WithBodyAsProtoBuf_WithWellKnownTypes_Timestamp // 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 @@ public async Task WireMockServer_WithBodyAsProtoBuf_WithWellKnownTypes_Duration_ // 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 @@ public async Task WireMockServer_WithBodyAsProtoBuf_WithWellKnownTypes_Duration_ 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 @@ public async Task WireMockServer_WithBodyAsProtoBuf_Enum_UsingPolicyGrpcGenerate 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); @@ -696,5 +698,68 @@ public async Task WireMockServer_WithBodyAsProtoBuf_Enum_UsingPolicyGrpcGenerate reply.Client.ClientName.Should().Be(NarrowIntegrationTest.Lookup.Client.Types.Clients.BillingCenter); reply.Client.CorrelationId.Should().Be(correlationId); } + + [Fact] + public async Task WireMockServer_WithBodyAsProtoBuf_ServerProtoDefinitionFromJson_UsingGrpcGeneratedClient() + { + var server = Given_When_ServerStartedUsingHttp2(); + Given_ProtoDefinition_IsAddedOnServerLevel(server); + await Given_When_ProtoBufMappingIsAddedViaAdminInterfaceAsync(server); + + var reply = await When_GrpcClient_Calls_SayHelloAsync(server.Urls[1]); + + Then_ReplyMessage_Should_BeCorrect(reply); + } + + private static WireMockServer Given_When_ServerStartedUsingHttp2() + { + 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) + { + var mappingsJson = ReadMappingFile("protobuf-mapping-3.json"); + + 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 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 \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServer.Admin.cs b/test/WireMock.Net.Tests/WireMockServer.Admin.cs index aa4f518d8..684633f96 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Admin.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Admin.cs @@ -15,7 +15,6 @@ 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,8 @@ namespace WireMock.Net.Tests; public class WireMockServerAdminTests { - // For for AppVeyor + OpenCover + private const int NumStaticMappings = 10; + private static string GetCurrentFolder() { return Directory.GetCurrentDirectory(); @@ -40,8 +40,8 @@ public void WireMockServer_Admin_ResetMappings() 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(NumStaticMappings); + Check.That(server.MappingModels).HasSize(NumStaticMappings); // Act server.ResetMappings(); @@ -220,7 +220,7 @@ public void WireMockServer_Admin_ReadStaticMappings() server.ReadStaticMappings(folder); var mappings = server.Mappings.ToArray(); - Check.That(mappings).HasSize(6); + Check.That(mappings).HasSize(NumStaticMappings); server.Stop(); } diff --git a/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-1.json b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-1.json new file mode 100644 index 000000000..b157ac618 --- /dev/null +++ b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-1.json @@ -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}}" + }, + "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" + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-2.json b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-2.json new file mode 100644 index 000000000..41f1e95e8 --- /dev/null +++ b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-2.json @@ -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}}" + }, + "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" +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-3.json b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-3.json new file mode 100644 index 000000000..d45a0a9c7 --- /dev/null +++ b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-3.json @@ -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" +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-4.json b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-4.json new file mode 100644 index 000000000..a56d43545 --- /dev/null +++ b/test/WireMock.Net.Tests/__admin/mappings/protobuf-mapping-4.json @@ -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}}" + }, + "UseTransformer": true, + "TransformerType": "Handlebars", + "TransformerReplaceNodeOptions": "EvaluateAndTryToConvert", + "Headers": { + "Content-Type": "application/grpc" + }, + "TrailingHeaders": { + "grpc-status": "0" + }, + "ProtoBufMessageType": "greet.HelloReply" + }, + "ProtoDefinition": "my-greeter" +} \ No newline at end of file