An OpenApi (swagger) parser to generate MappingModel or mapping.json file (#479)

* wip

* .

* .

* nuget

* .

* .

* WithMappingModel

* tests

* json

* codefactor

* sign

* .

* interface

* sln

* comments
This commit is contained in:
Stef Heyenrath
2020-07-01 09:57:52 +02:00
committed by GitHub
parent 769ddc1fd3
commit e2fbfda3f0
31 changed files with 4907 additions and 646 deletions

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using JetBrains.Annotations;
using WireMock.Admin.Mappings;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server interface.
/// </summary>
public interface IWireMockServer : IDisposable
{
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
bool IsStarted { get; }
//IEnumerable<LogEntry> LogEntries { get; }
/// <summary>
/// Gets the mappings as MappingModels.
/// </summary>
IEnumerable<MappingModel> MappingModels { get; }
//IEnumerable<IMapping> Mappings { get; }
/// <summary>
/// Gets the ports.
/// </summary>
List<int> Ports { get; }
/// <summary>
/// Gets the urls.
/// </summary>
string[] Urls { get; }
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
/// <summary>
/// Occurs when [log entries changed].
/// </summary>
event NotifyCollectionChangedEventHandler LogEntriesChanged;
/// <summary>
/// Adds the catch all mapping.
/// </summary>
void AddCatchAllMapping();
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
void AddGlobalProcessingDelay(TimeSpan delay);
/// <summary>
/// Allows the partial mapping.
/// </summary>
void AllowPartialMapping(bool allow = true);
/// <summary>
/// Deletes a LogEntry.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteLogEntry(Guid guid);
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteMapping(Guid guid);
//IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers);
//IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// <summary>
/// Reads a static mapping file and adds or updates the mapping.
/// </summary>
/// <param name="path">The path.</param>
bool ReadStaticMappingAndAddOrUpdate([NotNull] string path);
/// <summary>
/// Reads the static mappings from a folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void ReadStaticMappings([CanBeNull] string folder = null);
/// <summary>
/// Removes the basic authentication.
/// </summary>
void RemoveBasicAuthentication();
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
void Reset();
/// <summary>
/// Resets the Mappings.
/// </summary>
void ResetMappings();
/// <summary>
/// Resets all Scenarios.
/// </summary>
void ResetScenarios();
/// <summary>
/// Resets the LogEntries.
/// </summary>
void ResetLogEntries();
/// <summary>
/// Saves the static mappings.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void SaveStaticMappings([CanBeNull] string folder = null);
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
void SetBasicAuthentication([NotNull] string username, [NotNull] string password);
/// <summary>
/// Sets the maximum RequestLog count.
/// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount);
/// <summary>
/// Sets RequestLog expiration in hours.
/// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration);
/// <summary>
/// Stop this server.
/// </summary>
void Stop();
/// <summary>
/// Watches the static mappings for changes.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void WatchStaticMappings([CanBeNull] string folder = null);
/// <summary>
/// Register the mappings (via <see cref="MappingModel"/>).
/// </summary>
/// <param name="mappings">The MappingModels</param>
IWireMockServer WithMapping(params MappingModel[] mappings);
/// <summary>
/// Register the mappings (via json string).
/// </summary>
/// <param name="mappings">The mapping(s) as json string.</param>
IWireMockServer WithMapping(string mappings);
}
}

View File

@@ -29,7 +29,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

View File

@@ -0,0 +1,92 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Extensions
{
internal static class OpenApiSchemaExtensions
{
/// <summary>
/// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger
/// </summary>
public static bool TryGetXNullable(this OpenApiSchema schema, out bool value)
{
value = false;
if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean)
{
value = openApiBoolean.Value;
return true;
}
return false;
}
public static SchemaType GetSchemaType(this OpenApiSchema schema)
{
switch (schema?.Type)
{
case "object":
return SchemaType.Object;
case "array":
return SchemaType.Array;
case "integer":
return SchemaType.Integer;
case "number":
return SchemaType.Number;
case "boolean":
return SchemaType.Boolean;
case "string":
return SchemaType.String;
case "file":
return SchemaType.File;
default:
return SchemaType.Unknown;
}
}
public static SchemaFormat GetSchemaFormat(this OpenApiSchema schema)
{
switch (schema?.Format)
{
case "float":
return SchemaFormat.Float;
case "double":
return SchemaFormat.Double;
case "int32":
return SchemaFormat.Int32;
case "int64":
return SchemaFormat.Int64;
case "date":
return SchemaFormat.Date;
case "date-time":
return SchemaFormat.DateTime;
case "password":
return SchemaFormat.Password;
case "byte":
return SchemaFormat.Byte;
case "binary":
return SchemaFormat.Binary;
default:
return SchemaFormat.Undefined;
}
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions
{
public static class WireMockServerExtensions
{
/// <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>
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));
}
var mappings = new WireMockOpenApiParser().FromFile(path, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
/// <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="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{
var mappings = new WireMockOpenApiParser().FromStream(stream, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 document.
/// </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)
{
var mappings = new WireMockOpenApiParser().FromDocument(document);
return server.WithMapping(mappings.ToArray());
}
}
}

View File

@@ -0,0 +1,37 @@
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);
/// <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);
}
}

View File

@@ -0,0 +1,25 @@
namespace WireMock.Net.OpenApiParser.Types
{
internal enum SchemaFormat
{
Float,
Double,
Int32,
Int64,
Date,
DateTime,
Password,
Byte,
Binary,
Undefined
}
}

View File

@@ -0,0 +1,21 @@
namespace WireMock.Net.OpenApiParser.Types
{
internal enum SchemaType
{
Object,
Array,
String,
Integer,
Number,
Boolean,
File,
Unknown
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Globalization;
namespace WireMock.Net.OpenApiParser.Utils
{
internal static class DateTimeUtils
{
public static string ToRfc3339DateTime(DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
public static string ToRfc3339Date(DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo);
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.OpenApi.Models;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils
{
internal static class ExampleValueGenerator
{
public static object GetExampleValue(OpenApiSchema schema)
{
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
return true;
case SchemaType.Integer:
return 42;
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
return 4.2f;
default:
return 4.2d;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
return DateTimeUtils.ToRfc3339Date(DateTime.UtcNow);
case SchemaFormat.DateTime:
return DateTimeUtils.ToRfc3339DateTime(DateTime.UtcNow);
case SchemaFormat.Byte:
return new byte[] { 48, 49, 50 };
case SchemaFormat.Binary:
return "example-object";
default:
return "example-string";
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<VersionPrefix>1.2.4-preview-01</VersionPrefix>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.2.0" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Options\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,289 @@
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;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private const int ArrayItems = 3;
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
{
diagnostic = new OpenApiDiagnostic();
document = new RamlConverter().ConvertToOpenApiDocument(path);
}
else
{
var reader = new OpenApiStreamReader();
document = reader.Read(File.OpenRead(path), out diagnostic);
}
return FromDocument(document);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream" />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument)
{
return MapPaths(openApiDocument.Paths);
}
private static IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
{
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();
}
}
}
}

View File

@@ -36,7 +36,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2018.2.1" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="7.8.0.7320">
<PrivateAssets>all</PrivateAssets>

View File

@@ -109,14 +109,11 @@ namespace WireMock.Server
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete));
}
}
#endregion
#region StaticMappings
/// <summary>
/// Saves the static mappings.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
/// <inheritdoc cref="IWireMockServer.SaveStaticMappings" />
[PublicAPI]
public void SaveStaticMappings([CanBeNull] string folder = null)
{
@@ -124,12 +121,9 @@ namespace WireMock.Server
{
SaveMappingToFile(mapping, folder);
}
}
/// <summary>
/// Reads the static mappings from a folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
}
/// <inheritdoc cref="IWireMockServer.ReadStaticMappings" />
[PublicAPI]
public void ReadStaticMappings([CanBeNull] string folder = null)
{
@@ -157,12 +151,9 @@ namespace WireMock.Server
_settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename);
}
}
}
/// <summary>
/// Watches the static mappings for changes.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
}
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
[PublicAPI]
public void WatchStaticMappings([CanBeNull] string folder = null)
{
@@ -218,12 +209,9 @@ namespace WireMock.Server
};
watcher.EnableRaisingEvents = true;
}
/// <summary>
/// Reads a static mapping file and adds or updates the mapping.
/// </summary>
/// <param name="path">The path.</param>
}
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
[PublicAPI]
public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path)
{
@@ -233,16 +221,16 @@ namespace WireMock.Server
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value))
{
var mappingModels = DeserializeObjectToArray<MappingModel>(JsonUtils.DeserializeObject(value));
var mappingModels = DeserializeJsonToArray<MappingModel>(value);
foreach (var mappingModel in mappingModels)
{
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path);
ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path);
}
else
{
DeserializeAndAddOrUpdateMapping(mappingModel, null, path);
ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path);
}
}
@@ -409,7 +397,7 @@ namespace WireMock.Server
Guid guid = ParseGuidFromRequestMessage(requestMessage);
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
Guid? guidFromPut = DeserializeAndAddOrUpdateMapping(mappingModel, guid);
Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut);
}
@@ -484,13 +472,13 @@ namespace WireMock.Server
var mappingModels = DeserializeRequestMessageToArray<MappingModel>(requestMessage);
if (mappingModels.Length == 1)
{
Guid? guid = DeserializeAndAddOrUpdateMapping(mappingModels[0]);
Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create("Mapping added", 201, guid);
}
foreach (var mappingModel in mappingModels)
{
DeserializeAndAddOrUpdateMapping(mappingModel);
ConvertMappingAndRegisterAsRespondProvider(mappingModel);
}
return ResponseMessageBuilder.Create("Mappings added", 201);
@@ -507,7 +495,7 @@ namespace WireMock.Server
}
}
private Guid? DeserializeAndAddOrUpdateMapping(MappingModel mappingModel, Guid? guid = null, string path = null)
private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null)
{
Check.NotNull(mappingModel, nameof(mappingModel));
Check.NotNull(mappingModel.Request, nameof(mappingModel.Request));
@@ -987,5 +975,10 @@ namespace WireMock.Server
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult };
}
private T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
}
}
}

View File

@@ -12,10 +12,8 @@ using WireMock.Matchers.Request;
namespace WireMock.Server
{
public partial class WireMockServer
{
/// <summary>
/// Occurs when [log entries changed].
/// </summary>
{
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
[PublicAPI]
public event NotifyCollectionChangedEventHandler LogEntriesChanged
{
@@ -35,14 +33,14 @@ namespace WireMock.Server
}
remove => _options.LogEntries.CollectionChanged -= value;
}
}
/// <summary>
/// Gets the request logs.
/// </summary>
[PublicAPI]
public IEnumerable<LogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
public IEnumerable<LogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
/// <summary>
/// The search log-entries based on matchers.
/// </summary>
@@ -68,21 +66,16 @@ namespace WireMock.Server
}
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
}
/// <summary>
/// Resets the LogEntries.
/// </summary>
}
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
[PublicAPI]
public void ResetLogEntries()
{
_options.LogEntries.Clear();
}
/// <summary>
/// Deletes a LogEntry.
/// </summary>
/// <param name="guid">The unique identifier.</param>
}
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
[PublicAPI]
public bool DeleteLogEntry(Guid guid)
{

View File

@@ -1,493 +1,480 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Admin.Mappings;
using WireMock.Exceptions;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer : IDisposable
{
private const int ServerStartDelayInMs = 100;
private readonly IWireMockServerSettings _settings;
private readonly IOwinSelfHost _httpServer;
private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
private readonly MappingConverter _mappingConverter;
private readonly MatcherMapper _matcherMapper;
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
[PublicAPI]
public bool IsStarted => _httpServer != null && _httpServer.IsStarted;
/// <summary>
/// Gets the ports.
/// </summary>
[PublicAPI]
public List<int> Ports { get; }
/// <summary>
/// Gets the urls.
/// </summary>
[PublicAPI]
public string[] Urls { get; }
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IEnumerable<IMapping> Mappings => _options.Mappings.Values.ToArray();
/// <summary>
/// Gets the mappings as MappingModels.
/// </summary>
[PublicAPI]
public IEnumerable<MappingModel> MappingModels => ToMappingModels();
/// <summary>
/// Gets the scenarios.
/// </summary>
[PublicAPI]
public ConcurrentDictionary<string, ScenarioState> Scenarios => new ConcurrentDictionary<string, ScenarioState>(_options.Scenarios);
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_httpServer != null)
{
_httpServer.StopAsync();
}
}
#endregion
#region Start/Stop
/// <summary>
/// Starts the specified settings.
/// </summary>
/// <param name="settings">The WireMockServerSettings.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start([NotNull] IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
return new WireMockServer(settings);
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = ssl
});
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = ssl,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface and read static mappings.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true,
ReadStaticMappings = true
});
}
protected WireMockServer(IWireMockServerSettings settings)
{
_settings = settings;
// Set default values if not provided
_settings.Logger = settings.Logger ?? new WireMockNullLogger();
_settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
_settings.Logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)");
_settings.Logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));
HostUrlOptions urlOptions;
if (settings.Urls != null)
{
urlOptions = new HostUrlOptions
{
Urls = settings.Urls
};
}
else
{
urlOptions = new HostUrlOptions
{
UseSSL = settings.UseSSL == true,
Port = settings.Port
};
}
_options.FileSystemHandler = _settings.FileSystemHandler;
_options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit;
_options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit;
_options.Logger = _settings.Logger;
_options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing;
_matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(_matcherMapper);
#if USE_ASPNETCORE
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);
#endif
var startTask = _httpServer.StartAsync();
using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
{
while (!_httpServer.IsStarted)
{
// Throw exception if service start fails
if (_httpServer.RunningException != null)
{
throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException);
}
if (ctsStartTimeout.IsCancellationRequested)
{
// In case of an aggregate exception, throw the exception.
if (startTask.Exception != null)
{
throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception);
}
// Else throw TimeoutException
throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}");
}
ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs);
}
Urls = _httpServer.Urls.ToArray();
Ports = _httpServer.Ports;
}
if (settings.AllowBodyForAllHttpMethods == true)
{
_options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods;
_settings.Logger.Info("AllowBodyForAllHttpMethods is set to True");
}
if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true)
{
_options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True");
}
if (settings.AllowPartialMapping == true)
{
AllowPartialMapping();
}
if (settings.StartAdminInterface == true)
{
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{
SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword);
}
InitAdmin();
}
if (settings.ReadStaticMappings == true)
{
ReadStaticMappings();
}
if (settings.WatchStaticMappings == true)
{
WatchStaticMappings();
}
if (settings.ProxyAndRecordSettings != null)
{
InitProxyAndRecord(settings);
}
if (settings.RequestLogExpirationDuration != null)
{
SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration);
}
if (settings.MaxRequestLogCount != null)
{
SetMaxRequestLogCount(settings.MaxRequestLogCount);
}
}
/// <summary>
/// Stop this server.
/// </summary>
[PublicAPI]
public void Stop()
{
var result = _httpServer?.StopAsync();
result?.Wait(); // wait for stop to actually happen
}
#endregion
/// <summary>
/// Adds the catch all mapping.
/// </summary>
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404)));
}
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetMappings();
}
/// <summary>
/// Resets the Mappings.
/// </summary>
[PublicAPI]
public void ResetMappings()
{
foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface))
{
_options.Mappings.TryRemove(nonAdmin.Key, out _);
}
}
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
// Check a mapping exists with the same GUID, if so, remove it.
if (_options.Mappings.ContainsKey(guid))
{
return _options.Mappings.TryRemove(guid, out _);
}
return false;
}
private bool DeleteMapping(string path)
{
// Check a mapping exists with the same path, if so, remove it.
var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase));
return DeleteMapping(mapping.Key);
}
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
_options.RequestProcessingDelay = delay;
}
/// <summary>
/// Allows the partial mapping.
/// </summary>
[PublicAPI]
public void AllowPartialMapping(bool allow = true)
{
_settings.Logger.Info("AllowPartialMapping is set to {0}", allow);
_options.AllowPartialMapping = allow;
}
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
[PublicAPI]
public void SetBasicAuthentication([NotNull] string username, [NotNull] string password)
{
Check.NotNull(username, nameof(username));
Check.NotNull(password, nameof(password));
string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_options.AuthorizationMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, "^(?i)BASIC " + authorization + "$");
}
/// <summary>
/// Removes the basic authentication.
/// </summary>
[PublicAPI]
public void RemoveBasicAuthentication()
{
_options.AuthorizationMatcher = null;
}
/// <summary>
/// Sets the maximum RequestLog count.
/// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
[PublicAPI]
public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount)
{
_options.MaxRequestLogCount = maxRequestLogCount;
}
/// <summary>
/// Sets RequestLog expiration in hours.
/// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
[PublicAPI]
public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration)
{
_options.RequestLogExpirationDuration = requestLogExpirationDuration;
}
/// <summary>
/// Resets the Scenarios.
/// </summary>
[PublicAPI]
public void ResetScenarios()
{
_options.Scenarios.Clear();
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile);
}
private void RegisterMapping(IMapping mapping, bool saveToFile)
{
// Check a mapping exists with the same Guid, if so, replace it.
if (_options.Mappings.ContainsKey(mapping.Guid))
{
_options.Mappings[mapping.Guid] = mapping;
}
else
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
if (saveToFile)
{
SaveMappingToFile(mapping);
}
}
}
// 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Admin.Mappings;
using WireMock.Exceptions;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer : IWireMockServer
{
private const int ServerStartDelayInMs = 100;
private readonly IWireMockServerSettings _settings;
private readonly IOwinSelfHost _httpServer;
private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
private readonly MappingConverter _mappingConverter;
private readonly MatcherMapper _matcherMapper;
/// <inheritdoc cref="IWireMockServer.IsStarted" />
[PublicAPI]
public bool IsStarted => _httpServer != null && _httpServer.IsStarted;
/// <inheritdoc cref="IWireMockServer.Ports" />
[PublicAPI]
public List<int> Ports { get; }
/// <inheritdoc cref="IWireMockServer.Urls" />
[PublicAPI]
public string[] Urls { get; }
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IEnumerable<IMapping> Mappings => _options.Mappings.Values.ToArray();
/// <inheritdoc cref="IWireMockServer.MappingModels" />
[PublicAPI]
public IEnumerable<MappingModel> MappingModels => ToMappingModels();
/// <summary>
/// Gets the scenarios.
/// </summary>
[PublicAPI]
public ConcurrentDictionary<string, ScenarioState> Scenarios => new ConcurrentDictionary<string, ScenarioState>(_options.Scenarios);
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_httpServer != null)
{
_httpServer.StopAsync();
}
}
#endregion
#region Start/Stop
/// <summary>
/// Starts this WireMockServer with the specified settings.
/// </summary>
/// <param name="settings">The WireMockServerSettings.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start([NotNull] IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
return new WireMockServer(settings);
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = ssl
});
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = ssl,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface and read static mappings.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Check.NotNullOrEmpty(urls, nameof(urls));
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true,
ReadStaticMappings = true
});
}
protected WireMockServer(IWireMockServerSettings settings)
{
_settings = settings;
// Set default values if not provided
_settings.Logger = settings.Logger ?? new WireMockNullLogger();
_settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
_settings.Logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)");
_settings.Logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));
HostUrlOptions urlOptions;
if (settings.Urls != null)
{
urlOptions = new HostUrlOptions
{
Urls = settings.Urls
};
}
else
{
urlOptions = new HostUrlOptions
{
UseSSL = settings.UseSSL == true,
Port = settings.Port
};
}
_options.FileSystemHandler = _settings.FileSystemHandler;
_options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit;
_options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit;
_options.Logger = _settings.Logger;
_options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing;
_matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(_matcherMapper);
#if USE_ASPNETCORE
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);
#endif
var startTask = _httpServer.StartAsync();
using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
{
while (!_httpServer.IsStarted)
{
// Throw exception if service start fails
if (_httpServer.RunningException != null)
{
throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException);
}
if (ctsStartTimeout.IsCancellationRequested)
{
// In case of an aggregate exception, throw the exception.
if (startTask.Exception != null)
{
throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception);
}
// Else throw TimeoutException
throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}");
}
ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs);
}
Urls = _httpServer.Urls.ToArray();
Ports = _httpServer.Ports;
}
if (settings.AllowBodyForAllHttpMethods == true)
{
_options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods;
_settings.Logger.Info("AllowBodyForAllHttpMethods is set to True");
}
if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true)
{
_options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True");
}
if (settings.AllowPartialMapping == true)
{
AllowPartialMapping();
}
if (settings.StartAdminInterface == true)
{
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{
SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword);
}
InitAdmin();
}
if (settings.ReadStaticMappings == true)
{
ReadStaticMappings();
}
if (settings.WatchStaticMappings == true)
{
WatchStaticMappings();
}
if (settings.ProxyAndRecordSettings != null)
{
InitProxyAndRecord(settings);
}
if (settings.RequestLogExpirationDuration != null)
{
SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration);
}
if (settings.MaxRequestLogCount != null)
{
SetMaxRequestLogCount(settings.MaxRequestLogCount);
}
}
/// <inheritdoc cref="IWireMockServer.Stop" />
[PublicAPI]
public void Stop()
{
var result = _httpServer?.StopAsync();
result?.Wait(); // wait for stop to actually happen
}
#endregion
/// <inheritdoc cref="IWireMockServer.AddCatchAllMapping" />
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404)));
}
/// <inheritdoc cref="IWireMockServer.Reset" />
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetMappings();
}
/// <inheritdoc cref="IWireMockServer.ResetMappings" />
[PublicAPI]
public void ResetMappings()
{
foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface))
{
_options.Mappings.TryRemove(nonAdmin.Key, out _);
}
}
/// <inheritdoc cref="IWireMockServer.DeleteMapping" />
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
// Check a mapping exists with the same GUID, if so, remove it.
if (_options.Mappings.ContainsKey(guid))
{
return _options.Mappings.TryRemove(guid, out _);
}
return false;
}
private bool DeleteMapping(string path)
{
// Check a mapping exists with the same path, if so, remove it.
var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase));
return DeleteMapping(mapping.Key);
}
/// <inheritdoc cref="IWireMockServer.AddGlobalProcessingDelay" />
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
_options.RequestProcessingDelay = delay;
}
/// <inheritdoc cref="IWireMockServer.AllowPartialMapping" />
[PublicAPI]
public void AllowPartialMapping(bool allow = true)
{
_settings.Logger.Info("AllowPartialMapping is set to {0}", allow);
_options.AllowPartialMapping = allow;
}
/// <inheritdoc cref="IWireMockServer.SetBasicAuthentication" />
[PublicAPI]
public void SetBasicAuthentication([NotNull] string username, [NotNull] string password)
{
Check.NotNull(username, nameof(username));
Check.NotNull(password, nameof(password));
string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_options.AuthorizationMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, "^(?i)BASIC " + authorization + "$");
}
/// <inheritdoc cref="IWireMockServer.RemoveBasicAuthentication" />
[PublicAPI]
public void RemoveBasicAuthentication()
{
_options.AuthorizationMatcher = null;
}
/// <inheritdoc cref="IWireMockServer.SetMaxRequestLogCount" />
[PublicAPI]
public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount)
{
_options.MaxRequestLogCount = maxRequestLogCount;
}
/// <inheritdoc cref="IWireMockServer.SetRequestLogExpirationDuration" />
[PublicAPI]
public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration)
{
_options.RequestLogExpirationDuration = requestLogExpirationDuration;
}
/// <inheritdoc cref="IWireMockServer.ResetScenarios" />
[PublicAPI]
public void ResetScenarios()
{
_options.Scenarios.Clear();
}
/// <inheritdoc cref="IWireMockServer.WithMapping(MappingModel[])" />
[PublicAPI]
public IWireMockServer WithMapping(params MappingModel[] mappings)
{
foreach (var mapping in mappings)
{
ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid());
}
return this;
}
/// <inheritdoc cref="IWireMockServer.WithMapping(string)" />
[PublicAPI]
public IWireMockServer WithMapping(string mappings)
{
var mappingModels = DeserializeJsonToArray<MappingModel>(mappings);
foreach (var mappingModel in mappingModels)
{
ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid());
}
return this;
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile);
}
private void RegisterMapping(IMapping mapping, bool saveToFile)
{
// Check a mapping exists with the same Guid, if so, replace it.
if (_options.Mappings.ContainsKey(mapping.Guid))
{
_options.Mappings[mapping.Guid] = mapping;
}
else
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
if (saveToFile)
{
SaveMappingToFile(mapping);
}
}
}
}

View File

@@ -51,7 +51,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />