From 94f179ba1712b719ad3a37d065d879ea6254435d Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 17 Aug 2019 16:24:14 +0000 Subject: [PATCH] Fix MappingMatcher in case of an exception in LinqMatcher. (#322) * Fix MappingMatcher in case of an exception in LinqMatcher. * update unit-tests --- src/WireMock.Net/Matchers/LinqMatcher.cs | 41 +++++++++++++---- src/WireMock.Net/Matchers/XPathMatcher.cs | 1 + src/WireMock.Net/Owin/IMappingMatcher.cs | 6 +-- src/WireMock.Net/Owin/MappingMatcher.cs | 46 +++++++++++-------- src/WireMock.Net/Owin/MappingMatcherResult.cs | 11 +++++ src/WireMock.Net/Owin/WireMockMiddleware.cs | 15 +++--- .../Owin/MappingMatcherTests.cs | 43 +++++++++++++---- .../Owin/WireMockMiddlewareTests.cs | 7 ++- 8 files changed, 118 insertions(+), 52 deletions(-) create mode 100644 src/WireMock.Net/Owin/MappingMatcherResult.cs diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index 9e5c2bc9..c7e90f6d 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -56,11 +56,23 @@ namespace WireMock.Matchers /// public double IsMatch(string input) { + double match = MatchScores.Mismatch; + // Convert a single input string to a Queryable string-list with 1 entry. IQueryable queryable = new[] { input }.AsQueryable(); - // Use the Any(...) method to check if the result matches - double match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern))); + try + { + // Use the Any(...) method to check if the result matches + match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern))); + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + catch + { + // just ignore exception + // TODO add logging? + } return MatchBehaviourHelper.Convert(MatchBehaviour, match); } @@ -68,6 +80,8 @@ namespace WireMock.Matchers /// public double IsMatch(object input) { + double match = MatchScores.Mismatch; + JObject value; switch (input) { @@ -83,16 +97,27 @@ namespace WireMock.Matchers // Convert a single object to a Queryable JObject-list with 1 entry. var queryable1 = new[] { value }.AsQueryable(); - // Generate the DynamicLinq select statement. - string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); + try + { + // Generate the DynamicLinq select statement. + string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); - // Execute DynamicLinq Select statement. - var queryable2 = queryable1.Select(dynamicSelect); + // Execute DynamicLinq Select statement. + var queryable2 = queryable1.Select(dynamicSelect); - // Use the Any(...) method to check if the result matches. - double match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern))); + // Use the Any(...) method to check if the result matches. + match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern))); + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + catch + { + // just ignore exception + // TODO add logging? + } return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } /// diff --git a/src/WireMock.Net/Matchers/XPathMatcher.cs b/src/WireMock.Net/Matchers/XPathMatcher.cs index ec09ffe1..3be50236 100644 --- a/src/WireMock.Net/Matchers/XPathMatcher.cs +++ b/src/WireMock.Net/Matchers/XPathMatcher.cs @@ -59,6 +59,7 @@ namespace WireMock.Matchers catch (Exception) { // just ignore exception + // TODO add logging? } } diff --git a/src/WireMock.Net/Owin/IMappingMatcher.cs b/src/WireMock.Net/Owin/IMappingMatcher.cs index e1b5363c..54960c00 100644 --- a/src/WireMock.Net/Owin/IMappingMatcher.cs +++ b/src/WireMock.Net/Owin/IMappingMatcher.cs @@ -1,9 +1,7 @@ -using WireMock.Matchers.Request; - -namespace WireMock.Owin +namespace WireMock.Owin { internal interface IMappingMatcher { - (IMapping Mapping, RequestMatchResult RequestMatchResult) Match(RequestMessage request); + MappingMatcherResult FindBestMatch(RequestMessage request); } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs index 3d275054..f34b5b4a 100644 --- a/src/WireMock.Net/Owin/MappingMatcher.cs +++ b/src/WireMock.Net/Owin/MappingMatcher.cs @@ -1,5 +1,6 @@ -using System.Linq; -using WireMock.Matchers.Request; +using System; +using System.Collections.Generic; +using System.Linq; using WireMock.Validation; namespace WireMock.Owin @@ -15,34 +16,41 @@ namespace WireMock.Owin _options = options; } - public (IMapping Mapping, RequestMatchResult RequestMatchResult) Match(RequestMessage request) + public MappingMatcherResult FindBestMatch(RequestMessage request) { - var mappings = _options.Mappings.Values - .Select(m => new + var mappings = new List(); + foreach (var mapping in _options.Mappings.Values) + { + try { - Mapping = m, - MatchResult = m.GetRequestMatchResult(request, m.Scenario != null && _options.Scenarios.ContainsKey(m.Scenario) ? _options.Scenarios[m.Scenario].NextState : null) - }) - .ToList(); + string scenario = mapping.Scenario != null && _options.Scenarios.ContainsKey(mapping.Scenario) ? _options.Scenarios[mapping.Scenario].NextState : null; + + mappings.Add(new MappingMatcherResult + { + Mapping = mapping, + RequestMatchResult = mapping.GetRequestMatchResult(request, scenario) + }); + } + catch (Exception ex) + { + _options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}"); + } + } if (_options.AllowPartialMapping) { var partialMappings = mappings - .Where(pm => (pm.Mapping.IsAdminInterface && pm.MatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) - .OrderBy(m => m.MatchResult) + .Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) + .OrderBy(m => m.RequestMatchResult) .ThenBy(m => m.Mapping.Priority) .ToList(); - var bestPartialMatch = partialMappings.FirstOrDefault(pm => pm.MatchResult.AverageTotalScore > 0.0); - - return (bestPartialMatch?.Mapping, bestPartialMatch?.MatchResult); + return partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0); } - var perfectMatch = mappings + return mappings .OrderBy(m => m.Mapping.Priority) - .FirstOrDefault(m => m.MatchResult.IsPerfectMatch); - - return (perfectMatch?.Mapping, perfectMatch?.MatchResult); + .FirstOrDefault(m => m.RequestMatchResult.IsPerfectMatch); } } -} +} \ No newline at end of file diff --git a/src/WireMock.Net/Owin/MappingMatcherResult.cs b/src/WireMock.Net/Owin/MappingMatcherResult.cs new file mode 100644 index 00000000..c479bd46 --- /dev/null +++ b/src/WireMock.Net/Owin/MappingMatcherResult.cs @@ -0,0 +1,11 @@ +using WireMock.Matchers.Request; + +namespace WireMock.Owin +{ + internal class MappingMatcherResult + { + public IMapping Mapping { get; set; } + + public RequestMatchResult RequestMatchResult { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 7b9ea4c5..2e9cd3d0 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using WireMock.Logging; -using WireMock.Matchers.Request; using System.Linq; using WireMock.Matchers; using WireMock.Util; @@ -74,7 +73,7 @@ namespace WireMock.Owin bool logRequest = false; ResponseMessage response = null; - (IMapping TargetMapping, RequestMatchResult RequestMatchResult) result = (null, null); + MappingMatcherResult result = null; try { foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null)) @@ -89,9 +88,9 @@ namespace WireMock.Owin } } - result = _mappingMatcher.Match(request); - var targetMapping = result.TargetMapping; + result = _mappingMatcher.FindBestMatch(request); + var targetMapping = result?.Mapping; if (targetMapping == null) { logRequest = true; @@ -129,7 +128,7 @@ namespace WireMock.Owin } catch (Exception ex) { - _options.Logger.Error($"Providing a Response for Mapping '{result.TargetMapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); + _options.Logger.Error($"Providing a Response for Mapping '{result?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); response = ResponseMessageBuilder.Create(JsonConvert.SerializeObject(ex), 500); } finally @@ -139,9 +138,9 @@ namespace WireMock.Owin Guid = Guid.NewGuid(), RequestMessage = request, ResponseMessage = response, - MappingGuid = result.TargetMapping?.Guid, - MappingTitle = result.TargetMapping?.Title, - RequestMatchResult = result.RequestMatchResult + MappingGuid = result?.Mapping?.Guid, + MappingTitle = result?.Mapping?.Title, + RequestMatchResult = result?.RequestMatchResult }; LogRequest(log, logRequest); diff --git a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs index d1b04ccf..a05a816b 100644 --- a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs +++ b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs @@ -14,7 +14,7 @@ namespace WireMock.Net.Tests.Owin public class MappingMatcherTests { private readonly Mock _optionsMock; - private readonly IMappingMatcher _sut; + private readonly MappingMatcher _sut; public MappingMatcherTests() { @@ -24,25 +24,50 @@ namespace WireMock.Net.Tests.Owin _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); + var loggerMock = new Mock(); + loggerMock.SetupAllProperties(); + loggerMock.Setup(l => l.Error(It.IsAny())); + _optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object); + _sut = new MappingMatcher(_optionsMock.Object); } [Fact] - public void MappingMatcher_Match_NoMappingsDefined() + public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull() { // Assign var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); // Act - var result = _sut.Match(request); + var result = _sut.FindBestMatch(request); // Assert and Verify - Check.That(result.Mapping).IsNull(); - Check.That(result.RequestMatchResult).IsNull(); + Check.That(result).IsNull(); } [Fact] - public void MappingMatcher_Match_GetBestMapping_Exact() + public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull() + { + // Assign + var mappingMock = new Mock(); + mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny(), It.IsAny())).Throws(); + + var mappings = new ConcurrentDictionary(); + mappings.TryAdd(Guid.NewGuid(), mappingMock.Object); + + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert and Verify + Check.That(result).IsNull(); + } + + [Fact] + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch() { // Assign var mappings = InitMappings(new[] { (Guid.Parse("00000000-0000-0000-0000-000000000001"), 0.1), (Guid.Parse("00000000-0000-0000-0000-000000000002"), 1.0) }); @@ -51,7 +76,7 @@ namespace WireMock.Net.Tests.Owin var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); // Act - var result = _sut.Match(request); + var result = _sut.FindBestMatch(request); // Assert and Verify Check.That(result.Mapping.Guid).IsEqualTo(Guid.Parse("00000000-0000-0000-0000-000000000002")); @@ -59,7 +84,7 @@ namespace WireMock.Net.Tests.Owin } [Fact] - public void MappingMatcher_Match_GetBestMapping_AllowPartialMapping() + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch() { // Assign _optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true); @@ -69,7 +94,7 @@ namespace WireMock.Net.Tests.Owin var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); // Act - var result = _sut.Match(request); + var result = _sut.FindBestMatch(request); // Assert and Verify Check.That(result.Mapping.Guid).IsEqualTo(Guid.Parse("00000000-0000-0000-0000-000000000002")); diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index 08470fce..dfdcae3e 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -11,7 +11,6 @@ using WireMock.Owin.Mappers; using WireMock.Util; using WireMock.Admin.Requests; using WireMock.Logging; -using WireMock.Matchers.Request; using WireMock.Matchers; using System.Collections.Generic; #if NET452 @@ -61,7 +60,7 @@ namespace WireMock.Net.Tests.Owin _matcherMock = new Mock(); _matcherMock.SetupAllProperties(); - _matcherMock.Setup(m => m.Match(It.IsAny())).Returns(((IMapping)null, (RequestMatchResult)null)); + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns(new MappingMatcherResult()); _contextMock = new Mock(); @@ -92,7 +91,7 @@ namespace WireMock.Net.Tests.Owin _optionsMock.SetupGet(o => o.AuthorizationMatcher).Returns(new ExactMatcher()); _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); - _matcherMock.Setup(m => m.Match(It.IsAny())).Returns((_mappingMock.Object, (RequestMatchResult)null)); + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns(new MappingMatcherResult { Mapping = _mappingMock.Object }); // Act await _sut.Invoke(_contextMock.Object); @@ -113,7 +112,7 @@ namespace WireMock.Net.Tests.Owin _optionsMock.SetupGet(o => o.AuthorizationMatcher).Returns(new ExactMatcher()); _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); - _matcherMock.Setup(m => m.Match(It.IsAny())).Returns((_mappingMock.Object, (RequestMatchResult)null)); + _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns(new MappingMatcherResult { Mapping = _mappingMock.Object }); // Act await _sut.Invoke(_contextMock.Object);