Compare commits

..

12 Commits

Author SHA1 Message Date
Stef Heyenrath
282281aa7f 1.4.16 2021-06-05 11:00:51 +02:00
Stef Heyenrath
4052a0ef3b Upgrade Handlebars.Net.Helpers to 2.19 (#616)
* Upgrade Handlebars.Net.Helpers to 2.19

* fix ut
2021-06-05 10:59:09 +02:00
Stef Heyenrath
2ca394b7f6 1.4.15 2021-05-19 12:01:19 +00:00
Stef Heyenrath
b04000bfdd Add support for multiple webhooks (#615) 2021-05-19 13:58:48 +02:00
Stef Heyenrath
93ab4e1853 1.4.14 2021-05-11 05:45:07 +00:00
starkpl
80b5eaac6e Add AdditionalServiceRegistration action for custom ASP.NET Core DI setup (#611) 2021-05-11 07:37:20 +02:00
Stef Heyenrath
a1dc2ba646 condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests (#612) 2021-05-10 22:06:42 +02:00
Stef Heyenrath
934c444902 Fix some SonarCloud issues in UnitTests (#610) 2021-05-07 18:23:23 +02:00
Stef Heyenrath
83d178bdb5 1.4.13 2021-04-26 14:53:15 +00:00
Stef Heyenrath
d91b5d5831 Add possibility to use settings to generate MappingModel models with wildcard path parameters. (#609)
* Add optional settings for WithMappingFromOpenApi

* .

* .

* cleanup
2021-04-26 14:44:45 +02:00
Stef Heyenrath
43b96ce340 Add OpenApiParser NuGet to readme 2021-04-23 17:51:45 +02:00
dependabot[bot]
4d8cf43357 Bump System.Text.Encodings.Web (#607)
Bumps [System.Text.Encodings.Web](https://github.com/dotnet/corefx) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/dotnet/corefx/releases)
- [Commits](https://github.com/dotnet/corefx/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-22 18:11:30 +02:00
41 changed files with 988 additions and 599 deletions

View File

@@ -1,3 +1,17 @@
# 1.4.15 (19 May 2021)
- [#615](https://github.com/WireMock-Net/WireMock.Net/pull/615) - Add support for multiple webhooks [feature] contributed by [StefH](https://github.com/StefH)
- [#614](https://github.com/WireMock-Net/WireMock.Net/issues/614) - Is it possible to some how send multiple webhooks? [feature]
# 1.4.14 (11 May 2021)
- [#610](https://github.com/WireMock-Net/WireMock.Net/pull/610) - Fix some SonarCloud issues in UnitTests contributed by [StefH](https://github.com/StefH)
- [#611](https://github.com/WireMock-Net/WireMock.Net/pull/611) - Allow to add custom service registrations when using ASP.NET Core [feature] contributed by [starkpl](https://github.com/starkpl)
- [#612](https://github.com/WireMock-Net/WireMock.Net/pull/612) - Don't run SonarCloud tasks for PullRequests [feature] contributed by [StefH](https://github.com/StefH)
# 1.4.13 (26 April 2021)
- [#607](https://github.com/WireMock-Net/WireMock.Net/pull/607) - Bump System.Text.Encodings.Web from 4.5.0 to 4.5.1 in /examples/WireMock.Net.StandAlone.Net461 [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#609](https://github.com/WireMock-Net/WireMock.Net/pull/609) - Add possibility to use settings to generate MappingModel models with wildcard path parameters. [feature] contributed by [StefH](https://github.com/StefH)
- [#608](https://github.com/WireMock-Net/WireMock.Net/issues/608) - Import from OpenApi generates model with path parameter narrowed in range (example value=42 instead of '*') [feature]
# 1.4.12 (22 April 2021)
- [#605](https://github.com/WireMock-Net/WireMock.Net/pull/605) - Bump System.Net.Http from 4.3.3 to 4.3.4 in /src/WireMock.Net [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#606](https://github.com/WireMock-Net/WireMock.Net/pull/606) - Bump System.Net.Http from 4.3.3 to 4.3.4 in /examples/WireMock.Net.Service [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.4.12</VersionPrefix>
<VersionPrefix>1.4.16</VersionPrefix>
<PackageReleaseNotes>See CHANGELOG.md</PackageReleaseNotes>
<PackageIconUrl>https://raw.githubusercontent.com/WireMock-Net/WireMock.Net/master/WireMock.Net-Logo.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>

View File

@@ -1,3 +1,3 @@
https://github.com/StefH/GitHubReleaseNotes
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc --version 1.4.12
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc --version 1.4.16

View File

@@ -36,6 +36,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.FluentAssertions** | [![NuGet Badge WireMock.Net.FluentAssertions](https://buildstats.info/nuget/WireMock.Net.FluentAssertions)](https://www.nuget.org/packages/WireMock.Net.FluentAssertions) | [![MyGet Badge WireMock.Net.FluentAssertions](https://buildstats.info/myget/wiremock-net/WireMock.Net.FluentAssertions?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.FluentAssertions)
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://buildstats.info/nuget/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Net.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://buildstats.info/nuget/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://buildstats.info/myget/wiremock-net/WireMock.Net.Matchers.CSharpCode?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
| &nbsp;&nbsp;**WireMock.Net.OpenApiParser** | [![NuGet Badge WireMock.Net.OpenApiParser](https://buildstats.info/nuget/WireMock.Net.OpenApiParser)](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [![MyGet Badge WireMock.Net.OpenApiParser](https://buildstats.info/myget/wiremock-net/WireMock.Net.OpenApiParser?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
## Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.

View File

@@ -13,6 +13,9 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=funcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stef/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

View File

@@ -28,6 +28,7 @@ jobs:
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
inputs:
SonarCloud: SonarCloud
organization: wiremock-net
@@ -52,9 +53,11 @@ jobs:
- task: SonarCloudAnalyze@1
displayName: 'SonarCloud: Run Code Analysis'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
- task: SonarCloudPublish@1
displayName: 'SonarCloud: Publish Quality Gate Result'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
- task: whitesource.ws-bolt.bolt.wss.WhiteSource Bolt@19
displayName: 'WhiteSource Bolt'

View File

@@ -59,6 +59,7 @@ namespace WireMock.Net.ConsoleApplication
//},
PreWireMockMiddlewareInit = app => { System.Console.WriteLine($"PreWireMockMiddlewareInit : {app.GetType()}"); },
PostWireMockMiddlewareInit = app => { System.Console.WriteLine($"PostWireMockMiddlewareInit : {app.GetType()}"); },
AdditionalServiceRegistration = services => { System.Console.WriteLine($"AdditionalServiceRegistration : {services.GetType()}"); },
Logger = new WireMockConsoleLogger(),
HandlebarsRegistrationCallback = (handlebarsContext, fileSystemHandler) =>

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
using WireMock.Logging;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Net.OpenApiParser.Extensions;
namespace WireMock.Net.OpenApiParser.ConsoleApp
{
@@ -23,13 +25,18 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
Logger = new WireMockConsoleLogger()
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.SetBasicAuthentication("a", "b");
server.WithMappingFromOpenApiFile(path, out var diag);
var settings = new WireMockOpenApiParserSettings
{
PathPatternToUse = ExampleValueType.Wildcard
};
server.WithMappingFromOpenApiFile(path, settings, out var diag);
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();

View File

@@ -76,7 +76,7 @@
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" />
<package id="System.Security.Principal.Windows" version="4.5.1" targetFramework="net461" />
<package id="System.Text.Encodings.Web" version="4.5.0" targetFramework="net461" />
<package id="System.Text.Encodings.Web" version="4.5.1" targetFramework="net461" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net461" />
<package id="XPath2" version="1.1.0" targetFramework="net461" />
</packages>

View File

@@ -57,5 +57,10 @@ namespace WireMock.Admin.Mappings
/// The Webhook.
/// </summary>
public WebhookModel Webhook { get; set; }
/// <summary>
/// The Webhooks.
/// </summary>
public WebhookModel[] Webhooks { get; set; }
}
}

View File

@@ -1,12 +1,19 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using SharpYaml.Model;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions
{
/// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>.
/// </summary>
public static class WireMockServerExtensions
{
/// <summary>
@@ -15,18 +22,26 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
return WithMappingFromOpenApiFile(server, path, null, out diagnostic);
}
var mappings = new WireMockOpenApiParser().FromFile(path, out diagnostic);
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 file.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <param name="settings">Additional settings</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server, nameof(server));
Guard.NotNullOrEmpty(path, nameof(path));
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
@@ -37,9 +52,27 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{
var mappings = new WireMockOpenApiParser().FromStream(stream, out diagnostic);
return WithMappingFromOpenApiStream(server, stream, null, out diagnostic);
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server, nameof(server));
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(settings, nameof(settings));
var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
@@ -49,9 +82,14 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param>
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document)
/// <param name="settings">Additional settings [optional]</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings = null)
{
var mappings = new WireMockOpenApiParser().FromDocument(document);
Guard.NotNull(server, nameof(server));
Guard.NotNull(document, nameof(document));
var mappings = new WireMockOpenApiParser().FromDocument(document, settings);
return server.WithMapping(mappings.ToArray());
}

View File

@@ -1,37 +1,57 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary>
/// <param name="document">The source OpenApiDocument</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
}
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary>
/// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings settings = null);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;
using Newtonsoft.Json.Linq;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils;
namespace WireMock.Net.OpenApiParser.Mappers
{
internal class OpenApiPathsMapper
{
private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value));
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema);
if (int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel
{
Guid = Guid.NewGuid(),
Request = new RequestModel
{
Methods = new[] { httpMethod },
Path = MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters)
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
}
};
}
private bool TryGetContent(IDictionary<string, OpenApiMediaType> contents, out OpenApiMediaType openApiMediaType, out string contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private object MapSchemaToObject(OpenApiSchema schema, string name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < _settings.NumberOfArrayItems; i++)
{
if (schema.Items.Properties.Count > 0)
{
var arrayItem = new JObject();
foreach (var property in schema.Items.Properties)
{
var objectValue = MapSchemaToObject(property.Value, property.Key);
if (objectValue is JProperty jp)
{
arrayItem.Add(jp);
}
else
{
arrayItem.Add(new JProperty(property.Key, objectValue));
}
}
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
}
}
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
string propertyName = schemaProperty.Key;
var openApiSchema = schemaProperty.Value;
if (openApiSchema.GetSchemaType() == SchemaType.Object)
{
var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key);
if (mapped is JProperty jp)
{
propertyAsJObject.Add(jp);
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
propertyAsJObject.Add(new JProperty(propertyName, _exampleValueGenerator.GetExampleValue(openApiSchema)));
}
}
return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject;
default:
return null;
}
}
private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters)
{
if (parameters == null)
{
return path;
}
string newPath = path;
foreach (var parameter in parameters)
{
newPath = newPath.Replace($"{{{parameter.Name}}}", GetExampleValue(parameter.Schema, _settings.PathPatternToUse));
}
return newPath;
}
private JToken MapOpenApiAnyToJToken(IOpenApiAny any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(
item => item.Key,
item => GetExampleValue(null, _settings.HeaderPatternToUse) as object
);
if (!string.IsNullOrEmpty(responseContentType))
{
if (!mappedHeaders.ContainsKey("Content-Type"))
{
mappedHeaders.Add("Content-Type", responseContentType);
}
else
{
mappedHeaders["Content-Type"] = responseContentType;
}
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private IList<ParamModel> MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{
var list = queryParameters
.Select(qp => new ParamModel
{
Name = qp.Name,
Matchers = new[]
{
new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetDefaultValueAsStringForSchemaType(qp.Schema)
}
}
})
.ToList();
return list.Any() ? list : null;
}
private string GetDefaultValueAsStringForSchemaType(OpenApiSchema schema)
{
var value = _exampleValueGenerator.GetExampleValue(schema);
switch (value)
{
case string valueAsString:
return valueAsString;
default:
return value.ToString();
}
}
private string GetExampleValue(OpenApiSchema schema, ExampleValueType type)
{
switch (type)
{
case ExampleValueType.Value:
return GetDefaultValueAsStringForSchemaType(schema);
default:
return "*";
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace WireMock.Net.OpenApiParser.Settings
{
/// <summary>
/// A class defining the example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserExampleValues
{
#pragma warning disable 1591
public bool Boolean { get; set; } = true;
public int Integer { get; set; } = 42;
public float Float { get; set; } = 4.2f;
public double Double { get; set; } = 4.2d;
public Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
public Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
public byte[] Bytes { get; set; } = { 48, 49, 50 };
public object Object { get; set; } = "example-object";
public string String { get; set; } = "example-string";
#pragma warning restore 1591
}
}

View File

@@ -0,0 +1,30 @@
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Settings
{
/// <summary>
/// The WireMockOpenApiParser Settings
/// </summary>
public class WireMockOpenApiParserSettings
{
/// <summary>
/// The number of array items to generate (default is 3).
/// </summary>
public int NumberOfArrayItems { get; set; } = 3;
/// <summary>
/// The example value type to use when generating a Path
/// </summary>
public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example value type to use when generating a Header
/// </summary>
public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example values to use
/// </summary>
public WireMockOpenApiParserExampleValues ExampleValues { get; } = new WireMockOpenApiParserExampleValues();
}
}

View File

@@ -0,0 +1,18 @@
namespace WireMock.Net.OpenApiParser.Types
{
/// <summary>
/// The example value to use
/// </summary>
public enum ExampleValueType
{
/// <summary>
/// Use a generated example value based on the SchemaType (default).
/// </summary>
Value,
/// <summary>
/// Just use a Wildcard (*) character.
/// </summary>
Wildcard
}
}

View File

@@ -1,49 +1,57 @@
using System;
using Microsoft.OpenApi.Models;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils
{
internal static class ExampleValueGenerator
internal class ExampleValueGenerator
{
public static object GetExampleValue(OpenApiSchema schema)
private readonly WireMockOpenApiParserSettings _settings;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public object GetExampleValue(OpenApiSchema schema)
{
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
return true;
return _settings.ExampleValues.Boolean;
case SchemaType.Integer:
return 42;
return _settings.ExampleValues.Integer;
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
return 4.2f;
return _settings.ExampleValues.Float;
default:
return 4.2d;
return _settings.ExampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
return DateTimeUtils.ToRfc3339Date(DateTime.UtcNow);
return DateTimeUtils.ToRfc3339Date(_settings.ExampleValues.Date());
case SchemaFormat.DateTime:
return DateTimeUtils.ToRfc3339DateTime(DateTime.UtcNow);
return DateTimeUtils.ToRfc3339DateTime(_settings.ExampleValues.DateTime());
case SchemaFormat.Byte:
return new byte[] { 48, 49, 50 };
return _settings.ExampleValues.Bytes;
case SchemaFormat.Binary:
return "example-object";
return _settings.ExampleValues.Object;
default:
return "example-string";
return _settings.ExampleValues.String;
}
}
}

View File

@@ -12,6 +12,7 @@
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -24,14 +25,11 @@
<PackageReference Include="RamlToOpenApiConverter" Version="0.1.1" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Stef.Validation" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Options\" />
</ItemGroup>
</Project>

View File

@@ -1,34 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using Microsoft.OpenApi.Writers;
using Newtonsoft.Json.Linq;
using RamlToOpenApiConverter;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils;
using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private const int ArrayItems = 3;
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -42,248 +41,28 @@ namespace WireMock.Net.OpenApiParser
document = reader.Read(File.OpenRead(path), out diagnostic);
}
return FromDocument(document);
return FromDocument(document, settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument)
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return MapPaths(openApiDocument.Paths);
return FromDocument(_reader.Read(stream, out diagnostic), settings);
}
private static IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings settings = null)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private static IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value));
}
private static MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema);
if (int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel
{
Guid = Guid.NewGuid(),
Request = new RequestModel
{
Methods = new[] { httpMethod },
Path = MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters)
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
}
};
}
private static bool TryGetContent(IDictionary<string, OpenApiMediaType> contents, out OpenApiMediaType openApiMediaType, out string contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private static object MapSchemaToObject(OpenApiSchema schema, string name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < ArrayItems; i++)
{
if (schema.Items.Properties.Count > 0)
{
var arrayItem = new JObject();
foreach (var property in schema.Items.Properties)
{
var objectValue = MapSchemaToObject(property.Value, property.Key);
if (objectValue is JProperty jp)
{
arrayItem.Add(jp);
}
else
{
arrayItem.Add(new JProperty(property.Key, objectValue));
}
}
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
}
}
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return ExampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
string propertyName = schemaProperty.Key;
var openApiSchema = schemaProperty.Value;
if (openApiSchema.GetSchemaType() == SchemaType.Object)
{
var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key);
if (mapped is JProperty jp)
{
propertyAsJObject.Add(jp);
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
propertyAsJObject.Add(new JProperty(propertyName, ExampleValueGenerator.GetExampleValue(openApiSchema)));
}
}
return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject;
default:
return null;
}
}
private static string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters)
{
if (parameters == null)
{
return path;
}
string newPath = path;
foreach (var parameter in parameters)
{
newPath = newPath.Replace($"{{{parameter.Name}}}", ExampleValueGenerator.GetExampleValue(parameter.Schema).ToString());
}
return newPath;
}
private static JToken MapOpenApiAnyToJToken(IOpenApiAny any)
{
if (any == null)
{
return null;
}
using (var outputString = new StringWriter())
{
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
return JObject.Parse(outputString.ToString());
}
}
private static IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(item => item.Key, item => ExampleValueGenerator.GetExampleValue(null));
if (!string.IsNullOrEmpty(responseContentType))
{
if (!mappedHeaders.ContainsKey("Content-Type"))
{
mappedHeaders.Add("Content-Type", responseContentType);
}
else
{
mappedHeaders["Content-Type"] = responseContentType;
}
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private static IList<ParamModel> MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{
var list = queryParameters
.Select(qp => new ParamModel
{
Name = qp.Name,
Matchers = new[]
{
new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetDefaultValueAsStringForSchemaType(qp.Schema)
}
}
})
.ToList();
return list.Any() ? list : null;
}
private static string GetDefaultValueAsStringForSchemaType(OpenApiSchema schema)
{
var value = ExampleValueGenerator.GetExampleValue(schema);
switch (value)
{
case string valueAsString:
return valueAsString;
default:
return value.ToString();
}
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths);
}
}
}

View File

@@ -95,9 +95,9 @@ namespace WireMock
bool LogMapping { get; }
/// <summary>
/// The Webhook.
/// The Webhooks.
/// </summary>
IWebhook Webhook { get; }
IWebhook[] Webhooks { get; }
/// <summary>
/// ProvideResponseAsync

View File

@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
@@ -56,8 +55,8 @@ namespace WireMock
/// <inheritdoc cref="IMapping.LogMapping" />
public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider);
/// <inheritdoc cref="IMapping.Webhook" />
public IWebhook Webhook { get; }
/// <inheritdoc cref="IMapping.Webhooks" />
public IWebhook[] Webhooks { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
@@ -73,7 +72,7 @@ namespace WireMock
/// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhook">The Webhook. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param>
public Mapping(
Guid guid,
[CanBeNull] string title,
@@ -86,7 +85,7 @@ namespace WireMock
[CanBeNull] string executionConditionState,
[CanBeNull] string nextState,
[CanBeNull] int? stateTimes,
[CanBeNull] IWebhook webhook)
[CanBeNull] IWebhook[] webhooks)
{
Guid = guid;
Title = title;
@@ -99,7 +98,7 @@ namespace WireMock
ExecutionConditionState = executionConditionState;
NextState = nextState;
StateTimes = stateTimes;
Webhook = webhook;
Webhooks = webhooks;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

@@ -1,8 +1,4 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Types;
namespace WireMock.Models
namespace WireMock.Models
{
/// <summary>
/// Webhook

View File

@@ -66,6 +66,8 @@ namespace WireMock.Owin
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
})
.Configure(appBuilder =>
{

View File

@@ -8,6 +8,7 @@ using WireMock.Util;
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Owin
@@ -36,6 +37,10 @@ namespace WireMock.Owin
Action<IAppBuilder> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
IFileSystemHandler FileSystemHandler { get; set; }
bool? AllowBodyForAllHttpMethods { get; set; }

View File

@@ -156,9 +156,9 @@ namespace WireMock.Owin
UpdateScenarioState(targetMapping);
}
if (!targetMapping.IsAdminInterface && targetMapping.Webhook != null)
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{
await SendToWebhookAsync(targetMapping, request, response).ConfigureAwait(false);
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -191,18 +191,21 @@ namespace WireMock.Owin
await CompletedTask;
}
private async Task SendToWebhookAsync(IMapping mapping, RequestMessage request, ResponseMessage response)
private async Task SendToWebhooksAsync(IMapping mapping, RequestMessage request, ResponseMessage response)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
for (int index = 0; index < mapping.Webhooks.Length; index++)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
try
{
await webhookSender.SendAsync(httpClientForWebhook, mapping.Webhook.Request, request, response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error($"Sending message to Webhook Mapping '{mapping.Guid}' failed. Exception: {ex}");
try
{
await webhookSender.SendAsync(httpClientForWebhook, mapping.Webhooks[index].Request, request, response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error($"Sending message to Webhook [{index}] from Mapping '{mapping.Guid}' failed. Exception: {ex}");
}
}
}

View File

@@ -8,6 +8,7 @@ using WireMock.Util;
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Owin
@@ -36,6 +37,10 @@ namespace WireMock.Owin
public Action<IAppBuilder> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
public Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <inheritdoc cref="IWireMockMiddlewareOptions.FileSystemHandler"/>
public IFileSystemHandler FileSystemHandler { get; set; }

View File

@@ -84,10 +84,18 @@ namespace WireMock.Serialization
Response = new ResponseModel
{
Delay = (int?)response.Delay?.TotalMilliseconds
},
Webhook = WebhookMapper.Map(mapping.Webhook)
}
};
if (mapping.Webhooks?.Length == 1)
{
mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]);
}
else if (mapping.Webhooks?.Length > 1)
{
mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray();
}
if (bodyMatcher?.Matchers != null)
{
mappingModel.Request.Body = new BodyModel();

View File

@@ -103,14 +103,14 @@ namespace WireMock.Server
IRespondWithAProvider WillSetStateTo(int state, int? times = 1);
/// <summary>
/// Add a Webbook to call after the response has been generated.
/// Add (multiple) Webhook(s) to call after the response has been generated.
/// </summary>
/// <param name="webhook">The Webhook</param>
/// <param name="webhooks">The Webhooks</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(IWebhook webhook);
IRespondWithAProvider WithWebhook(params IWebhook[] webhooks);
/// <summary>
/// Add a Webbook to call after the response has been generated.
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>
@@ -129,7 +129,7 @@ namespace WireMock.Server
);
/// <summary>
/// Add a Webbook to call after the response has been generated.
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>

View File

@@ -1,209 +1,212 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private int _priority;
private string _title;
private string _path;
private string _executionConditionState;
private string _nextState;
private string _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly IWireMockServerSettings _settings;
private readonly bool _saveToFile;
public Guid Guid { get; private set; } = Guid.NewGuid();
public IWebhook[] Webhooks { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, IWireMockServerSettings settings, bool saveToFile = false)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
_settings = settings;
_saveToFile = saveToFile;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks), _saveToFile);
}
/// <see cref="IRespondWithAProvider.WithGuid(string)"/>
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <see cref="IRespondWithAProvider.WithGuid(Guid)"/>
public IRespondWithAProvider WithGuid(Guid guid)
{
Guid = guid;
return this;
}
/// <see cref="IRespondWithAProvider.WithTitle"/>
public IRespondWithAProvider WithTitle(string title)
{
_title = title;
return this;
}
/// <see cref="IRespondWithAProvider.WithPath"/>
public IRespondWithAProvider WithPath(string path)
{
_path = path;
return this;
}
/// <see cref="IRespondWithAProvider.AtPriority"/>
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(string)"/>
public IRespondWithAProvider InScenario(string scenario)
{
_scenario = scenario;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(int)"/>
public IRespondWithAProvider InScenario(int scenario)
{
return InScenario(scenario.ToString());
}
/// <see cref="IRespondWithAProvider.WhenStateIs(string)"/>
public IRespondWithAProvider WhenStateIs(string state)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set state condition when no scenario is defined.");
}
_executionConditionState = state;
return this;
}
/// <see cref="IRespondWithAProvider.WhenStateIs(int)"/>
public IRespondWithAProvider WhenStateIs(int state)
{
return WhenStateIs(state.ToString());
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(string, int?)"/>
public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set next state when no scenario is defined.");
}
_nextState = state;
_timesInSameState = times ?? 1;
return this;
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(int, int?)"/>
public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
return WillSetStateTo(state.ToString(), times);
}
/// <see cref="IRespondWithAProvider.WithWebhook(IWebhook[])"/>
public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks)
{
Check.HasNoNulls(webhooks, nameof(webhooks));
Webhooks = webhooks;
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, string, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] string body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private int _priority;
private string _title;
private string _path;
private string _executionConditionState;
private string _nextState;
private string _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly IWireMockServerSettings _settings;
private readonly bool _saveToFile;
public Guid Guid { get; private set; } = Guid.NewGuid();
public IWebhook Webhook { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, IWireMockServerSettings settings, bool saveToFile = false)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
_settings = settings;
_saveToFile = saveToFile;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhook), _saveToFile);
}
/// <see cref="IRespondWithAProvider.WithGuid(string)"/>
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <see cref="IRespondWithAProvider.WithGuid(Guid)"/>
public IRespondWithAProvider WithGuid(Guid guid)
{
Guid = guid;
return this;
}
/// <see cref="IRespondWithAProvider.WithTitle"/>
public IRespondWithAProvider WithTitle(string title)
{
_title = title;
return this;
}
/// <see cref="IRespondWithAProvider.WithPath"/>
public IRespondWithAProvider WithPath(string path)
{
_path = path;
return this;
}
/// <see cref="IRespondWithAProvider.AtPriority"/>
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(string)"/>
public IRespondWithAProvider InScenario(string scenario)
{
_scenario = scenario;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(int)"/>
public IRespondWithAProvider InScenario(int scenario)
{
return InScenario(scenario.ToString());
}
/// <see cref="IRespondWithAProvider.WhenStateIs(string)"/>
public IRespondWithAProvider WhenStateIs(string state)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set state condition when no scenario is defined.");
}
_executionConditionState = state;
return this;
}
/// <see cref="IRespondWithAProvider.WhenStateIs(int)"/>
public IRespondWithAProvider WhenStateIs(int state)
{
return WhenStateIs(state.ToString());
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(string, int?)"/>
public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set next state when no scenario is defined.");
}
_nextState = state;
_timesInSameState = times ?? 1;
return this;
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(int, int?)"/>
public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
return WillSetStateTo(state.ToString(), times);
}
/// <see cref="IRespondWithAProvider.WithWebhook(IWebhook)"/>
public IRespondWithAProvider WithWebhook(IWebhook webhook)
{
Webhook = webhook;
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(string,string, IDictionary{string, WireMockList{string}}, string, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] string body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhook = InitWebhook(url, method, headers, useTransformer, transformerType);
if (body != null)
{
Webhook.Request.BodyData = new BodyData
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsString = body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
};
}
return this;
}
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, object, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] object body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhook = InitWebhook(url, method, headers, useTransformer, transformerType);
/// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, object, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] object body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
if (body != null)
{
Webhook.Request.BodyData = new BodyData
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsJson = body,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyType.Json
};
}
return this;
}
}
return this;
}
private IWebhook InitWebhook(
string url,
string method,
@@ -222,6 +225,6 @@ namespace WireMock.Server
TransformerType = transformerType
}
};
}
}
}
}
}

View File

@@ -470,10 +470,15 @@ namespace WireMock.Server
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo);
}
if (mappingModel.Webhook?.Request != null)
if (mappingModel.Webhook != null)
{
respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook));
}
else if (mappingModel.Webhooks?.Length > 1)
{
var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray();
respondProvider = respondProvider.WithWebhook(webhooks);
}
respondProvider.RespondWith(responseBuilder);

View File

@@ -242,6 +242,7 @@ namespace WireMock.Server
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);

View File

@@ -4,6 +4,9 @@ using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
#if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Settings
{
@@ -109,6 +112,14 @@ namespace WireMock.Settings
[PublicAPI]
Action<object> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
/// <summary>
/// Action which is called with IServiceCollection when ASP.NET Core DI is being configured. [Optional]
/// </summary>
[PublicAPI]
Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <summary>
/// The IWireMockLogger which logs Debug, Info, Warning or Error
/// </summary>

View File

@@ -4,6 +4,9 @@ using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Handlers;
using WireMock.Logging;
#if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Settings
{
@@ -79,6 +82,13 @@ namespace WireMock.Settings
[JsonIgnore]
public Action<object> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
/// <inheritdoc cref="IWireMockServerSettings.AdditionalServiceRegistration"/>
[PublicAPI]
[JsonIgnore]
public Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <inheritdoc cref="IWireMockServerSettings.Logger"/>
[PublicAPI]
[JsonIgnore]

View File

@@ -121,12 +121,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Handlebars.Net.Helpers" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.1.2" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.1.9" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.1.9" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.1.9" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.1.9" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.1.9" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.1.9" />
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>

View File

@@ -1,6 +1,7 @@
using NFluent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using WireMock.Http;
using WireMock.Models;
using WireMock.Types;
@@ -28,7 +29,7 @@ namespace WireMock.Net.Tests.Http
}
[Fact]
public async void HttpRequestMessageHelper_Create_Bytes()
public async Task HttpRequestMessageHelper_Create_Bytes()
{
// Assign
var body = new BodyData
@@ -46,7 +47,7 @@ namespace WireMock.Net.Tests.Http
}
[Fact]
public async void HttpRequestMessageHelper_Create_Json()
public async Task HttpRequestMessageHelper_Create_Json()
{
// Assign
var body = new BodyData
@@ -64,7 +65,7 @@ namespace WireMock.Net.Tests.Http
}
[Fact]
public async void HttpRequestMessageHelper_Create_Json_With_ContentType_ApplicationJson()
public async Task HttpRequestMessageHelper_Create_Json_With_ContentType_ApplicationJson()
{
// Assign
var headers = new Dictionary<string, string[]> { { "Content-Type", new[] { "application/json" } } };
@@ -84,7 +85,7 @@ namespace WireMock.Net.Tests.Http
}
[Fact]
public async void HttpRequestMessageHelper_Create_Json_With_ContentType_ApplicationJson_UTF8()
public async Task HttpRequestMessageHelper_Create_Json_With_ContentType_ApplicationJson_UTF8()
{
// Assign
var headers = new Dictionary<string, string[]> { { "Content-Type", new[] { "application/json; charset=utf-8" } } };

View File

@@ -19,7 +19,7 @@ namespace WireMock.Net.Tests
public class ObservableLogEntriesTest
{
[Fact]
public async void WireMockServer_LogEntriesChanged_WithException_Should_LogError()
public async Task WireMockServer_LogEntriesChanged_WithException_Should_LogError()
{
// Assign
string path = $"/log_{Guid.NewGuid()}";
@@ -48,7 +48,7 @@ namespace WireMock.Net.Tests
}
[Fact]
public async void WireMockServer_LogEntriesChanged()
public async Task WireMockServer_LogEntriesChanged()
{
// Assign
string path = $"/log_{Guid.NewGuid()}";

View File

@@ -77,7 +77,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_NoMatch()
public async Task WireMockMiddleware_Invoke_NoMatch()
{
// Act
await _sut.Invoke(_contextMock.Object);
@@ -90,7 +90,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401()
public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary<string, string[]>());
@@ -113,7 +113,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401()
public async Task WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary<string, string[]> { { "h", new[] { "x" } } });
@@ -136,7 +136,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_RequestLogExpirationDurationIsDefined()
public async Task WireMockMiddleware_Invoke_RequestLogExpirationDurationIsDefined()
{
// Assign
_optionsMock.SetupGet(o => o.RequestLogExpirationDuration).Returns(1);
@@ -146,7 +146,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_True()
public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_True()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary<string, string[]>());
@@ -195,7 +195,7 @@ namespace WireMock.Net.Tests.Owin
}
[Fact]
public async void WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_False_But_WireMockServerSettings_SaveMapping_Is_True()
public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_False_But_WireMockServerSettings_SaveMapping_Is_True()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary<string, string[]>());

View File

@@ -65,25 +65,6 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(response.Message.BodyData.BodyAsString).Equals("");
}
[Fact]
public async Task Response_ProvideResponseAsync_Handlebars_RegexMatch_NoMatch_WithDefaultValue()
{
// Assign
var body = new BodyData { BodyAsString = "abc", DetectedBodyType = BodyType.String };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var responseBuilder = Response.Create()
.WithBody("{{Regex.Match request.body \"^?0$\" \"d\"}}")
.WithTransformer();
// Act
var response = await responseBuilder.ProvideResponseAsync(request, _settings);
// assert
Check.That(response.Message.BodyData.BodyAsString).Equals("d");
}
[Fact]
public async Task Response_ProvideResponseAsync_Handlebars_RegexMatch2()
{
@@ -122,25 +103,6 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(response.Message.BodyData.BodyAsString).Equals("");
}
[Fact]
public async Task Response_ProvideResponseAsync_Handlebars_RegexMatch2_NoMatch_WithDefaultValue()
{
// Assign
var body = new BodyData { BodyAsString = "{{\\test", DetectedBodyType = BodyType.String };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var responseBuilder = Response.Create()
.WithBody("{{#Regex.Match request.body \"^(?<proto>\\w+)://[^/]+?(?<port>\\d+)/?\" \"x\"}}{{this}}{{/Regex.Match}}")
.WithTransformer();
// Act
var response = await responseBuilder.ProvideResponseAsync(request, _settings);
// assert
Check.That(response.Message.BodyData.BodyAsString).Equals("x");
}
[Fact]
public void Response_ProvideResponseAsync_Handlebars_RegexMatch2_Throws()
{

View File

@@ -24,31 +24,34 @@ namespace WireMock.Net.Tests.Serialization
}
[Fact]
public void ToMappingModel()
public void ToMappingModel_With_SingleWebHook()
{
// Assign
var request = Request.Create();
var response = Response.Create();
var webhook = new Webhook
var webhooks = new IWebhook[]
{
Request = new WebhookRequest
new Webhook
{
Url = "https://test.com",
Headers = new Dictionary<string, WireMockList<string>>
Request = new WebhookRequest
{
{ "Single", new WireMockList<string>("x") },
{ "Multi", new WireMockList<string>("a", "b") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "b",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
Url = "https://test.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "Single", new WireMockList<string>("x") },
{ "Multi", new WireMockList<string>("a", "b") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "b",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
}
};
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhook);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -61,6 +64,8 @@ namespace WireMock.Net.Tests.Serialization
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.Webhooks.Should().BeNull();
model.Webhook.Request.Method.Should().Be("post");
model.Webhook.Request.Url.Should().Be("https://test.com");
model.Webhook.Request.Headers.Should().HaveCount(2);
@@ -68,6 +73,80 @@ namespace WireMock.Net.Tests.Serialization
model.Webhook.Request.BodyAsJson.Should().BeNull();
}
[Fact]
public void ToMappingModel_With_MultipleWebHooks()
{
// Assign
var request = Request.Create();
var response = Response.Create();
var webhooks = new IWebhook[]
{
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test1.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "One", new WireMockList<string>("x") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "1",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
},
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test2.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "First", new WireMockList<string>("x") },
{ "Second", new WireMockList<string>("a", "b") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "2",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
}
};
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks
);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().BeNull();
model.Response.BodyAsJsonIndented.Should().BeNull();
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.Webhook.Should().BeNull();
model.Webhooks[0].Request.Method.Should().Be("post");
model.Webhooks[0].Request.Url.Should().Be("https://test1.com");
model.Webhooks[0].Request.Headers.Should().HaveCount(1);
model.Webhooks[0].Request.Body.Should().Be("1");
model.Webhooks[1].Request.Method.Should().Be("post");
model.Webhooks[1].Request.Url.Should().Be("https://test2.com");
model.Webhooks[1].Request.Headers.Should().HaveCount(2);
model.Webhooks[1].Request.Body.Should().Be("2");
}
[Fact]
public void ToMappingModel_WithPriority_ReturnsPriority()
{

View File

@@ -422,7 +422,7 @@ namespace WireMock.Net.Tests
}
[Fact]
public async void WireMockServer_Admin_DeleteMappings()
public async Task WireMockServer_Admin_DeleteMappings()
{
// Arrange
var server = WireMockServer.Start(new WireMockServerSettings

View File

@@ -16,6 +16,74 @@ namespace WireMock.Net.Tests
{
public class WireMockServerWebhookTests
{
[Fact]
public async Task WireMockServer_WithWebhooks_Should_Send_Message_To_Webhooks()
{
// Assign
var serverReceivingTheWebhook1 = WireMockServer.Start();
serverReceivingTheWebhook1.Given(Request.Create().UsingPost()).RespondWith(Response.Create().WithStatusCode(200));
var serverReceivingTheWebhook2 = WireMockServer.Start();
serverReceivingTheWebhook2.Given(Request.Create().UsingPost()).RespondWith(Response.Create().WithStatusCode(200));
var webhook1 = new Webhook
{
Request = new WebhookRequest
{
Url = serverReceivingTheWebhook1.Urls[0],
Method = "post",
BodyData = new BodyData
{
BodyAsString = "1",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
};
var webhook2 = new Webhook
{
Request = new WebhookRequest
{
Url = serverReceivingTheWebhook2.Urls[0],
Method = "post",
BodyData = new BodyData
{
BodyAsString = "2",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
};
// Act
var server = WireMockServer.Start();
server.Given(Request.Create().UsingPost())
.WithWebhook(webhook1, webhook2)
.RespondWith(Response.Create().WithBody("a-response"));
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{server.Urls[0]}/TST"),
Content = new StringContent("test")
};
// Assert
var response = await new HttpClient().SendAsync(request);
string content = await response.Content.ReadAsStringAsync();
response.StatusCode.Should().Be(HttpStatusCode.OK);
content.Should().Be("a-response");
serverReceivingTheWebhook1.LogEntries.Should().HaveCount(1);
serverReceivingTheWebhook2.LogEntries.Should().HaveCount(1);
server.Dispose();
serverReceivingTheWebhook1.Dispose();
serverReceivingTheWebhook2.Dispose();
}
[Fact]
public async Task WireMockServer_WithWebhook_Should_Send_Message_To_Webhook()
{