diff --git a/Directory.Build.props b/Directory.Build.props index 256ffcab..42632d86 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.4.20 + 1.4.21-preview-04 See CHANGELOG.md https://raw.githubusercontent.com/WireMock-Net/WireMock.Net/master/WireMock.Net-Logo.png https://github.com/WireMock-Net/WireMock.Net diff --git a/README.md b/README.md index 0c2477e5..858517da 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,11 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co |   **WireMock.Net** | [![NuGet Badge WireMock.Net](https://buildstats.info/nuget/WireMock.Net)](https://www.nuget.org/packages/WireMock.Net) | [![MyGet Badge WireMock.Net](https://buildstats.info/myget/wiremock-net/WireMock.Net?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net) |   **WireMock.Net.StandAlone** | [![NuGet Badge WireMock.Net](https://buildstats.info/nuget/WireMock.Net.StandAlone)](https://www.nuget.org/packages/WireMock.Net.StandAlone) | [![MyGet Badge WireMock.Net.StandAlone](https://buildstats.info/myget/wiremock-net/WireMock.Net.StandAlone?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.StandAlone) |   **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) -|   **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) |   **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) |   **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) +|   **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) +|   **WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient) + ## Development For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page. diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index fbf8fef1..51551bd2 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -92,6 +92,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure Pipelines", "Azure Pi azure-pipelines-nuget.yml = azure-pipelines-nuget.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Org.RestClient", "src\WireMock.Org.RestClient\WireMock.Org.RestClient.csproj", "{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Org.Abstractions", "src\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj", "{3BA5109E-5F30-4CC2-B699-02EC82560AA6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -214,6 +218,14 @@ Global {6580580B-1EFD-4922-B0EC-FF290DB279EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {6580580B-1EFD-4922-B0EC-FF290DB279EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {6580580B-1EFD-4922-B0EC-FF290DB279EE}.Release|Any CPU.Build.0 = Release|Any CPU + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|Any CPU.Build.0 = Release|Any CPU + {3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -250,6 +262,8 @@ Global {6580580B-1EFD-4922-B0EC-FF290DB279EE} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {1DAEFF47-D117-4E95-8B3E-4F7C8B92011A} = {7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D} {5B64F6CA-BF6B-4F67-BB2A-9C47E441703E} = {7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D} + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {3BA5109E-5F30-4CC2-B699-02EC82560AA6} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMock.Net.Client/WireMock.Net.Client.csproj b/examples/WireMock.Net.Client/WireMock.Net.Client.csproj index 59ae7f93..3bd29635 100644 --- a/examples/WireMock.Net.Client/WireMock.Net.Client.csproj +++ b/examples/WireMock.Net.Client/WireMock.Net.Client.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj b/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj index e7af8c79..c667c1cf 100644 --- a/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj +++ b/examples/WireMock.Net.StandAlone.Net461/WireMock.Net.StandAlone.Net461.csproj @@ -204,8 +204,8 @@ ..\..\packages\Owin.1.0\lib\net40\Owin.dll - - ..\..\packages\RestEase.1.4.10\lib\net45\RestEase.dll + + ..\..\packages\RestEase.1.5.5\lib\net45\RestEase.dll ..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll diff --git a/examples/WireMock.Net.StandAlone.Net461/packages.config b/examples/WireMock.Net.StandAlone.Net461/packages.config index ff1fc9ea..5a7028ba 100644 --- a/examples/WireMock.Net.StandAlone.Net461/packages.config +++ b/examples/WireMock.Net.StandAlone.Net461/packages.config @@ -59,7 +59,7 @@ - + diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj index 88136a09..0de8c74e 100644 --- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj +++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj @@ -1,7 +1,7 @@ - Commonly used interfaces, models, enumerations and types. + Commonly used models, enumerations and types. WireMock.Net.Abstractions Stef Heyenrath net45;netstandard1.0;netstandard2.0;netstandard2.1 @@ -11,7 +11,7 @@ WireMock.Net.Abstractions WireMock.Net.Abstractions - wiremock;interfaces;models;classes;enumerations;types + wiremock;wiremock.org;interfaces;models;classes;enumerations;types WireMock {B6269AAC-170A-4346-8B9A-579DED3D9A94} true @@ -33,11 +33,11 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj index 1d26a816..732f4c42 100644 --- a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj +++ b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj @@ -1,4 +1,4 @@ - + A RestClient using RestEase to access the admin interface. @@ -31,7 +31,7 @@ - + diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index bea5df7e..c53d3099 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -1,167 +1,167 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using SimMetrics.Net; -using WireMock.Admin.Mappings; -using WireMock.Matchers; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using SimMetrics.Net; +using WireMock.Admin.Mappings; +using WireMock.Matchers; using WireMock.Plugin; -using WireMock.Settings; -using WireMock.Validation; - -namespace WireMock.Serialization -{ - internal class MatcherMapper - { - private readonly IWireMockServerSettings _settings; - - public MatcherMapper(IWireMockServerSettings settings) - { - Check.NotNull(settings, nameof(settings)); - _settings = settings; - } - - public IMatcher[] Map([CanBeNull] IEnumerable matchers) - { - return matchers?.Select(Map).Where(m => m != null).ToArray(); - } - - public IMatcher Map([CanBeNull] MatcherModel matcher) - { - if (matcher == null) - { - return null; - } - - string[] parts = matcher.Name.Split('.'); - string matcherName = parts[0]; - string matcherType = parts.Length > 1 ? parts[1] : null; - - string[] stringPatterns = (matcher.Patterns != null ? matcher.Patterns : new[] { matcher.Pattern }).OfType().ToArray(); - MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; - bool ignoreCase = matcher.IgnoreCase == true; - bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; - - switch (matcherName) +using WireMock.Settings; +using WireMock.Validation; + +namespace WireMock.Serialization +{ + internal class MatcherMapper + { + private readonly IWireMockServerSettings _settings; + + public MatcherMapper(IWireMockServerSettings settings) + { + Check.NotNull(settings, nameof(settings)); + _settings = settings; + } + + public IMatcher[] Map([CanBeNull] IEnumerable matchers) + { + return matchers?.Select(Map).Where(m => m != null).ToArray(); + } + + public IMatcher Map([CanBeNull] MatcherModel matcher) + { + if (matcher == null) { - case "NotNullOrEmptyMatcher": + return null; + } + + string[] parts = matcher.Name.Split('.'); + string matcherName = parts[0]; + string matcherType = parts.Length > 1 ? parts[1] : null; + + string[] stringPatterns = (matcher.Patterns != null ? matcher.Patterns : new[] { matcher.Pattern }).OfType().ToArray(); + MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; + bool ignoreCase = matcher.IgnoreCase == true; + bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; + + switch (matcherName) + { + case "NotNullOrEmptyMatcher": return new NotNullOrEmptyMatcher(matchBehaviour); - - case "CSharpCodeMatcher": - if (_settings.AllowCSharpCodeMatcher == true) - { - return PluginLoader.Load(matchBehaviour, stringPatterns); - } - - throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because IWireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); - - case "LinqMatcher": - return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case "ExactMatcher": - return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case "ExactObjectMatcher": - return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails); - - case "RegexMatcher": - return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); - - case "JsonMatcher": - object value = matcher.Pattern ?? matcher.Patterns; - return new JsonMatcher(matchBehaviour, value, ignoreCase, throwExceptionWhenMatcherFails); - - case "JsonPartialMatcher": - object matcherValue = matcher.Pattern ?? matcher.Patterns; - return new JsonPartialMatcher(matchBehaviour, matcherValue, ignoreCase, throwExceptionWhenMatcherFails); - - case "JsonPathMatcher": - return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case "JmesPathMatcher": - return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case "XPathMatcher": - return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case "WildcardMatcher": - return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); - - case "ContentTypeMatcher": - return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); - - case "SimMetricsMatcher": - SimMetricType type = SimMetricType.Levenstein; - if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) - { - throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); - } - - return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails); - - default: - throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); - } - } - - public MatcherModel[] Map([CanBeNull] IEnumerable matchers) - { - return matchers?.Select(Map).Where(m => m != null).ToArray(); - } - - public MatcherModel Map([CanBeNull] IMatcher matcher) - { - if (matcher == null) - { - return null; - } - - object[] patterns = new object[0]; // Default empty array - switch (matcher) - { - // If the matcher is a IStringMatcher, get the patterns. - case IStringMatcher stringMatcher: - patterns = stringMatcher.GetPatterns().Cast().ToArray(); - break; - - // If the matcher is a IValueMatcher, get the value (can be string or object). - case IValueMatcher valueMatcher: - patterns = new[] { valueMatcher.Value }; - break; - - // If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes. - case ExactObjectMatcher exactObjectMatcher: - patterns = new[] { exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes }; - break; - } - - bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null; - - bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null; - - return new MatcherModel - { - RejectOnMatch = rejectOnMatch, - IgnoreCase = ignoreCase, - Name = matcher.Name, - Pattern = patterns.Length == 1 ? patterns.First() : null, - Patterns = patterns.Length > 1 ? patterns : null - }; - } - - private ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, string stringPattern, bool throwException) - { - byte[] bytePattern; - try - { - bytePattern = Convert.FromBase64String(stringPattern); - } - catch - { - throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern)); - } - - return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException); - } - } + + case "CSharpCodeMatcher": + if (_settings.AllowCSharpCodeMatcher == true) + { + return PluginLoader.Load(matchBehaviour, stringPatterns); + } + + throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because IWireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); + + case "LinqMatcher": + return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); + + case "ExactMatcher": + return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); + + case "ExactObjectMatcher": + return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails); + + case "RegexMatcher": + return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); + + case "JsonMatcher": + object value = matcher.Pattern ?? matcher.Patterns; + return new JsonMatcher(matchBehaviour, value, ignoreCase, throwExceptionWhenMatcherFails); + + case "JsonPartialMatcher": + object matcherValue = matcher.Pattern ?? matcher.Patterns; + return new JsonPartialMatcher(matchBehaviour, matcherValue, ignoreCase, throwExceptionWhenMatcherFails); + + case "JsonPathMatcher": + return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); + + case "JmesPathMatcher": + return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); + + case "XPathMatcher": + return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); + + case "WildcardMatcher": + return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); + + case "ContentTypeMatcher": + return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); + + case "SimMetricsMatcher": + SimMetricType type = SimMetricType.Levenstein; + if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) + { + throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); + } + + return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails); + + default: + throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); + } + } + + public MatcherModel[] Map([CanBeNull] IEnumerable matchers) + { + return matchers?.Select(Map).Where(m => m != null).ToArray(); + } + + public MatcherModel Map([CanBeNull] IMatcher matcher) + { + if (matcher == null) + { + return null; + } + + object[] patterns = new object[0]; // Default empty array + switch (matcher) + { + // If the matcher is a IStringMatcher, get the patterns. + case IStringMatcher stringMatcher: + patterns = stringMatcher.GetPatterns().Cast().ToArray(); + break; + + // If the matcher is a IValueMatcher, get the value (can be string or object). + case IValueMatcher valueMatcher: + patterns = new[] { valueMatcher.Value }; + break; + + // If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes. + case ExactObjectMatcher exactObjectMatcher: + patterns = new[] { exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes }; + break; + } + + bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null; + + bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null; + + return new MatcherModel + { + RejectOnMatch = rejectOnMatch, + IgnoreCase = ignoreCase, + Name = matcher.Name, + Pattern = patterns.Length == 1 ? patterns.First() : null, + Patterns = patterns.Length > 1 ? patterns : null + }; + } + + private ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, string stringPattern, bool throwException) + { + byte[] bytePattern; + try + { + bytePattern = Convert.FromBase64String(stringPattern); + } + catch + { + throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern)); + } + + return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException); + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index b6970ad9..eda99b6b 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -39,6 +39,7 @@ namespace WireMock.Server private const string ContentTypeJson = "application/json"; private const string AdminFiles = "/__admin/files"; private const string AdminMappings = "/__admin/mappings"; + private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org"; private const string AdminRequests = "/__admin/requests"; private const string AdminSettings = "/__admin/settings"; private const string AdminScenarios = "/__admin/scenarios"; @@ -58,6 +59,7 @@ namespace WireMock.Server // __admin/mappings Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); + Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); // __admin/mappings/reset diff --git a/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs b/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs new file mode 100644 index 00000000..d603f4b1 --- /dev/null +++ b/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using JetBrains.Annotations; +using Newtonsoft.Json.Linq; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Util; +using WireMock.Validation; +using OrgMapping = WireMock.Org.Abstractions.Mapping; + +namespace WireMock.Server +{ + public partial class WireMockServer + { + /// + /// Read WireMock.org mapping json file. + /// + /// The path to the WireMock.org mapping json file. + [PublicAPI] + public void ReadStaticWireMockOrgMappingAndAddOrUpdate([NotNull] string path) + { + Check.NotNull(path, nameof(path)); + + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); + + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + { + var mappings = DeserializeJsonToArray(value); + foreach (var mapping in mappings) + { + if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) + { + ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, guidFromFilename, path); + } + else + { + ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, null, path); + } + } + } + } + + private ResponseMessage MappingsPostWireMockOrg(RequestMessage requestMessage) + { + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + if (mappingModels.Length == 1) + { + Guid? guid = ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModels[0]); + return ResponseMessageBuilder.Create("Mapping added", 201, guid); + } + + foreach (var mappingModel in mappingModels) + { + ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModel); + } + + return ResponseMessageBuilder.Create("Mappings added", 201); + } + catch (ArgumentException a) + { + _settings.Logger.Error("HttpStatusCode set to 400 {0}", a); + return ResponseMessageBuilder.Create(a.Message, 400); + } + catch (Exception e) + { + _settings.Logger.Error("HttpStatusCode set to 500 {0}", e); + return ResponseMessageBuilder.Create(e.ToString(), 500); + } + } + + private Guid? ConvertWireMockOrgMappingAndRegisterAsRespondProvider(OrgMapping mapping, Guid? guid = null, string path = null) + { + var requestBuilder = Request.Create(); + + var request = mapping.Request; + if (request != null) + { + if (request.Url != null) + { + requestBuilder = requestBuilder.WithUrl(request.Url); + } + else if (request.UrlPattern != null) + { + requestBuilder = requestBuilder.WithUrl(new RegexMatcher(request.UrlPattern)); + } + else if (request.UrlPath != null) + { + requestBuilder = requestBuilder.WithPath(request.Url); + } + else if (request.UrlPathPattern != null) + { + requestBuilder = requestBuilder.WithPath(new RegexMatcher(request.UrlPathPattern)); + } + + if (request.Method != null) + { + requestBuilder = requestBuilder.UsingMethod(request.Method); + } + + /* + "headers" : { + "Accept" : { + "contains" : "xml" + } + } + */ + if (request.Headers is JObject headers) + { + ProcessWireMockOrgJObjectAndUseStringMatcher(headers, (key, match) => + { + requestBuilder = requestBuilder.WithHeader(key, match as IStringMatcher); + }); + } + + if (request.Cookies is JObject cookies) + { + ProcessWireMockOrgJObjectAndUseStringMatcher(cookies, (key, match) => + { + requestBuilder = requestBuilder.WithCookie(key, match as IStringMatcher); + }); + } + + /* + "queryParameters" : { + "search_term" : { + "equalTo" : "WireMock" + } + } + */ + if (request.QueryParameters is JObject queryParameters) + { + ProcessWireMockOrgJObjectAndUseStringMatcher(queryParameters, (key, match) => + { + requestBuilder = requestBuilder.WithParam(key, match); + }); + } + + /* + "bodyPatterns" : [ { + "equalToJson" : "{ "cityName": "São Paulo", "cityCode": 5001 }, + "ignoreArrayOrder" : true, + "ignoreExtraElements" : true + } ] + */ + if (request.BodyPatterns?.Any() == true) + { + var jObjectArray = request.BodyPatterns.Cast(); + var bodyPattern = jObjectArray.First(); + ProcessWireMockOrgJObjectAndUseIMatcher(bodyPattern, (match) => + { + requestBuilder = requestBuilder.WithBody(match); + }); + } + } + + IResponseBuilder responseBuilder = Response.Create(); + + var response = mapping.Response; + if (response != null) + { + responseBuilder = responseBuilder.WithStatusCode(response.Status); + + if (response.Headers is JObject responseHeaders) + { + ProcessWireMockOrgJObjectAndConvertToIDictionary(responseHeaders, (headers) => + { + responseBuilder = responseBuilder.WithHeaders(headers); + }); + } + + if (response.Transformers != null) + { + responseBuilder = responseBuilder.WithTransformer(); + } + + if (response.Body != null) + { + responseBuilder = responseBuilder.WithBody(response.Body); + } + + if (response.JsonBody != null) + { + responseBuilder = responseBuilder.WithBodyAsJson(response.JsonBody); + } + + if (response.Base64Body != null) + { + responseBuilder = responseBuilder.WithBody(Encoding.UTF8.GetString(Convert.FromBase64String(response.Base64Body))); + } + + if (response.BodyFileName != null) + { + responseBuilder = responseBuilder.WithBodyFromFile(response.BodyFileName); + } + } + + var respondProvider = Given(requestBuilder); + if (guid != null) + { + respondProvider = respondProvider.WithGuid(guid.Value); + } + else if (!string.IsNullOrEmpty(mapping.Uuid)) + { + respondProvider = respondProvider.WithGuid(new Guid(mapping.Uuid)); + } + + if (mapping.Name != null) + { + respondProvider = respondProvider.WithTitle(mapping.Name); + } + + if (path != null) + { + respondProvider = respondProvider.WithPath(path); + } + + respondProvider.RespondWith(responseBuilder); + + return respondProvider.Guid; + } + + private void ProcessWireMockOrgJObjectAndConvertToIDictionary(JObject items, Action> action) + { + var dict = new Dictionary(); + foreach (var item in items) + { + var key = item.Key; + var valueAsString = item.Value.Value(); + dict.Add(key, valueAsString); + } + + action(dict); + } + + private void ProcessWireMockOrgJObjectAndUseStringMatcher(JObject items, Action action) + { + foreach (var item in items) + { + var key = item.Key; + var match = item.Value.First as JProperty; + var valueAsString = match?.Value.Value(); + if (string.IsNullOrEmpty(valueAsString)) + { + continue; + } + + var matcher = ProcessAsStringMatcher(match, valueAsString); + if (matcher != null) + { + action(key, matcher); + } + } + } + + private void ProcessWireMockOrgJObjectAndUseIMatcher(JObject items, Action action) + { + IMatcher matcher = null; + + var firstItem = items.First as JProperty; + + if (firstItem?.Name == "equalToJson") + { + matcher = new JsonMatcher(firstItem.Value); + } + else + { + var valueAsString = (firstItem.Value as JValue)?.Value as string; + if (valueAsString == null) + { + return; + } + + matcher = ProcessAsStringMatcher(firstItem, valueAsString); + } + + if (matcher != null) + { + action(matcher); + } + } + + private static IStringMatcher ProcessAsStringMatcher(JProperty match, string valueAsString) + { + switch (match?.Name) + { + case "contains": + return new WildcardMatcher(valueAsString); + + case "matches": + return new RegexMatcher(valueAsString); + + case "equalTo": + return new ExactMatcher(valueAsString); + + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 8dd9e4a9..eda841fd 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -132,5 +132,6 @@ + \ No newline at end of file diff --git a/src/WireMock.Org.Abstractions/BasicAuthCredentials.cs b/src/WireMock.Org.Abstractions/BasicAuthCredentials.cs new file mode 100644 index 00000000..c66476eb --- /dev/null +++ b/src/WireMock.Org.Abstractions/BasicAuthCredentials.cs @@ -0,0 +1,12 @@ +namespace WireMock.Org.Abstractions +{ + /// + /// Pre-emptive basic auth credentials to match against + /// + public class BasicAuthCredentials + { + public string Password { get; set; } + + public string Username { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/GetAdminMappingsResponse.cs b/src/WireMock.Org.Abstractions/GetAdminMappingsResponse.cs new file mode 100644 index 00000000..336b72cf --- /dev/null +++ b/src/WireMock.Org.Abstractions/GetAdminMappingsResponse.cs @@ -0,0 +1,9 @@ +namespace WireMock.Org.Abstractions +{ + public class GetAdminMappingsResponse + { + public Mapping[] Mappings { get; set; } + + public Meta Meta { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/GetAdminRecordingsStatusResponse.cs b/src/WireMock.Org.Abstractions/GetAdminRecordingsStatusResponse.cs new file mode 100644 index 00000000..0dc8fc91 --- /dev/null +++ b/src/WireMock.Org.Abstractions/GetAdminRecordingsStatusResponse.cs @@ -0,0 +1,7 @@ +namespace WireMock.Org.Abstractions +{ + public class GetAdminRecordingsStatusResponse + { + public string Status { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/GetAdminRequestsUnmatchedNearMissesResponse.cs b/src/WireMock.Org.Abstractions/GetAdminRequestsUnmatchedNearMissesResponse.cs new file mode 100644 index 00000000..335f0280 --- /dev/null +++ b/src/WireMock.Org.Abstractions/GetAdminRequestsUnmatchedNearMissesResponse.cs @@ -0,0 +1,7 @@ +namespace WireMock.Org.Abstractions +{ + public class GetAdminRequestsUnmatchedNearMissesResponse + { + public NearMiss[] NearMisses { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/GetAdminScenariosResponse.cs b/src/WireMock.Org.Abstractions/GetAdminScenariosResponse.cs new file mode 100644 index 00000000..e4b98405 --- /dev/null +++ b/src/WireMock.Org.Abstractions/GetAdminScenariosResponse.cs @@ -0,0 +1,7 @@ +namespace WireMock.Org.Abstractions +{ + public class GetAdminScenariosResponse + { + public Scenario[] Scenarios { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/Mapping.cs b/src/WireMock.Org.Abstractions/Mapping.cs new file mode 100644 index 00000000..12676d7c --- /dev/null +++ b/src/WireMock.Org.Abstractions/Mapping.cs @@ -0,0 +1,59 @@ +namespace WireMock.Org.Abstractions +{ + public class Mapping + { + /// + /// This stub mapping's unique identifier + /// + public string Id { get; set; } + + /// + /// Alias for the id + /// + public string Uuid { get; set; } + + /// + /// The stub mapping's name + /// + public string Name { get; set; } + + public Request Request { get; set; } + + public Response Response { get; set; } + + /// + /// Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default. + /// + public bool Persistent { get; set; } + + /// + /// This stub mapping's priority relative to others. 1 is highest. + /// + public int Priority { get; set; } + + /// + /// The name of the scenario that this stub mapping is part of + /// + public string ScenarioName { get; set; } + + /// + /// The required state of the scenario in order for this stub to be matched. + /// + public string RequiredScenarioState { get; set; } + + /// + /// The new state for the scenario to be updated to after this stub is served. + /// + public string NewScenarioState { get; set; } + + /// + /// A map of the names of post serve action extensions to trigger and their parameters. + /// + public object PostServeActions { get; set; } + + /// + /// Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs. + /// + public object Metadata { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/Meta.cs b/src/WireMock.Org.Abstractions/Meta.cs new file mode 100644 index 00000000..bab36106 --- /dev/null +++ b/src/WireMock.Org.Abstractions/Meta.cs @@ -0,0 +1,7 @@ +namespace WireMock.Org.Abstractions +{ + public class Meta + { + public int Total { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/NearMiss.cs b/src/WireMock.Org.Abstractions/NearMiss.cs new file mode 100644 index 00000000..4ea19722 --- /dev/null +++ b/src/WireMock.Org.Abstractions/NearMiss.cs @@ -0,0 +1,35 @@ +namespace WireMock.Org.Abstractions +{ + public class NearMiss + { + /// + /// The HTTP request method + /// + public string Method { get; set; } + + /// + /// The path and query to match exactly against + /// + public string Url { get; set; } + + /// + /// The full URL to match against + /// + public string AbsoluteUrl { get; set; } + + /// + /// Header patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object Headers { get; set; } + + /// + /// Cookie patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object Cookies { get; set; } + + /// + /// Body string to match against + /// + public string Body { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/PostAdminRequestsCountResponse.cs b/src/WireMock.Org.Abstractions/PostAdminRequestsCountResponse.cs new file mode 100644 index 00000000..4043f54e --- /dev/null +++ b/src/WireMock.Org.Abstractions/PostAdminRequestsCountResponse.cs @@ -0,0 +1,7 @@ +namespace WireMock.Org.Abstractions +{ + public class PostAdminRequestsCountResponse + { + public int Count { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/Request.cs b/src/WireMock.Org.Abstractions/Request.cs new file mode 100644 index 00000000..33e896fe --- /dev/null +++ b/src/WireMock.Org.Abstractions/Request.cs @@ -0,0 +1,55 @@ +namespace WireMock.Org.Abstractions +{ + public class Request + { + /// + /// The HTTP request method e.g. GET + /// + public string Method { get; set; } + + /// + /// The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified. + /// + public string Url { get; set; } + + /// + /// The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified. + /// + public string UrlPath { get; set; } + + /// + /// The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified. + /// + public string UrlPathPattern { get; set; } + + /// + /// The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified. + /// + public string UrlPattern { get; set; } + + /// + /// Query parameter patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object QueryParameters { get; set; } + + /// + /// Header patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object Headers { get; set; } + + /// + /// Pre-emptive basic auth credentials to match against + /// + public BasicAuthCredentials BasicAuthCredentials { get; set; } + + /// + /// Cookie patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object Cookies { get; set; } + + /// + /// Request body patterns to match against in the <key>: { "<predicate>": "<value>" } form + /// + public object[] BodyPatterns { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/Response.cs b/src/WireMock.Org.Abstractions/Response.cs new file mode 100644 index 00000000..d15e5b91 --- /dev/null +++ b/src/WireMock.Org.Abstractions/Response.cs @@ -0,0 +1,75 @@ +namespace WireMock.Org.Abstractions +{ + public class Response + { + /// + /// The HTTP status code to be returned + /// + public int Status { get; set; } + + /// + /// The HTTP status message to be returned + /// + public string StatusMessage { get; set; } + + /// + /// Map of response headers to send + /// + public object Headers { get; set; } + + /// + /// Extra request headers to send when proxying to another host. + /// + public object AdditionalProxyRequestHeaders { get; set; } + + /// + /// The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified. + /// + public string Body { get; set; } + + /// + /// The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified. + /// + public string Base64Body { get; set; } + + /// + /// The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified. + /// + public object JsonBody { get; set; } + + /// + /// The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified. + /// + public string BodyFileName { get; set; } + + /// + /// The fault to apply (instead of a full, valid response). + /// + public string Fault { get; set; } + + /// + /// Number of milliseconds to delay be before sending the response. + /// + public int FixedDelayMilliseconds { get; set; } + + /// + /// Read-only flag indicating false if this was the default, unmatched response. Not present otherwise. + /// + public bool FromConfiguredStub { get; set; } + + /// + /// The base URL of the target to proxy matching requests to. + /// + public string ProxyBaseUrl { get; set; } + + /// + /// Parameters to apply to response transformers. + /// + public object TransformerParameters { get; set; } + + /// + /// List of names of transformers to apply to this response. + /// + public string Transformers { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/ResponseLogNormal.cs b/src/WireMock.Org.Abstractions/ResponseLogNormal.cs new file mode 100644 index 00000000..cce37656 --- /dev/null +++ b/src/WireMock.Org.Abstractions/ResponseLogNormal.cs @@ -0,0 +1,14 @@ +namespace WireMock.Org.Abstractions +{ + /// + /// Log normal randomly distributed response delay. + /// + public class ResponseLogNormal + { + public int Median { get; set; } + + public double Sigma { get; set; } + + public string Type { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/ResponseLogUniformlyDistributed.cs b/src/WireMock.Org.Abstractions/ResponseLogUniformlyDistributed.cs new file mode 100644 index 00000000..1b65bcde --- /dev/null +++ b/src/WireMock.Org.Abstractions/ResponseLogUniformlyDistributed.cs @@ -0,0 +1,14 @@ +namespace WireMock.Org.Abstractions +{ + /// + /// Uniformly distributed random response delay. + /// + public class ResponseLogUniformlyDistributed + { + public int Lower { get; set; } + + public string Type { get; set; } + + public int Upper { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/Scenario.cs b/src/WireMock.Org.Abstractions/Scenario.cs new file mode 100644 index 00000000..48456e33 --- /dev/null +++ b/src/WireMock.Org.Abstractions/Scenario.cs @@ -0,0 +1,22 @@ +namespace WireMock.Org.Abstractions +{ + public class Scenario + { + /// + /// The scenario ID + /// + public string Id { get; set; } + + /// + /// The scenario name + /// + public string Name { get; set; } + + public string PossibleStates { get; set; } + + /// + /// The current state of this scenario + /// + public string State { get; set; } + } +} diff --git a/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj b/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj new file mode 100644 index 00000000..77b786a8 --- /dev/null +++ b/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj @@ -0,0 +1,41 @@ + + + + Commonly used interfaces, models, enumerations and types. + WireMock.Org.Abstractions + Stef Heyenrath + net45;netstandard1.0;netstandard2.0;netstandard2.1 + + true + $(NoWarn);1591;8603 + + WireMock.Org.Abstractions + WireMock.Org.Abstractions + wiremock;wiremock.org;interfaces;models;classes;enumerations;types + WireMock + {3BA5109E-5F30-4CC2-B699-02EC82560AA6} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + ../WireMock.Net/WireMock.Net.ruleset + true + ../WireMock.Net/WireMock.Net.snk + + true + MIT + + + + + true + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Org.Abstractions/wiremock.org.json b/src/WireMock.Org.Abstractions/wiremock.org.json new file mode 100644 index 00000000..3acd1392 --- /dev/null +++ b/src/WireMock.Org.Abstractions/wiremock.org.json @@ -0,0 +1,6332 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "WireMock", + "version": "2.31.0" + }, + "externalDocs": { + "description": "WireMock user documentation", + "url": "http://wiremock.org/docs/" + }, + "servers": [ + { + "url": "/" + } + ], + "tags": [ + { + "name": "Stub Mappings", + "description": "Operations on stub mappings", + "externalDocs": { + "description": "User documentation", + "url": "http://wiremock.org/docs/stubbing/" + } + }, + { + "name": "Requests", + "description": "Logged requests and responses received by the mock service", + "externalDocs": { + "description": "User documentation", + "url": "http://wiremock.org/docs/verifying/" + } + }, + { + "name": "Near Misses", + "description": "Near misses allow querying of received requests or request patterns according to similarity", + "externalDocs": { + "description": "User documentation", + "url": "http://wiremock.org/docs/verifying/#near-misses" + } + }, + { + "name": "Recordings", + "description": "Stub mapping record and snapshot functions", + "externalDocs": { + "description": "User documentation", + "url": "http://wiremock.org/docs/record-playback/" + } + }, + { + "name": "Scenarios", + "description": "Scenarios support modelling of stateful behaviour", + "externalDocs": { + "description": "User documentation", + "url": "http://wiremock.org/docs/stateful-behaviour/" + } + }, + { + "name": "System", + "description": "Global operations" + } + ], + "paths": { + "/__admin/mappings": { + "get": { + "summary": "Get all stub mappings", + "tags": [ + "Stub Mappings" + ], + "parameters": [ + { + "description": "The maximum number of results to return", + "in": "query", + "name": "limit", + "required": false, + "example": 10, + "schema": { + "type": "integer" + } + }, + { + "description": "The start index of the results to return", + "in": "query", + "name": "offset", + "required": false, + "example": 0, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 4 + } + }, + "required": [ + "total" + ] + } + } + }, + "example": { + "meta": { + "total": 2 + }, + "mappings": [ + { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "uuid": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "request": { + "method": "GET", + "url": "/search?q=things", + "headers": { + "Accept": { + "equalTo": "application/json" + } + } + }, + "response": { + "status": 200, + "jsonBody": [ + "thing1", + "thing2" + ], + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "method": "POST", + "urlPath": "/some/things", + "bodyPatterns": [ + { + "equalToXml": "" + } + ] + }, + "response": { + "status": 201 + } + } + ] + } + } + }, + "description": "All stub mappings" + } + } + }, + "post": { + "summary": "Create a new stub mapping", + "tags": [ + "Stub Mappings" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "request": { + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "responses": { + "201": { + "description": "The stub mapping", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "priority": 3, + "request": { + "headers": { + "Accept": { + "equalTo": "text/plain" + } + }, + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete all stub mappings", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "Successfully deleted" + } + } + } + }, + "/__admin/mappings/reset": { + "post": { + "summary": "Reset stub mappings", + "description": "Restores stub mappings to the defaults defined back in the backing store", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "Successfully reset" + } + } + } + }, + "/__admin/mappings/save": { + "post": { + "summary": "Persist stub mappings", + "description": "Save all persistent stub mappings to the backing store", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "Successfully saved" + } + } + } + }, + "/__admin/mappings/{stubMappingId}": { + "parameters": [ + { + "description": "The UUID of stub mapping", + "in": "path", + "name": "stubMappingId", + "required": true, + "example": "730d3e32-d098-4169-a20c-554c3bedce58", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Get stub mapping by ID", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "The stub mapping", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "priority": 3, + "request": { + "headers": { + "Accept": { + "equalTo": "text/plain" + } + }, + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "404": { + "description": "Stub mapping not found" + } + } + }, + "put": { + "summary": "Update a stub mapping", + "tags": [ + "Stub Mappings" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "request": { + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "responses": { + "200": { + "description": "The stub mapping", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "priority": 3, + "request": { + "headers": { + "Accept": { + "equalTo": "text/plain" + } + }, + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "404": { + "description": "Stub mapping not found" + } + } + }, + "delete": { + "summary": "Delete a stub mapping", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Stub mapping not found" + } + } + } + }, + "/__admin/mappings/find-by-metadata": { + "post": { + "description": "Find stubs by matching on their metadata", + "tags": [ + "Stub Mappings" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "title": "String equals", + "type": "object", + "properties": { + "equalTo": { + "type": "boolean" + }, + "caseInsensitive": { + "type": "boolean" + } + }, + "required": [ + "equalTo" + ] + }, + { + "title": "String contains", + "type": "object", + "properties": { + "contains": { + "type": "string" + } + }, + "required": [ + "contains" + ] + }, + { + "title": "Regular expression match", + "type": "object", + "properties": { + "matches": { + "type": "string" + } + }, + "required": [ + "matches" + ] + }, + { + "title": "Negative regular expression match", + "type": "object", + "properties": { + "doesNotMatch": { + "type": "string" + } + }, + "required": [ + "doesNotMatch" + ] + }, + { + "title": "JSON equals", + "type": "object", + "properties": { + "equalToJson": { + "type": "string" + } + }, + "required": [ + "equalToJson" + ] + }, + { + "title": "JSONPath match", + "type": "object", + "properties": { + "matchesJsonPath": { + "type": "string" + }, + "ignoreArrayOrder": { + "type": "boolean" + }, + "ignoreExtraElements": { + "type": "boolean" + } + }, + "required": [ + "matchesJsonPath" + ] + }, + { + "title": "XML equality", + "type": "object", + "properties": { + "equalToXml": { + "type": "string" + } + }, + "required": [ + "equalToXml" + ] + }, + { + "title": "XPath match", + "type": "object", + "properties": { + "matchesXpath": { + "type": "string" + }, + "namespaces": { + "type": "object" + }, + "valuePattern": { + "type": "object" + } + }, + "required": [ + "matchesXpath" + ] + } + ] + }, + "example": { + "matchesJsonPath": { + "expression": "$.outer", + "equalToJson": "{ \"inner\": 42 }" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 4 + } + }, + "required": [ + "total" + ] + } + } + }, + "example": { + "meta": { + "total": 2 + }, + "mappings": [ + { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "uuid": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "request": { + "method": "GET", + "url": "/search?q=things", + "headers": { + "Accept": { + "equalTo": "application/json" + } + } + }, + "response": { + "status": 200, + "jsonBody": [ + "thing1", + "thing2" + ], + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "method": "POST", + "urlPath": "/some/things", + "bodyPatterns": [ + { + "equalToXml": "" + } + ] + }, + "response": { + "status": 201 + } + } + ] + } + } + }, + "description": "Matched stub mappings" + } + } + } + }, + "/__admin/mappings/remove-by-metadata": { + "post": { + "summary": "Delete stub mappings matching metadata", + "tags": [ + "Stub Mappings" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "title": "String equals", + "type": "object", + "properties": { + "equalTo": { + "type": "boolean" + }, + "caseInsensitive": { + "type": "boolean" + } + }, + "required": [ + "equalTo" + ] + }, + { + "title": "String contains", + "type": "object", + "properties": { + "contains": { + "type": "string" + } + }, + "required": [ + "contains" + ] + }, + { + "title": "Regular expression match", + "type": "object", + "properties": { + "matches": { + "type": "string" + } + }, + "required": [ + "matches" + ] + }, + { + "title": "Negative regular expression match", + "type": "object", + "properties": { + "doesNotMatch": { + "type": "string" + } + }, + "required": [ + "doesNotMatch" + ] + }, + { + "title": "JSON equals", + "type": "object", + "properties": { + "equalToJson": { + "type": "string" + } + }, + "required": [ + "equalToJson" + ] + }, + { + "title": "JSONPath match", + "type": "object", + "properties": { + "matchesJsonPath": { + "type": "string" + }, + "ignoreArrayOrder": { + "type": "boolean" + }, + "ignoreExtraElements": { + "type": "boolean" + } + }, + "required": [ + "matchesJsonPath" + ] + }, + { + "title": "XML equality", + "type": "object", + "properties": { + "equalToXml": { + "type": "string" + } + }, + "required": [ + "equalToXml" + ] + }, + { + "title": "XPath match", + "type": "object", + "properties": { + "matchesXpath": { + "type": "string" + }, + "namespaces": { + "type": "object" + }, + "valuePattern": { + "type": "object" + } + }, + "required": [ + "matchesXpath" + ] + } + ] + }, + "example": { + "matchesJsonPath": { + "expression": "$.outer", + "equalToJson": "{ \"inner\": 42 }" + } + } + } + } + }, + "responses": { + "200": { + "description": "The stub mappings were successfully removed" + } + } + } + }, + "/__admin/requests": { + "get": { + "summary": "Get all requests in journal", + "tags": [ + "Requests" + ], + "parameters": [ + { + "description": "The maximum number of results to return", + "in": "query", + "name": "limit", + "example": 10, + "schema": { + "type": "string" + } + }, + { + "description": "Only return logged requests after this date", + "in": "query", + "name": "since", + "example": "2016-10-05T12:33:01.000Z", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "requests": [ + { + "id": "45760a03-eebb-4387-ad0d-bb89b5d3d662", + "request": { + "url": "/received-request/9", + "absoluteUrl": "http://localhost:56715/received-request/9", + "method": "GET", + "clientIp": "127.0.0.1", + "headers": { + "Connection": "keep-alive", + "Host": "localhost:56715", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1471442494809, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-08-17T14:01:34Z" + }, + "responseDefinition": { + "status": 404, + "transformers": [], + "fromConfiguredStub": false, + "transformerParameters": {} + } + }, + { + "id": "6ae78311-0178-46c9-987a-fbfc528d54d8", + "request": { + "url": "/received-request/8", + "absoluteUrl": "http://localhost:56715/received-request/8", + "method": "GET", + "clientIp": "127.0.0.1", + "headers": { + "Connection": "keep-alive", + "Host": "localhost:56715", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1471442494802, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-08-17T14:01:34Z" + }, + "responseDefinition": { + "status": 404, + "transformers": [], + "fromConfiguredStub": false, + "transformerParameters": {} + } + }, + { + "id": "aba8e4ad-1b5b-4518-8f05-b2170a24de35", + "request": { + "url": "/received-request/7", + "absoluteUrl": "http://localhost:56715/received-request/7", + "method": "GET", + "clientIp": "127.0.0.1", + "headers": { + "Connection": "keep-alive", + "Host": "localhost:56715", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1471442494795, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-08-17T14:01:34Z" + }, + "responseDefinition": { + "status": 404, + "transformers": [], + "fromConfiguredStub": false, + "transformerParameters": {} + } + } + ], + "meta": { + "total": 9 + }, + "requestJournalDisabled": false + } + } + }, + "description": "List of received requests" + } + } + }, + "delete": { + "summary": "Delete all requests in journal", + "tags": [ + "Requests" + ], + "responses": { + "200": { + "description": "Successfully deleted" + } + } + } + }, + "/__admin/requests/{requestId}": { + "get": { + "summary": "Get request by ID", + "tags": [ + "Requests" + ], + "parameters": [ + { + "description": "The UUID of the logged request", + "in": "path", + "name": "requestId", + "required": true, + "example": "12fb14bb-600e-4bfa-bd8d-be7f12562c99", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "example": { + "id": "12fb14bb-600e-4bfa-bd8d-be7f12562c99", + "request": { + "url": "/received-request/2", + "absoluteUrl": "http://localhost:56738/received-request/2", + "method": "GET", + "clientIp": "127.0.0.1", + "headers": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1471442557047, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-08-17T14:02:37Z" + }, + "responseDefinition": { + "status": 404, + "transformers": [], + "fromConfiguredStub": false, + "transformerParameters": {} + } + } + } + } + }, + "404": { + "description": "Request not found" + } + } + }, + "delete": { + "summary": "Delete request by ID", + "tags": [ + "Requests" + ], + "parameters": [ + { + "description": "The UUID of the logged request", + "in": "path", + "name": "requestId", + "required": true, + "example": "12fb14bb-600e-4bfa-bd8d-be7f12562c99", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successfully deleted" + } + } + } + }, + "/__admin/requests/reset": { + "post": { + "deprecated": true, + "summary": "Empty the request journal", + "tags": [ + "Requests" + ], + "responses": { + "200": { + "description": "Successfully reset" + } + } + } + }, + "/__admin/requests/count": { + "post": { + "summary": "Count requests by criteria", + "description": "Count requests logged in the journal matching the specified criteria", + "tags": [ + "Requests" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "example": { + "method": "POST", + "url": "/resource", + "headers": { + "Content-Type": { + "matches": ".*/xml" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Number of matching requests", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 4 + } + } + } + } + } + } + } + } + }, + "/__admin/requests/remove": { + "post": { + "summary": "Remove requests by criteria", + "description": "Removed requests logged in the journal matching the specified criteria", + "tags": [ + "Requests" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "example": { + "method": "POST", + "url": "/resource", + "headers": { + "Content-Type": { + "matches": ".*/xml" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Removed request details", + "content": { + "application/json": { + "example": { + "requests": [ + { + "url": "/my/url", + "absoluteUrl": "http://mydomain.com/my/url", + "method": "GET", + "headers": { + "Accept-Language": "en-us,en;q=0.5", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + "body": "", + "browserProxyRequest": true, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + }, + { + "url": "/my/other/url", + "absoluteUrl": "http://my.other.domain.com/my/other/url", + "method": "POST", + "headers": { + "Accept": "text/plain", + "Content-Type": "text/plain" + }, + "body": "My text", + "browserProxyRequest": false, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + } + ] + } + } + } + } + } + } + }, + "/__admin/requests/remove-by-metadata": { + "post": { + "summary": "Delete requests mappings matching metadata", + "tags": [ + "Requests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "title": "String equals", + "type": "object", + "properties": { + "equalTo": { + "type": "boolean" + }, + "caseInsensitive": { + "type": "boolean" + } + }, + "required": [ + "equalTo" + ] + }, + { + "title": "String contains", + "type": "object", + "properties": { + "contains": { + "type": "string" + } + }, + "required": [ + "contains" + ] + }, + { + "title": "Regular expression match", + "type": "object", + "properties": { + "matches": { + "type": "string" + } + }, + "required": [ + "matches" + ] + }, + { + "title": "Negative regular expression match", + "type": "object", + "properties": { + "doesNotMatch": { + "type": "string" + } + }, + "required": [ + "doesNotMatch" + ] + }, + { + "title": "JSON equals", + "type": "object", + "properties": { + "equalToJson": { + "type": "string" + } + }, + "required": [ + "equalToJson" + ] + }, + { + "title": "JSONPath match", + "type": "object", + "properties": { + "matchesJsonPath": { + "type": "string" + }, + "ignoreArrayOrder": { + "type": "boolean" + }, + "ignoreExtraElements": { + "type": "boolean" + } + }, + "required": [ + "matchesJsonPath" + ] + }, + { + "title": "XML equality", + "type": "object", + "properties": { + "equalToXml": { + "type": "string" + } + }, + "required": [ + "equalToXml" + ] + }, + { + "title": "XPath match", + "type": "object", + "properties": { + "matchesXpath": { + "type": "string" + }, + "namespaces": { + "type": "object" + }, + "valuePattern": { + "type": "object" + } + }, + "required": [ + "matchesXpath" + ] + } + ] + }, + "example": { + "matchesJsonPath": { + "expression": "$.outer", + "equalToJson": "{ \"inner\": 42 }" + } + } + } + } + }, + "responses": { + "200": { + "description": "Removed request details", + "content": { + "application/json": { + "example": { + "requests": [ + { + "url": "/my/url", + "absoluteUrl": "http://mydomain.com/my/url", + "method": "GET", + "headers": { + "Accept-Language": "en-us,en;q=0.5", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + "body": "", + "browserProxyRequest": true, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + }, + { + "url": "/my/other/url", + "absoluteUrl": "http://my.other.domain.com/my/other/url", + "method": "POST", + "headers": { + "Accept": "text/plain", + "Content-Type": "text/plain" + }, + "body": "My text", + "browserProxyRequest": false, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + } + ] + } + } + } + } + } + } + }, + "/__admin/requests/find": { + "post": { + "summary": "Find requests by criteria", + "description": "Retrieve details of requests logged in the journal matching the specified criteria", + "tags": [ + "Requests" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "example": { + "method": "POST", + "url": "/resource", + "headers": { + "Content-Type": { + "matches": ".*/xml" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Matching request details", + "content": { + "application/json": { + "example": { + "requests": [ + { + "url": "/my/url", + "absoluteUrl": "http://mydomain.com/my/url", + "method": "GET", + "headers": { + "Accept-Language": "en-us,en;q=0.5", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + "body": "", + "browserProxyRequest": true, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + }, + { + "url": "/my/other/url", + "absoluteUrl": "http://my.other.domain.com/my/other/url", + "method": "POST", + "headers": { + "Accept": "text/plain", + "Content-Type": "text/plain" + }, + "body": "My text", + "browserProxyRequest": false, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + } + ] + } + } + } + } + } + } + }, + "/__admin/requests/unmatched": { + "get": { + "summary": "Find unmatched requests", + "description": "Get details of logged requests that weren't matched by any stub mapping", + "tags": [ + "Requests" + ], + "responses": { + "200": { + "description": "Unmatched request details", + "content": { + "application/json": { + "example": { + "requests": [ + { + "url": "/my/url", + "absoluteUrl": "http://mydomain.com/my/url", + "method": "GET", + "headers": { + "Accept-Language": "en-us,en;q=0.5", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + "body": "", + "browserProxyRequest": true, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + }, + { + "url": "/my/other/url", + "absoluteUrl": "http://my.other.domain.com/my/other/url", + "method": "POST", + "headers": { + "Accept": "text/plain", + "Content-Type": "text/plain" + }, + "body": "My text", + "browserProxyRequest": false, + "loggedDate": 1339083581823, + "loggedDateString": "2012-06-07 16:39:41" + } + ] + } + } + } + } + } + } + }, + "/__admin/requests/unmatched/near-misses": { + "get": { + "description": "Retrieve near-misses for all unmatched requests", + "tags": [ + "Near Misses" + ], + "responses": { + "200": { + "description": "Near misses matching criteria", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nearMisses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "description": "The HTTP request method", + "type": "string", + "example": "GET" + }, + "url": { + "description": "The path and query to match exactly against", + "type": "string", + "example": "/received-request/2" + }, + "absoluteUrl": { + "description": "The full URL to match against", + "type": "string", + "example": "http://localhost:56738/received-request/2" + }, + "headers": { + "description": "Header patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + } + }, + "cookies": { + "description": "Cookie patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": {} + }, + "body": { + "description": "Body string to match against", + "type": "string", + "example": "Hello world" + } + } + } + } + } + }, + "example": { + "nearMisses": [ + { + "request": { + "url": "/nomatch", + "absoluteUrl": "http://localhost:8080/nomatch", + "method": "GET", + "clientIp": "0:0:0:0:0:0:0:1", + "headers": { + "User-Agent": "curl/7.30.0", + "Accept": "*/*", + "Host": "localhost:8080" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1467402464520, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-07-01T19:47:44Z" + }, + "requestPattern": { + "url": "/almostmatch", + "method": "GET" + }, + "matchResult": { + "distance": 0.06944444444444445 + } + } + ] + } + } + } + } + } + } + }, + "/__admin/near-misses/request": { + "post": { + "summary": "Find near misses matching specific request", + "description": "Find at most 3 near misses for closest stub mappings to the specified request", + "tags": [ + "Near Misses" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "method": { + "description": "The HTTP request method", + "type": "string", + "example": "GET" + }, + "url": { + "description": "The path and query to match exactly against", + "type": "string", + "example": "/received-request/2" + }, + "absoluteUrl": { + "description": "The full URL to match against", + "type": "string", + "example": "http://localhost:56738/received-request/2" + }, + "headers": { + "description": "Header patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + } + }, + "cookies": { + "description": "Cookie patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": {} + }, + "body": { + "description": "Body string to match against", + "type": "string", + "example": "Hello world" + } + } + }, + "example": { + "url": "/actual", + "absoluteUrl": "http://localhost:8080/actual", + "method": "GET", + "clientIp": "0:0:0:0:0:0:0:1", + "headers": { + "User-Agent": "curl/7.30.0", + "Accept": "*/*", + "Host": "localhost:8080" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1467402464520, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-07-01T19:47:44Z" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Near misses matching criteria", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nearMisses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "description": "The HTTP request method", + "type": "string", + "example": "GET" + }, + "url": { + "description": "The path and query to match exactly against", + "type": "string", + "example": "/received-request/2" + }, + "absoluteUrl": { + "description": "The full URL to match against", + "type": "string", + "example": "http://localhost:56738/received-request/2" + }, + "headers": { + "description": "Header patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + } + }, + "cookies": { + "description": "Cookie patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": {} + }, + "body": { + "description": "Body string to match against", + "type": "string", + "example": "Hello world" + } + } + } + } + } + }, + "example": { + "nearMisses": [ + { + "request": { + "url": "/nomatch", + "absoluteUrl": "http://localhost:8080/nomatch", + "method": "GET", + "clientIp": "0:0:0:0:0:0:0:1", + "headers": { + "User-Agent": "curl/7.30.0", + "Accept": "*/*", + "Host": "localhost:8080" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1467402464520, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-07-01T19:47:44Z" + }, + "requestPattern": { + "url": "/almostmatch", + "method": "GET" + }, + "matchResult": { + "distance": 0.06944444444444445 + } + } + ] + } + } + } + } + } + } + }, + "/__admin/near-misses/request-pattern": { + "post": { + "summary": "Find near misses matching request pattern", + "description": "Find at most 3 near misses for closest logged requests to the specified request pattern", + "tags": [ + "Near Misses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "example": { + "method": "POST", + "url": "/resource", + "headers": { + "Content-Type": { + "matches": ".*/xml" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Near misses matching criteria", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nearMisses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "description": "The HTTP request method", + "type": "string", + "example": "GET" + }, + "url": { + "description": "The path and query to match exactly against", + "type": "string", + "example": "/received-request/2" + }, + "absoluteUrl": { + "description": "The full URL to match against", + "type": "string", + "example": "http://localhost:56738/received-request/2" + }, + "headers": { + "description": "Header patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + } + }, + "cookies": { + "description": "Cookie patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": {} + }, + "body": { + "description": "Body string to match against", + "type": "string", + "example": "Hello world" + } + } + } + } + } + }, + "example": { + "nearMisses": [ + { + "request": { + "url": "/nomatch", + "absoluteUrl": "http://localhost:8080/nomatch", + "method": "GET", + "clientIp": "0:0:0:0:0:0:0:1", + "headers": { + "User-Agent": "curl/7.30.0", + "Accept": "*/*", + "Host": "localhost:8080" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1467402464520, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-07-01T19:47:44Z" + }, + "requestPattern": { + "url": "/almostmatch", + "method": "GET" + }, + "matchResult": { + "distance": 0.06944444444444445 + } + } + ] + } + } + } + } + } + } + }, + "/__admin/recordings/start": { + "post": { + "summary": "Start recording", + "description": "Begin recording stub mappings", + "tags": [ + "Recordings" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "captureHeaders": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "caseInsensitive": { + "type": "boolean" + } + } + }, + "description": "Headers from the request to include in the generated stub mappings, mapped to parameter objects. The only parameter available is \"caseInsensitive\", which defaults to false", + "example": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + } + }, + "extractBodyCriteria": { + "type": "object", + "description": "Criteria for extracting response bodies to a separate file instead of including it in the stub mapping", + "example": [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ], + "properties": { + "binarySizeThreshold": { + "type": "string", + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB" + }, + "textSizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + } + } + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Whether to save stub mappings to the file system or just return them" + }, + "repeatsAsScenarios": { + "type": "boolean", + "default": true, + "description": "When true, duplicate requests will be added to a Scenario. When false, duplicates are discarded" + }, + "requestBodyPattern": { + "type": "object", + "description": "Control the request body matcher used in generated stub mappings", + "oneOf": [ + { + "type": "object", + "description": "Automatically determine matcher based on content type (the default)", + "properties": { + "caseInsensitive": { + "type": "boolean", + "default": false, + "description": "If equalTo is used, match body use case-insensitive string comparison" + }, + "ignoreArrayOrder": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, ignore order of array elements" + }, + "ignoreExtraElements": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, matcher ignores extra elements in objects" + }, + "matcher": { + "type": "string", + "enum": [ + "auto" + ] + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalTo", + "properties": { + "caseInsensitive": { + "default": false, + "description": "Match body using case-insensitive string comparison", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalTo" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToJson", + "properties": { + "ignoreArrayOrder": { + "default": true, + "description": "Ignore order of array elements", + "type": "boolean" + }, + "ignoreExtraElements": { + "default": true, + "description": "Ignore extra elements in objects", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalToJson" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToXml", + "properties": { + "matcher": { + "type": "string", + "enum": [ + "equalToXml" + ] + } + } + } + ] + }, + "transformerParameters": { + "type": "object", + "description": "List of names of stub mappings transformers to apply to generated stubs" + }, + "transformers": { + "type": "array", + "description": "Parameters to pass to stub mapping transformers", + "items": { + "type": "string" + } + } + } + }, + { + "properties": { + "filters": { + "allOf": [ + { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + { + "description": "Filter requests for which to create stub mapping" + } + ] + }, + "targetBaseUrl": { + "type": "string", + "description": "Target URL when using the record and playback API", + "example": "http://example.mocklab.io" + } + } + } + ] + }, + "example": { + "targetBaseUrl": "http://example.mocklab.io", + "filters": { + "urlPathPattern": "/api/.*", + "method": "GET" + }, + "captureHeaders": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + }, + "requestBodyPattern": { + "matcher": "equalToJson", + "ignoreArrayOrder": false, + "ignoreExtraElements": true + }, + "extractBodyCriteria": { + "textSizeThreshold": "2048", + "binarySizeThreshold": "10240" + }, + "persist": false, + "repeatsAsScenarios": false, + "transformers": [ + "modify-response-header" + ], + "transformerParameters": { + "headerValue": "123" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully started recording" + } + } + } + }, + "/__admin/recordings/stop": { + "post": { + "summary": "Stop recording", + "description": "End recording of stub mappings", + "tags": [ + "Recordings" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 4 + } + }, + "required": [ + "total" + ] + } + } + }, + "example": { + "mappings": [ + { + "id": "093f1027-e5e0-4921-9e6d-e619dfd5d2c7", + "name": "recordables_123", + "request": { + "url": "/recordables/123", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"message\": \"Congratulations on your first recording!\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "093f1027-e5e0-4921-9e6d-e619dfd5d2c7", + "persistent": true + } + ] + } + } + }, + "description": "Successfully stopped recording" + } + } + } + }, + "/__admin/recordings/status": { + "get": { + "summary": "Get recording status", + "tags": [ + "Recordings" + ], + "responses": { + "200": { + "description": "Successfully got the record status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "Stopped", + "enum": [ + "NeverStarted", + "Recording", + "Stopped" + ] + } + } + } + } + } + } + } + } + }, + "/__admin/recordings/snapshot": { + "post": { + "summary": "Take a snapshot recording", + "tags": [ + "Recordings" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "allOf": [ + { + "type": "object", + "properties": { + "captureHeaders": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "caseInsensitive": { + "type": "boolean" + } + } + }, + "description": "Headers from the request to include in the generated stub mappings, mapped to parameter objects. The only parameter available is \"caseInsensitive\", which defaults to false", + "example": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + } + }, + "extractBodyCriteria": { + "type": "object", + "description": "Criteria for extracting response bodies to a separate file instead of including it in the stub mapping", + "example": [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ], + "properties": { + "binarySizeThreshold": { + "type": "string", + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB" + }, + "textSizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + } + } + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Whether to save stub mappings to the file system or just return them" + }, + "repeatsAsScenarios": { + "type": "boolean", + "default": true, + "description": "When true, duplicate requests will be added to a Scenario. When false, duplicates are discarded" + }, + "requestBodyPattern": { + "type": "object", + "description": "Control the request body matcher used in generated stub mappings", + "oneOf": [ + { + "type": "object", + "description": "Automatically determine matcher based on content type (the default)", + "properties": { + "caseInsensitive": { + "type": "boolean", + "default": false, + "description": "If equalTo is used, match body use case-insensitive string comparison" + }, + "ignoreArrayOrder": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, ignore order of array elements" + }, + "ignoreExtraElements": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, matcher ignores extra elements in objects" + }, + "matcher": { + "type": "string", + "enum": [ + "auto" + ] + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalTo", + "properties": { + "caseInsensitive": { + "default": false, + "description": "Match body using case-insensitive string comparison", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalTo" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToJson", + "properties": { + "ignoreArrayOrder": { + "default": true, + "description": "Ignore order of array elements", + "type": "boolean" + }, + "ignoreExtraElements": { + "default": true, + "description": "Ignore extra elements in objects", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalToJson" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToXml", + "properties": { + "matcher": { + "type": "string", + "enum": [ + "equalToXml" + ] + } + } + } + ] + }, + "transformerParameters": { + "type": "object", + "description": "List of names of stub mappings transformers to apply to generated stubs" + }, + "transformers": { + "type": "array", + "description": "Parameters to pass to stub mapping transformers", + "items": { + "type": "string" + } + } + } + }, + { + "properties": { + "filters": { + "type": "object", + "allOf": [ + { + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object", + "description": "Filter requests for which to create stub mapping" + }, + { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + } + ] + } + } + } + ] + }, + "example": { + "filters": { + "urlPathPattern": "/api/.*", + "method": "GET", + "ids": [ + "40a93c4a-d378-4e07-8321-6158d5dbcb29" + ] + }, + "captureHeaders": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + }, + "requestBodyPattern": { + "matcher": "equalToJson", + "ignoreArrayOrder": false, + "ignoreExtraElements": true + }, + "extractBodyCriteria": { + "textSizeThreshold": "2 kb", + "binarySizeThreshold": "1 Mb" + }, + "outputFormat": "FULL", + "persist": false, + "repeatsAsScenarios": false, + "transformers": [ + "modify-response-header" + ], + "transformerParameters": { + "headerValue": "123" + } + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 4 + } + }, + "required": [ + "total" + ] + } + } + }, + "example": { + "mappings": [ + { + "id": "093f1027-e5e0-4921-9e6d-e619dfd5d2c7", + "name": "recordables_123", + "request": { + "url": "/recordables/123", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"message\": \"Congratulations on your first recording!\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "093f1027-e5e0-4921-9e6d-e619dfd5d2c7", + "persistent": true + } + ] + } + } + }, + "description": "Successfully took a snapshot recording" + } + } + } + }, + "/__admin/scenarios": { + "get": { + "summary": "Get all scenarios", + "tags": [ + "Scenarios" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "scenarios": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The scenario ID", + "example": "c8d249ec-d86d-48b1-88a8-a660e6848042" + }, + "name": { + "type": "string", + "description": "The scenario name", + "example": "my_scenario" + }, + "possibleStates": { + "type": "array", + "items": { + "default": "Started", + "description": "All the states this scenario can be in", + "type": "string" + }, + "example": [ + "Started", + "state_1", + "state_2" + ] + }, + "state": { + "type": "string", + "default": "Started", + "description": "The current state of this scenario", + "example": "state_2" + } + } + } + } + } + } + } + }, + "description": "All scenarios" + } + } + } + }, + "/__admin/scenarios/reset": { + "post": { + "summary": "Reset the state of all scenarios", + "tags": [ + "Scenarios" + ], + "responses": { + "200": { + "description": "Successfully reset" + } + } + } + }, + "/__admin/settings": { + "post": { + "summary": "Update global settings", + "tags": [ + "System" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "fixedDelay": { + "type": "number" + } + }, + "example": { + "fixedDelay": 500 + } + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Settings successfully updated" + } + } + } + }, + "/__admin/reset": { + "post": { + "summary": "Reset mappings and request journal", + "description": "Reset mappings to the default state and reset the request journal", + "tags": [ + "System" + ], + "responses": { + "200": { + "description": "Successfully reset" + } + } + } + }, + "/__admin/shutdown": { + "post": { + "description": "Shutdown the WireMock server", + "tags": [ + "System" + ], + "responses": { + "200": { + "description": "Server will be shut down" + } + } + } + } + }, + "components": { + "requestBodies": { + "requestPattern": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "example": { + "method": "POST", + "url": "/resource", + "headers": { + "Content-Type": { + "matches": ".*/xml" + } + } + } + } + } + }, + "stubMapping": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "request": { + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "startRecording": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "captureHeaders": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "caseInsensitive": { + "type": "boolean" + } + } + }, + "description": "Headers from the request to include in the generated stub mappings, mapped to parameter objects. The only parameter available is \"caseInsensitive\", which defaults to false", + "example": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + } + }, + "extractBodyCriteria": { + "type": "object", + "description": "Criteria for extracting response bodies to a separate file instead of including it in the stub mapping", + "example": [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ], + "properties": { + "binarySizeThreshold": { + "type": "string", + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB" + }, + "textSizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + } + } + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Whether to save stub mappings to the file system or just return them" + }, + "repeatsAsScenarios": { + "type": "boolean", + "default": true, + "description": "When true, duplicate requests will be added to a Scenario. When false, duplicates are discarded" + }, + "requestBodyPattern": { + "type": "object", + "description": "Control the request body matcher used in generated stub mappings", + "oneOf": [ + { + "type": "object", + "description": "Automatically determine matcher based on content type (the default)", + "properties": { + "caseInsensitive": { + "type": "boolean", + "default": false, + "description": "If equalTo is used, match body use case-insensitive string comparison" + }, + "ignoreArrayOrder": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, ignore order of array elements" + }, + "ignoreExtraElements": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, matcher ignores extra elements in objects" + }, + "matcher": { + "type": "string", + "enum": [ + "auto" + ] + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalTo", + "properties": { + "caseInsensitive": { + "default": false, + "description": "Match body using case-insensitive string comparison", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalTo" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToJson", + "properties": { + "ignoreArrayOrder": { + "default": true, + "description": "Ignore order of array elements", + "type": "boolean" + }, + "ignoreExtraElements": { + "default": true, + "description": "Ignore extra elements in objects", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalToJson" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToXml", + "properties": { + "matcher": { + "type": "string", + "enum": [ + "equalToXml" + ] + } + } + } + ] + }, + "transformerParameters": { + "type": "object", + "description": "List of names of stub mappings transformers to apply to generated stubs" + }, + "transformers": { + "type": "array", + "description": "Parameters to pass to stub mapping transformers", + "items": { + "type": "string" + } + } + } + }, + { + "properties": { + "filters": { + "allOf": [ + { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + { + "description": "Filter requests for which to create stub mapping" + } + ] + }, + "targetBaseUrl": { + "type": "string", + "description": "Target URL when using the record and playback API", + "example": "http://example.mocklab.io" + } + } + } + ] + }, + "example": { + "targetBaseUrl": "http://example.mocklab.io", + "filters": { + "urlPathPattern": "/api/.*", + "method": "GET" + }, + "captureHeaders": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + }, + "requestBodyPattern": { + "matcher": "equalToJson", + "ignoreArrayOrder": false, + "ignoreExtraElements": true + }, + "extractBodyCriteria": { + "textSizeThreshold": "2048", + "binarySizeThreshold": "10240" + }, + "persist": false, + "repeatsAsScenarios": false, + "transformers": [ + "modify-response-header" + ], + "transformerParameters": { + "headerValue": "123" + } + } + } + } + }, + "snapshotRecording": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "allOf": [ + { + "type": "object", + "properties": { + "captureHeaders": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "caseInsensitive": { + "type": "boolean" + } + } + }, + "description": "Headers from the request to include in the generated stub mappings, mapped to parameter objects. The only parameter available is \"caseInsensitive\", which defaults to false", + "example": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + } + }, + "extractBodyCriteria": { + "type": "object", + "description": "Criteria for extracting response bodies to a separate file instead of including it in the stub mapping", + "example": [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ], + "properties": { + "binarySizeThreshold": { + "type": "string", + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB" + }, + "textSizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + } + } + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Whether to save stub mappings to the file system or just return them" + }, + "repeatsAsScenarios": { + "type": "boolean", + "default": true, + "description": "When true, duplicate requests will be added to a Scenario. When false, duplicates are discarded" + }, + "requestBodyPattern": { + "type": "object", + "description": "Control the request body matcher used in generated stub mappings", + "oneOf": [ + { + "type": "object", + "description": "Automatically determine matcher based on content type (the default)", + "properties": { + "caseInsensitive": { + "type": "boolean", + "default": false, + "description": "If equalTo is used, match body use case-insensitive string comparison" + }, + "ignoreArrayOrder": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, ignore order of array elements" + }, + "ignoreExtraElements": { + "type": "boolean", + "default": true, + "description": "If equalToJson is used, matcher ignores extra elements in objects" + }, + "matcher": { + "type": "string", + "enum": [ + "auto" + ] + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalTo", + "properties": { + "caseInsensitive": { + "default": false, + "description": "Match body using case-insensitive string comparison", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalTo" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToJson", + "properties": { + "ignoreArrayOrder": { + "default": true, + "description": "Ignore order of array elements", + "type": "boolean" + }, + "ignoreExtraElements": { + "default": true, + "description": "Ignore extra elements in objects", + "type": "boolean" + }, + "matcher": { + "enum": [ + "equalToJson" + ], + "type": "string" + } + } + }, + { + "type": "object", + "description": "Always match request bodies using equalToXml", + "properties": { + "matcher": { + "type": "string", + "enum": [ + "equalToXml" + ] + } + } + } + ] + }, + "transformerParameters": { + "type": "object", + "description": "List of names of stub mappings transformers to apply to generated stubs" + }, + "transformers": { + "type": "array", + "description": "Parameters to pass to stub mapping transformers", + "items": { + "type": "string" + } + } + } + }, + { + "properties": { + "filters": { + "type": "object", + "allOf": [ + { + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object", + "description": "Filter requests for which to create stub mapping" + }, + { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + } + ] + } + } + } + ] + }, + "example": { + "filters": { + "urlPathPattern": "/api/.*", + "method": "GET", + "ids": [ + "40a93c4a-d378-4e07-8321-6158d5dbcb29" + ] + }, + "captureHeaders": { + "Accept": {}, + "Content-Type": { + "caseInsensitive": true + } + }, + "requestBodyPattern": { + "matcher": "equalToJson", + "ignoreArrayOrder": false, + "ignoreExtraElements": true + }, + "extractBodyCriteria": { + "textSizeThreshold": "2 kb", + "binarySizeThreshold": "1 Mb" + }, + "outputFormat": "FULL", + "persist": false, + "repeatsAsScenarios": false, + "transformers": [ + "modify-response-header" + ], + "transformerParameters": { + "headerValue": "123" + } + } + } + } + } + }, + "responses": { + "stubMapping": { + "description": "The stub mapping", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "This stub mapping's unique identifier" + }, + "uuid": { + "type": "string", + "description": "Alias for the id" + }, + "name": { + "type": "string", + "description": "The stub mapping's name" + }, + "request": { + "type": "object", + "example": { + "bodyPatterns": [ + { + "equalToJson": "{ \"numbers\": [1, 2, 3] }" + } + ], + "headers": { + "Content-Type": { + "equalTo": "application/json" + } + }, + "method": "POST", + "url": "/some/thing" + }, + "properties": { + "method": { + "type": "string", + "description": "The HTTP request method e.g. GET" + }, + "url": { + "type": "string", + "description": "The path and query to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPath": { + "type": "string", + "description": "The path to match exactly against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPathPattern": { + "type": "string", + "description": "The path regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "urlPattern": { + "type": "string", + "description": "The path and query regex to match against. Only one of url, urlPattern, urlPath or urlPathPattern may be specified." + }, + "queryParameters": { + "type": "object", + "description": "Query parameter patterns to match against in the : { \"\": \"\" } form" + }, + "headers": { + "type": "object", + "description": "Header patterns to match against in the : { \"\": \"\" } form" + }, + "basicAuthCredentials": { + "type": "object", + "description": "Pre-emptive basic auth credentials to match against", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "cookies": { + "type": "object", + "description": "Cookie patterns to match against in the : { \"\": \"\" } form" + }, + "bodyPatterns": { + "type": "array", + "description": "Request body patterns to match against in the : { \"\": \"\" } form", + "items": { + "type": "object" + } + } + } + }, + "response": { + "allOf": [ + { + "oneOf": [ + { + "title": "Log normal", + "description": "Log normal randomly distributed response delay.", + "type": "object", + "properties": { + "median": { + "type": "integer" + }, + "sigma": { + "type": "number" + }, + "type": { + "enum": [ + "lognormal" + ], + "type": "string" + } + } + }, + { + "title": "Uniform", + "description": "Uniformly distributed random response delay.", + "type": "object", + "properties": { + "lower": { + "type": "integer" + }, + "type": { + "enum": [ + "uniform" + ], + "type": "string" + }, + "upper": { + "type": "integer" + } + } + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The HTTP status code to be returned" + }, + "statusMessage": { + "type": "string", + "description": "The HTTP status message to be returned" + }, + "headers": { + "type": "object", + "description": "Map of response headers to send" + }, + "additionalProxyRequestHeaders": { + "type": "object", + "description": "Extra request headers to send when proxying to another host." + }, + "body": { + "type": "string", + "description": "The response body as a string. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "base64Body": { + "type": "string", + "description": "The response body as a base64 encoded string (useful for binary content). Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "jsonBody": { + "type": "object", + "description": "The response body as a JSON object. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "bodyFileName": { + "type": "string", + "description": "The path to the file containing the response body, relative to the configured file root. Only one of body, base64Body, jsonBody or bodyFileName may be specified." + }, + "fault": { + "type": "string", + "description": "The fault to apply (instead of a full, valid response).", + "enum": [ + "CONNECTION_RESET_BY_PEER", + "EMPTY_RESPONSE", + "MALFORMED_RESPONSE_CHUNK", + "RANDOM_DATA_THEN_CLOSE" + ] + }, + "fixedDelayMilliseconds": { + "type": "integer", + "description": "Number of milliseconds to delay be before sending the response." + }, + "fromConfiguredStub": { + "type": "boolean", + "description": "Read-only flag indicating false if this was the default, unmatched response. Not present otherwise." + }, + "proxyBaseUrl": { + "type": "string", + "description": "The base URL of the target to proxy matching requests to." + }, + "transformerParameters": { + "type": "object", + "description": "Parameters to apply to response transformers." + }, + "transformers": { + "type": "array", + "description": "List of names of transformers to apply to this response.", + "items": { + "type": "string" + } + } + } + } + ] + }, + "persistent": { + "type": "boolean", + "description": "Indicates that the stub mapping should be persisted immediately on create/update/delete and survive resets to default." + }, + "priority": { + "type": "integer", + "description": "This stub mapping's priority relative to others. 1 is highest.", + "minimum": 1 + }, + "scenarioName": { + "type": "string", + "description": "The name of the scenario that this stub mapping is part of" + }, + "requiredScenarioState": { + "type": "string", + "description": "The required state of the scenario in order for this stub to be matched." + }, + "newScenarioState": { + "type": "string", + "description": "The new state for the scenario to be updated to after this stub is served." + }, + "postServeActions": { + "type": "object", + "description": "A map of the names of post serve action extensions to trigger and their parameters." + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs." + } + } + }, + "example": { + "id": "76ada7b0-49ae-4229-91c4-396a36f18e09", + "priority": 3, + "request": { + "headers": { + "Accept": { + "equalTo": "text/plain" + } + }, + "method": "GET", + "url": "/some/thing" + }, + "response": { + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + }, + "status": 200 + } + } + } + } + }, + "nearMisses": { + "description": "Near misses matching criteria", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nearMisses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "description": "The HTTP request method", + "type": "string", + "example": "GET" + }, + "url": { + "description": "The path and query to match exactly against", + "type": "string", + "example": "/received-request/2" + }, + "absoluteUrl": { + "description": "The full URL to match against", + "type": "string", + "example": "http://localhost:56738/received-request/2" + }, + "headers": { + "description": "Header patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": { + "Connection": "keep-alive", + "Host": "localhost:56738", + "User-Agent": "Apache-HttpClient/4.5.1 (Java/1.7.0_51)" + } + }, + "cookies": { + "description": "Cookie patterns to match against in the : { \"\": \"\" } form", + "type": "object", + "example": {} + }, + "body": { + "description": "Body string to match against", + "type": "string", + "example": "Hello world" + } + } + } + } + } + }, + "example": { + "nearMisses": [ + { + "request": { + "url": "/nomatch", + "absoluteUrl": "http://localhost:8080/nomatch", + "method": "GET", + "clientIp": "0:0:0:0:0:0:0:1", + "headers": { + "User-Agent": "curl/7.30.0", + "Accept": "*/*", + "Host": "localhost:8080" + }, + "cookies": {}, + "browserProxyRequest": false, + "loggedDate": 1467402464520, + "bodyAsBase64": "", + "body": "", + "loggedDateString": "2016-07-01T19:47:44Z" + }, + "requestPattern": { + "url": "/almostmatch", + "method": "GET" + }, + "matchResult": { + "distance": 0.06944444444444445 + } + } + ] + } + } + } + } + } + } +} + diff --git a/src/WireMock.Org.RestClient/IWireMockOrgApi.cs b/src/WireMock.Org.RestClient/IWireMockOrgApi.cs new file mode 100644 index 00000000..d554ba2b --- /dev/null +++ b/src/WireMock.Org.RestClient/IWireMockOrgApi.cs @@ -0,0 +1,232 @@ +using System.IO; +using System.Threading.Tasks; +using RestEase; +using WireMock.Org.Abstractions; + +namespace WireMock.Org.RestClient +{ + /// + /// WireMockOrg + /// + public interface IWireMockOrgApi + { + /// + /// Get all stub mappings + /// + /// The maximum number of results to return + /// The start index of the results to return + [Get("/__admin/mappings")] + Task GetAdminMappingsAsync([Query] int? limit, [Query] int? offset); + + /// + /// Create a new stub mapping + /// + /// + [Post("/__admin/mappings")] + [Header("Content-Type", "application/json")] + Task PostAdminMappingsAsync([Body] Mapping request); + + /// + /// Delete all stub mappings + /// + [Delete("/__admin/mappings")] + Task DeleteAdminMappingsAsync(); + + /// + /// Reset stub mappings + /// + [Post("/__admin/mappings/reset")] + Task PostAdminMappingsResetAsync(); + + /// + /// Persist stub mappings + /// + [Post("/__admin/mappings/save")] + Task PostAdminMappingsSaveAsync(); + + /// + /// Get stub mapping by ID + /// + [Get("/__admin/mappings/{stubMappingId}")] + Task GetAdminMappingsByStubMappingIdAsync(); + + /// + /// Update a stub mapping + /// + /// + [Put("/__admin/mappings/{stubMappingId}")] + [Header("Content-Type", "application/json")] + Task PutAdminMappingsByStubMappingIdAsync([Body] Mapping request); + + /// + /// Delete a stub mapping + /// + [Delete("/__admin/mappings/{stubMappingId}")] + Task DeleteAdminMappingsByStubMappingIdAsync(); + + /// + /// PostAdminMappingsFindByMetadata (/__admin/mappings/find-by-metadata) + /// + [Post("/__admin/mappings/find-by-metadata")] + [Header("Content-Type", "application/json")] + Task PostAdminMappingsFindByMetadataAsync(); + + /// + /// Delete stub mappings matching metadata + /// + [Post("/__admin/mappings/remove-by-metadata")] + [Header("Content-Type", "application/json")] + Task PostAdminMappingsRemoveByMetadataAsync(); + + /// + /// Get all requests in journal + /// + /// The maximum number of results to return + /// Only return logged requests after this date + [Get("/__admin/requests")] + Task GetAdminRequestsAsync([Query] string limit, [Query] string since); + + /// + /// Delete all requests in journal + /// + [Delete("/__admin/requests")] + Task DeleteAdminRequestsAsync(); + + /// + /// Get request by ID + /// + /// The UUID of the logged request + [Get("/__admin/requests/{requestId}")] + Task GetAdminRequestsByRequestIdAsync([Path] string requestId); + + /// + /// Delete request by ID + /// + /// The UUID of the logged request + [Delete("/__admin/requests/{requestId}")] + Task DeleteAdminRequestsByRequestIdAsync([Path] string requestId); + + /// + /// Empty the request journal + /// + [Post("/__admin/requests/reset")] + Task PostAdminRequestsResetAsync(); + + /// + /// Count requests by criteria + /// + /// + [Post("/__admin/requests/count")] + [Header("Content-Type", "application/json")] + Task PostAdminRequestsCountAsync([Body] Request request); + + /// + /// Remove requests by criteria + /// + /// + [Post("/__admin/requests/remove")] + [Header("Content-Type", "application/json")] + Task PostAdminRequestsRemoveAsync([Body] Request request); + + /// + /// Delete requests mappings matching metadata + /// + [Post("/__admin/requests/remove-by-metadata")] + [Header("Content-Type", "application/json")] + Task PostAdminRequestsRemoveByMetadataAsync(); + + /// + /// Find requests by criteria + /// + /// + [Post("/__admin/requests/find")] + [Header("Content-Type", "application/json")] + Task PostAdminRequestsFindAsync([Body] Request request); + + /// + /// Find unmatched requests + /// + [Get("/__admin/requests/unmatched")] + Task GetAdminRequestsUnmatchedAsync(); + + /// + /// GetAdminRequestsUnmatchedNearMisses (/__admin/requests/unmatched/near-misses) + /// + [Get("/__admin/requests/unmatched/near-misses")] + Task GetAdminRequestsUnmatchedNearMissesAsync(); + + /// + /// Find near misses matching specific request + /// + /// + [Post("/__admin/near-misses/request")] + [Header("Content-Type", "application/json")] + Task PostAdminNearMissesRequestAsync([Body] NearMiss request); + + /// + /// Find near misses matching request pattern + /// + /// + [Post("/__admin/near-misses/request-pattern")] + [Header("Content-Type", "application/json")] + Task PostAdminNearMissesRequestPatternAsync([Body] Request request); + + /// + /// Start recording + /// + [Post("/__admin/recordings/start")] + [Header("Content-Type", "application/json")] + Task PostAdminRecordingsStartAsync(); + + /// + /// Stop recording + /// + [Post("/__admin/recordings/stop")] + Task PostAdminRecordingsStopAsync(); + + /// + /// Get recording status + /// + [Get("/__admin/recordings/status")] + Task GetAdminRecordingsStatusAsync(); + + /// + /// Take a snapshot recording + /// + /// + [Post("/__admin/recordings/snapshot")] + [Header("Content-Type", "application/json")] + Task PostAdminRecordingsSnapshotAsync([Body] object request); + + /// + /// Get all scenarios + /// + [Get("/__admin/scenarios")] + Task GetAdminScenariosAsync(); + + /// + /// Reset the state of all scenarios + /// + [Post("/__admin/scenarios/reset")] + Task PostAdminScenariosResetAsync(); + + /// + /// Update global settings + /// + [Post("/__admin/settings")] + [Header("Content-Type", "application/json")] + Task PostAdminSettingsAsync(); + + /// + /// Reset mappings and request journal + /// + [Post("/__admin/reset")] + Task PostAdminResetAsync(); + + /// + /// PostAdminShutdown (/__admin/shutdown) + /// + [Post("/__admin/shutdown")] + Task PostAdminShutdownAsync(); + } +} diff --git a/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj b/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj new file mode 100644 index 00000000..f0294ea3 --- /dev/null +++ b/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj @@ -0,0 +1,41 @@ + + + + A WireMock.org RestClient using RestEase to access the admin interface. + WireMock.Org.RestClient + Stef Heyenrath + net45;netstandard1.1;netstandard2.0;netstandard2.1 + true + WireMock.Org.RestClient + WireMock.Org.RestClient + wiremock;wiremock.org;rest;client;restclient;restease;rest;json + WireMock.Org.Client + {08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + ../WireMock.Net/WireMock.Net.ruleset + true + ../WireMock.Net/WireMock.Net.snk + + true + MIT + + + + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 2dcef826..031b6660 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -1,4 +1,4 @@ - + Stef Heyenrath @@ -51,7 +51,7 @@ - + @@ -72,6 +72,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/test/WireMock.Net.Tests/WireMockServer.ImportWireMockOrg.cs b/test/WireMock.Net.Tests/WireMockServer.ImportWireMockOrg.cs new file mode 100644 index 00000000..db56d541 --- /dev/null +++ b/test/WireMock.Net.Tests/WireMockServer.ImportWireMockOrg.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Linq; +using FluentAssertions; +using WireMock.Server; +using Xunit; + +namespace WireMock.Net.Tests +{ + public class WireMockServerImportWireMockOrgTests + { + // For for AppVeyor + OpenCover + private string GetCurrentFolder() + { + return Directory.GetCurrentDirectory(); + } + + [Fact] + public void WireMockServer_Admin_ReadStaticWireMockOrgMappingAndAddOrUpdate() + { + var server = WireMockServer.Start(); + string path = Path.Combine(GetCurrentFolder(), "__admin", "mappings.org", "mapping1.json"); + server.ReadStaticWireMockOrgMappingAndAddOrUpdate(path); + + var mappings = server.Mappings.ToArray(); + + mappings.Should().HaveCount(1); + + server.Stop(); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServer.Settings.cs b/test/WireMock.Net.Tests/WireMockServer.Settings.cs index 2a40d43c..f14c06f5 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Settings.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Settings.cs @@ -62,7 +62,7 @@ namespace WireMock.Net.Tests // Assert var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(24); + Check.That(mappings.Count()).IsEqualTo(25); Check.That(mappings.All(m => m.Priority == int.MinValue)).IsTrue(); } @@ -81,8 +81,8 @@ namespace WireMock.Net.Tests // Assert var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(25); - Check.That(mappings.Count(m => m.Priority == int.MinValue)).IsEqualTo(24); + Check.That(mappings.Count()).IsEqualTo(26); + Check.That(mappings.Count(m => m.Priority == int.MinValue)).IsEqualTo(25); Check.That(mappings.Count(m => m.Priority == 1000)).IsEqualTo(1); } diff --git a/test/WireMock.Net.Tests/__admin/mappings.org/mapping1.json b/test/WireMock.Net.Tests/__admin/mappings.org/mapping1.json new file mode 100644 index 00000000..32126c72 --- /dev/null +++ b/test/WireMock.Net.Tests/__admin/mappings.org/mapping1.json @@ -0,0 +1,38 @@ +{ + "id": "ef53ea56-f118-4b3a-8c69-a9484851d99a", + "name": "weatherforecast", + "request": { + "path": "/WeatherForecast/register-city", + "method": "POST", + "headers": { + "XXX": { + "contains": "xml" + } + }, + "queryParameters": { + "search_term": { + "equalTo": "WireMock" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "cityName": "São Paulo", + "cityCode": 5001 + }, + "ignoreArrayOrder": true, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "body": "[{\"date\":\"2021-09-09T20:44:48.0992639-03:00\",\"temperatureC\":51,\"temperatureF\":123,\"summary\":\"Hot\"},{\"date\":\"2021-09-10T20:44:48.0992692-03:00\",\"temperatureC\":34,\"temperatureF\":93,\"summary\":\"Mild\"},{\"date\":\"2021-09-11T20:44:48.0992696-03:00\",\"temperatureC\":43,\"temperatureF\":109,\"summary\":\"Sweltering\"},{\"date\":\"2021-09-12T20:44:48.0992698-03:00\",\"temperatureC\":46,\"temperatureF\":114,\"summary\":\"Cool\"},{\"date\":\"2021-09-13T20:44:48.0992701-03:00\",\"temperatureC\":3,\"temperatureF\":37,\"summary\":\"Freezing\"}]", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "ef53ea56-f118-4b3a-8c69-a9484851d99a", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file