diff --git a/examples/WireMock.Net.ConsoleApplication/App.config b/examples/WireMock.Net.ConsoleApplication/App.config index 88fa4027..71a06ba3 100644 --- a/examples/WireMock.Net.ConsoleApplication/App.config +++ b/examples/WireMock.Net.ConsoleApplication/App.config @@ -1,6 +1,14 @@ - + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs index 42401af4..b4f808cc 100644 --- a/examples/WireMock.Net.ConsoleApplication/Program.cs +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Newtonsoft.Json; using WireMock.Matchers; using WireMock.RequestBuilders; @@ -20,62 +21,64 @@ namespace WireMock.Net.ConsoleApplication server.AllowPartialMapping(); - //server - // .Given(Request.Create().WithPath(p => p.Contains("x")).UsingGet()) - // .AtPriority(4) - // .RespondWith(Response.Create() - // .WithStatusCode(200) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""Contains x with FUNC 200""}")); - - //server - // .Given(Request.Create().WithPath("/data").UsingPost().WithBody(b => b.Contains("e"))) - // .RespondWith(Response.Create() - // .WithStatusCode(201) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""data posted with FUNC 201""}")); - - //server - // .Given(Request.Create().WithPath("/data", "/ax").UsingPost().WithHeader("Content-Type", "application/json*")) - // .RespondWith(Response.Create() - // .WithStatusCode(201) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""data posted with 201""}")); - - //server - // .Given(Request.Create().WithPath("/json").UsingPost().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]"))) - // .RespondWith(Response.Create() - // .WithStatusCode(201) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""json posted with 201""}")); - - //server - // .Given(Request.Create().WithPath("/json2").UsingPost().WithBody("x")) - // .RespondWith(Response.Create() - // .WithStatusCode(201) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""json posted with x - 201""}")); - - //server - // .Given(Request.Create().WithPath("/data").UsingDelete()) - // .RespondWith(Response.Create() - // .WithStatusCode(200) - // .WithHeader("Content-Type", "application/json") - // .WithBody(@"{ ""result"": ""data deleted with 200""}")); - - //server - // .Given(Request.Create().WithPath("/nobody").UsingGet()) - // .RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1)) - // .WithStatusCode(200)); + server + .Given(Request.Create().WithPath(p => p.Contains("x")).UsingGet()) + .AtPriority(4) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""Contains x with FUNC 200""}")); server - .Given(Request.Create().WithPath("/partial").UsingGet().WithHeader("p", "p")) + .Given(Request.Create().WithPath("/data").UsingPost().WithBody(b => b.Contains("e"))) + .AtPriority(999) + .RespondWith(Response.Create() + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""data posted with FUNC 201""}")); + + server + .Given(Request.Create().WithPath("/data", "/ax").UsingPost().WithHeader("Content-Type", "application/json*")) + .RespondWith(Response.Create() + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""data posted with 201""}")); + + server + .Given(Request.Create().WithPath("/json").UsingPost().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]"))) + .RespondWith(Response.Create() + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""json posted with 201""}")); + + server + .Given(Request.Create().WithPath("/json2").UsingPost().WithBody("x")) + .RespondWith(Response.Create() + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""json posted with x - 201""}")); + + server + .Given(Request.Create().WithPath("/data").UsingDelete()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""data deleted with 200""}")); + + server + .Given(Request.Create().WithPath("/nobody").UsingGet()) + .RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1)) + .WithStatusCode(200)); + + server + .Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher("cat"))) .RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200")); // http://localhost:8080/any/any?start=1000&stop=1&stop=2 server .Given(Request.Create().WithPath("/*").UsingGet()) .WithGuid(Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f05")) + .AtPriority(server.Mappings.Count() + 1) .RespondWith(Response.Create() .WithStatusCode(200) .WithHeader("Content-Type", "application/json") diff --git a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj index ca45c1da..03c13115 100644 --- a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj +++ b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj @@ -37,6 +37,10 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True + + ..\..\packages\SimMetrics.Net.1.0.1.0\lib\net45\SimMetrics.Net.dll + True + diff --git a/examples/WireMock.Net.ConsoleApplication/packages.config b/examples/WireMock.Net.ConsoleApplication/packages.config index 9d64bf36..c18085ec 100644 --- a/examples/WireMock.Net.ConsoleApplication/packages.config +++ b/examples/WireMock.Net.ConsoleApplication/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/WireMock.Net/Admin/Requests/LogEntryModel.cs b/src/WireMock.Net/Admin/Requests/LogEntryModel.cs index e1a58808..76e7a6ea 100644 --- a/src/WireMock.Net/Admin/Requests/LogEntryModel.cs +++ b/src/WireMock.Net/Admin/Requests/LogEntryModel.cs @@ -31,6 +31,14 @@ namespace WireMock.Admin.Requests /// public LogResponseModel Response { get; set; } + /// + /// Gets or sets the mapping unique identifier. + /// + /// + /// The mapping unique identifier. + /// + public Guid? MappingGuid { get; set; } + /// /// Gets or sets the request match result. /// diff --git a/src/WireMock.Net/Logging/LogEntry.cs b/src/WireMock.Net/Logging/LogEntry.cs index a2f0c8cd..35c9d01c 100644 --- a/src/WireMock.Net/Logging/LogEntry.cs +++ b/src/WireMock.Net/Logging/LogEntry.cs @@ -39,5 +39,13 @@ namespace WireMock.Logging /// The request match result. /// public RequestMatchResult RequestMatchResult { get; set; } + + /// + /// Gets or sets the mapping unique identifier. + /// + /// + /// The mapping unique identifier. + /// + public Guid? MappingGuid { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs index 74f09d3a..c4c98ee0 100644 --- a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs +++ b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs @@ -108,7 +108,7 @@ namespace WireMock.Matchers /// Name public string GetName() { - return $"SimMetricsMatcher ({_simMetricType})"; + return $"SimMetricsMatcher.{_simMetricType}"; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index a0e63fc3..28497ee3 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -4,6 +4,7 @@ using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SimMetrics.Net; using WireMock.Admin.Mappings; using WireMock.Admin.Requests; using WireMock.Logging; @@ -11,6 +12,7 @@ using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; +using WireMock.Util; namespace WireMock.Server { @@ -203,11 +205,12 @@ namespace WireMock.Server BodyOriginal = logEntry.ResponseMessage.BodyOriginal, Headers = logEntry.ResponseMessage.Headers }, - RequestMatchResult = new LogRequestMatchModel + MappingGuid = logEntry.MappingGuid, + RequestMatchResult = logEntry.RequestMatchResult != null ? new LogRequestMatchModel { MatchScore = logEntry.RequestMatchResult.MatchScore, Total = logEntry.RequestMatchResult.Total - } + } : null }; } @@ -222,26 +225,31 @@ namespace WireMock.Server private IRequestBuilder InitRequestBuilder(MappingModel mappingModel) { IRequestBuilder requestBuilder = Request.Create(); - string path = mappingModel.Request.Path as string; - if (path != null) - requestBuilder = requestBuilder.WithPath(path); - else + + if (mappingModel.Request.Path != null) { - JToken pathToken = (JToken)mappingModel.Request.Path; - PathModel pathModel = pathToken.ToObject(); - if (pathModel?.Matchers != null) - requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(Map).ToArray()); + string path = mappingModel.Request.Path as string; + if (path != null) + requestBuilder = requestBuilder.WithPath(path); + else + { + var pathModel = JsonUtils.ParseJTokenToObject(mappingModel.Request.Path); + if (pathModel?.Matchers != null) + requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(Map).ToArray()); + } } - string url = mappingModel.Request.Url as string; - if (url != null) - requestBuilder = requestBuilder.WithUrl(url); - else + if (mappingModel.Request.Url != null) { - JToken urlToken = (JToken)mappingModel.Request.Url; - UrlModel urlModel = urlToken.ToObject(); - if (urlModel?.Matchers != null) - requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(Map).ToArray()); + string url = mappingModel.Request.Url as string; + if (url != null) + requestBuilder = requestBuilder.WithUrl(url); + else + { + var urlModel = JsonUtils.ParseJTokenToObject(mappingModel.Request.Url); + if (urlModel?.Matchers != null) + requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(Map).ToArray()); + } } if (mappingModel.Request.Methods != null) @@ -412,7 +420,11 @@ namespace WireMock.Server if (matcher == null) return null; - switch (matcher.Name) + var parts = matcher.Name.Split('.'); + string matcherName = parts[0]; + string matcherType = parts.Length > 1 ? parts[1] : null; + + switch (matcherName) { case "RegexMatcher": return new RegexMatcher(matcher.Pattern); @@ -423,8 +435,18 @@ namespace WireMock.Server case "XPathMatcher": return new XPathMatcher(matcher.Pattern); - default: + case "WildcardMatcher": return new WildcardMatcher(matcher.Pattern, matcher.IgnoreCase == true); + + 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(matcher.Pattern, type); + + default: + throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); } } diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs index 8967fc30..56ea4bab 100644 --- a/src/WireMock.Net/Server/FluentMockServer.cs +++ b/src/WireMock.Net/Server/FluentMockServer.cs @@ -331,6 +331,7 @@ namespace WireMock.Server var request = _requestMapper.Map(ctx.Request); ResponseMessage response = null; + Mapping targetMapping = null; RequestMatchResult requestMatchResult = null; try { @@ -338,15 +339,19 @@ namespace WireMock.Server .Select(m => new { Mapping = m, MatchResult = m.IsRequestHandled(request) }) .ToList(); - Mapping targetMapping; if (_allowPartialMapping) { var orderedMappings = possibleMatchingMappings - .OrderBy(m => m.Mapping.Priority) - .ThenBy(m => m.MatchResult) + .Where(pm => + (pm.Mapping.Provider is DynamicResponseProvider && pm.MatchResult.IsPerfectMatch) || + !(pm.Mapping.Provider is DynamicResponseProvider) + ) + .OrderBy(m => m.MatchResult) + .ThenBy(m => m.Mapping.Priority) .ToList(); var bestPartialMatch = orderedMappings.FirstOrDefault(); + targetMapping = bestPartialMatch?.Mapping; requestMatchResult = bestPartialMatch?.MatchResult; } @@ -388,6 +393,7 @@ namespace WireMock.Server Guid = Guid.NewGuid(), RequestMessage = request, ResponseMessage = response, + MappingGuid = targetMapping?.Guid, RequestMatchResult = requestMatchResult }; diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs new file mode 100644 index 00000000..3d80c89f --- /dev/null +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json.Linq; + +namespace WireMock.Util +{ + internal static class JsonUtils + { + public static T ParseJTokenToObject(object value) + { + if (value == null) + return default(T); + + JToken token = value as JToken; + if (token == null) + return default(T); + + return token.ToObject(); + } + } +} \ No newline at end of file