From aff936e3b641fce09ad1f05841b769ed71492ac9 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 5 Jul 2020 10:51:49 +0200 Subject: [PATCH] Refactor: extract interfaces (#484) * . * MatchDetail * rm * resp * log * f --- .../WireMockService.cs | 10 +- .../IRequestMessage.cs | 140 ++ .../IResponseMessage.cs | 51 + .../Logging/ILogEntry.cs | 80 + .../Matchers/Request/IRequestMatchResult.cs | 48 + .../Matchers/Request/MatchDetail.cs | 0 .../ResponseBuilders/FaultType.cs | 0 .../Server/IWireMockServer.cs | 10 +- .../Util/IBodyData.cs | 61 + .../WireMockANumberOfCallsAssertions.cs | 4 +- .../Assertions/WireMockAssertions.cs | 4 +- .../Assertions/WireMockReceivedAssertions.cs | 4 +- .../Extensions/WireMockExtensions.cs | 2 +- .../WireMock.Net.FluentAssertions.csproj | 2 +- src/WireMock.Net/IMapping.cs | 2 +- src/WireMock.Net/Logging/LogEntry.cs | 73 +- .../Matchers/Request/IRequestMatcher.cs | 2 +- .../Matchers/Request/RequestMatchResult.cs | 34 +- .../Request/RequestMessageBodyMatcher.cs | 6 +- .../Request/RequestMessageClientIPMatcher.cs | 4 +- .../Request/RequestMessageCompositeMatcher.cs | 2 +- .../Request/RequestMessageCookieMatcher.cs | 4 +- .../Request/RequestMessageHeaderMatcher.cs | 4 +- .../Request/RequestMessageMethodMatcher.cs | 4 +- .../Request/RequestMessageParamMatcher.cs | 6 +- .../Request/RequestMessagePathMatcher.cs | 4 +- .../RequestMessageScenarioAndStateMatcher.cs | 2 +- .../Request/RequestMessageUrlMatcher.cs | 4 +- src/WireMock.Net/RequestMessage.cs | 104 +- src/WireMock.Net/ResponseMessage.cs | 32 +- .../Serialization/LogEntryMapper.cs | 4 +- .../Server/WireMockServer.Admin.cs | 1952 ++++++++--------- .../Server/WireMockServer.LogEntries.cs | 166 +- src/WireMock.Net/Util/BodyData.cs | 42 +- 34 files changed, 1554 insertions(+), 1313 deletions(-) create mode 100644 src/WireMock.Net.Abstractions/IRequestMessage.cs create mode 100644 src/WireMock.Net.Abstractions/IResponseMessage.cs create mode 100644 src/WireMock.Net.Abstractions/Logging/ILogEntry.cs create mode 100644 src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs rename src/{WireMock.Net => WireMock.Net.Abstractions}/Matchers/Request/MatchDetail.cs (100%) rename src/{WireMock.Net => WireMock.Net.Abstractions}/ResponseBuilders/FaultType.cs (100%) create mode 100644 src/WireMock.Net.Abstractions/Util/IBodyData.cs diff --git a/examples/WireMock.Net.WebApplication/WireMockService.cs b/examples/WireMock.Net.WebApplication/WireMockService.cs index 486f25b0..4aa28e14 100644 --- a/examples/WireMock.Net.WebApplication/WireMockService.cs +++ b/examples/WireMock.Net.WebApplication/WireMockService.cs @@ -1,8 +1,9 @@ -using System.Threading; +using System; +using System.Threading; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using WireMock.Admin.Requests; using WireMock.Logging; -using WireMock.Models.Requests; using WireMock.Server; using WireMock.Settings; @@ -49,6 +50,11 @@ namespace WireMock.Net.WebApplication string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented); _logger.LogDebug("Admin[{0}] {1}", isAdminrequest, message); } + + public void Error(string formatString, Exception exception) + { + _logger.LogError(formatString, exception.Message); + } } public WireMockService(ILogger logger, WireMockServerSettings settings) diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs new file mode 100644 index 00000000..99281257 --- /dev/null +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock +{ + /// + /// IRequestMessage + /// + public interface IRequestMessage + { + /// + /// Gets the Client IP Address. + /// + string ClientIP { get; } + + /// + /// Gets the url (relative). + /// + string Url { get; } + + /// + /// Gets the AbsoluteUrl. + /// + string AbsoluteUrl { get; } + + /// + /// The ProxyUrl (if a proxy is used). + /// + string ProxyUrl { get; } + + /// + /// Gets the DateTime. + /// + DateTime DateTime { get; } + + /// + /// Gets the path (relative). + /// + string Path { get; } + + /// + /// Gets the AbsolutePath. + /// + string AbsolutePath { get; } + + /// + /// Gets the path segments. + /// + string[] PathSegments { get; } + + /// + /// Gets the absolute path segments. + /// + string[] AbsolutePathSegments { get; } + + /// + /// Gets the method. + /// + string Method { get; } + + /// + /// Gets the headers. + /// + IDictionary> Headers { get; } + + /// + /// Gets the cookies. + /// + IDictionary Cookies { get; } + + /// + /// Gets the query. + /// + IDictionary> Query { get; } + + /// + /// Gets the raw query. + /// + string RawQuery { get; } + + /// + /// The body. + /// + IBodyData BodyData { get; } + + /// + /// The original body as string. Convenience getter for Handlebars. + /// + string Body { get; } + + /// + /// The body (as JSON object). Convenience getter for Handlebars. + /// + object BodyAsJson { get; } + + /// + /// The body (as bytearray). Convenience getter for Handlebars. + /// + byte[] BodyAsBytes { get; } + + /// + /// The detected body type. Convenience getter for Handlebars. + /// + string DetectedBodyType { get; } + + /// + /// The detected body type from the Content-Type header. Convenience getter for Handlebars. + /// + string DetectedBodyTypeFromContentType { get; } + + /// + /// The detected compression from the Content-Encoding header. Convenience getter for Handlebars. + /// + string DetectedCompression { get; } + + /// + /// Gets the Host + /// + string Host { get; } + + /// + /// Gets the protocol + /// + string Protocol { get; } + + /// + /// Gets the port + /// + int Port { get; } + + /// + /// Gets the origin + /// + string Origin { get; } + + // WireMockList GetParameter(string key, bool ignoreCase = false); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs new file mode 100644 index 00000000..ebfd4b0b --- /dev/null +++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using WireMock.ResponseBuilders; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock +{ + /// + /// IResponseMessage + /// + public interface IResponseMessage + { + /// + /// The Body. + /// + IBodyData BodyData { get; } + + /// + /// Gets the body destination (SameAsSource, String or Bytes). + /// + string BodyDestination { get; } + + /// + /// Gets or sets the body. + /// + string BodyOriginal { get; } + + /// + /// Gets the Fault percentage. + /// + double? FaultPercentage { get; } + + /// + /// The FaultType. + /// + FaultType FaultType { get; } + + /// + /// Gets the headers. + /// + IDictionary> Headers { get; } + + /// + /// Gets or sets the status code. + /// + object StatusCode { get; } + + //void AddHeader(string name, params string[] values); + //void AddHeader(string name, string value); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs b/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs new file mode 100644 index 00000000..b6d17704 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs @@ -0,0 +1,80 @@ +using System; +using WireMock.Matchers.Request; + +namespace WireMock.Logging +{ + public interface ILogEntry + { + /// + /// Gets the unique identifier. + /// + /// + /// The unique identifier. + /// + Guid Guid { get; } + + /// + /// Gets the mapping unique identifier. + /// + /// + /// The mapping unique identifier. + /// + Guid? MappingGuid { get; } + + /// + /// Gets the mapping unique title. + /// + /// + /// The mapping unique title. + /// + string MappingTitle { get; } + + /// + /// Gets the partial mapping unique identifier. + /// + /// + /// The mapping unique identifier. + /// + Guid? PartialMappingGuid { get; } + + /// + /// Gets the partial mapping unique title. + /// + /// + /// The mapping unique title. + /// + string PartialMappingTitle { get; } + + /// + /// Gets the partial match result. + /// + /// + /// The request match result. + /// + IRequestMatchResult PartialMatchResult { get; } + + /// + /// Gets the request match result. + /// + /// + /// The request match result. + /// + IRequestMatchResult RequestMatchResult { get; } + + /// + /// Gets the request message. + /// + /// + /// The request message. + /// + IRequestMessage RequestMessage { get; } + + /// + /// Gets the response message. + /// + /// + /// The response message. + /// + IResponseMessage ResponseMessage { get; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs new file mode 100644 index 00000000..f38e733f --- /dev/null +++ b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; + +namespace WireMock.Matchers.Request +{ + /// + /// IRequestMatchResult + /// + public interface IRequestMatchResult : IComparable + { + /// + /// Gets the match percentage. + /// + /// + /// The match percentage. + /// + double AverageTotalScore { get; } + + /// + /// Gets or sets a value indicating whether this instance is perfect match. + /// + /// + /// true if this instance is perfect match; otherwise, false. + /// + bool IsPerfectMatch { get; } + + /// + /// Gets the match details. + /// + IList MatchDetails { get; } + + /// + /// Gets or sets the total number of matches. + /// + /// + /// The total number of matches. + /// + int TotalNumber { get; } + + /// + /// Gets or sets the match-score. + /// + /// + /// The match-score. + /// + double TotalScore { get; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/MatchDetail.cs b/src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/MatchDetail.cs rename to src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs diff --git a/src/WireMock.Net/ResponseBuilders/FaultType.cs b/src/WireMock.Net.Abstractions/ResponseBuilders/FaultType.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/FaultType.cs rename to src/WireMock.Net.Abstractions/ResponseBuilders/FaultType.cs diff --git a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs index fe24f15e..ca822beb 100644 --- a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs +++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using JetBrains.Annotations; using WireMock.Admin.Mappings; +using WireMock.Logging; namespace WireMock.Server { @@ -16,13 +17,20 @@ namespace WireMock.Server /// bool IsStarted { get; } - //IEnumerable LogEntries { get; } + /// + /// Gets the request logs. + /// + IEnumerable LogEntries { get; } /// /// Gets the mappings as MappingModels. /// IEnumerable MappingModels { get; } + /// + /// Gets the mappings. + /// + //[PublicAPI] //IEnumerable Mappings { get; } /// diff --git a/src/WireMock.Net.Abstractions/Util/IBodyData.cs b/src/WireMock.Net.Abstractions/Util/IBodyData.cs new file mode 100644 index 00000000..e9223df4 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Util/IBodyData.cs @@ -0,0 +1,61 @@ +using System.Text; +using WireMock.Types; + +namespace WireMock.Util +{ + /// + /// IBodyData + /// + public interface IBodyData + { + /// + /// The body (as bytearray). + /// + byte[] BodyAsBytes { get; set; } + + /// + /// Gets or sets the body as a file. + /// + string BodyAsFile { get; set; } + + /// + /// Is the body as file cached? + /// + bool? BodyAsFileIsCached { get; set; } + + /// + /// The body (as JSON object). + /// + object BodyAsJson { get; set; } + + /// + /// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. + /// + bool? BodyAsJsonIndented { get; set; } + + /// + /// The body as string, this is defined when BodyAsString or BodyAsJson are not null. + /// + string BodyAsString { get; set; } + + /// + /// The detected body type (detection based on body content). + /// + BodyType DetectedBodyType { get; set; } + + /// + /// The detected body type (detection based on Content-Type). + /// + BodyType DetectedBodyTypeFromContentType { get; set; } + + /// + /// The detected compression. + /// + string DetectedCompression { get; set; } + + /// + /// The body encoding. + /// + Encoding Encoding { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs index aff156e4..f830be07 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs @@ -5,10 +5,10 @@ namespace WireMock.FluentAssertions { public class WireMockANumberOfCallsAssertions { - private readonly WireMockServer _server; + private readonly IWireMockServer _server; private readonly int _callsCount; - public WireMockANumberOfCallsAssertions(WireMockServer server, int callsCount) + public WireMockANumberOfCallsAssertions(IWireMockServer server, int callsCount) { _server = server; _callsCount = callsCount; diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs index 41f89b66..52f8b12d 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs @@ -8,9 +8,9 @@ namespace WireMock.FluentAssertions { public class WireMockAssertions { - private readonly WireMockServer _instance; + private readonly IWireMockServer _instance; - public WireMockAssertions(WireMockServer instance, int? callsCount) + public WireMockAssertions(IWireMockServer instance, int? callsCount) { _instance = instance; } diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs index 1728a895..0eba33b7 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs @@ -4,9 +4,9 @@ using WireMock.Server; // ReSharper disable once CheckNamespace namespace WireMock.FluentAssertions { - public class WireMockReceivedAssertions : ReferenceTypeAssertions + public class WireMockReceivedAssertions : ReferenceTypeAssertions { - public WireMockReceivedAssertions(WireMockServer server) + public WireMockReceivedAssertions(IWireMockServer server) { Subject = server; } diff --git a/src/WireMock.Net.FluentAssertions/Extensions/WireMockExtensions.cs b/src/WireMock.Net.FluentAssertions/Extensions/WireMockExtensions.cs index cd174e4e..e638f6cf 100644 --- a/src/WireMock.Net.FluentAssertions/Extensions/WireMockExtensions.cs +++ b/src/WireMock.Net.FluentAssertions/Extensions/WireMockExtensions.cs @@ -5,7 +5,7 @@ namespace WireMock.FluentAssertions { public static class WireMockExtensions { - public static WireMockReceivedAssertions Should(this WireMockServer instance) + public static WireMockReceivedAssertions Should(this IWireMockServer instance) { return new WireMockReceivedAssertions(instance); } diff --git a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj index 1a6d02d5..1c89aa71 100644 --- a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj +++ b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index 8da094c4..71d93743 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -99,7 +99,7 @@ namespace WireMock /// /// The request message. /// The Next State. - /// The . + /// The . RequestMatchResult GetRequestMatchResult(RequestMessage requestMessage, [CanBeNull] string nextState); } } \ No newline at end of file diff --git a/src/WireMock.Net/Logging/LogEntry.cs b/src/WireMock.Net/Logging/LogEntry.cs index 053a8d66..885705ba 100644 --- a/src/WireMock.Net/Logging/LogEntry.cs +++ b/src/WireMock.Net/Logging/LogEntry.cs @@ -6,78 +6,33 @@ namespace WireMock.Logging /// /// LogEntry /// - public class LogEntry + public class LogEntry : ILogEntry { - /// - /// Gets or sets the unique identifier. - /// - /// - /// The unique identifier. - /// + /// public Guid Guid { get; set; } - /// - /// Gets or sets the request message. - /// - /// - /// The request message. - /// - public RequestMessage RequestMessage { get; set; } + /// + public IRequestMessage RequestMessage { get; set; } - /// - /// Gets or sets the response message. - /// - /// - /// The response message. - /// - public ResponseMessage ResponseMessage { get; set; } + /// + public IResponseMessage ResponseMessage { get; set; } - /// - /// Gets or sets the request match result. - /// - /// - /// The request match result. - /// - public RequestMatchResult RequestMatchResult { get; set; } + /// + public IRequestMatchResult RequestMatchResult { get; set; } - /// - /// Gets or sets the mapping unique identifier. - /// - /// - /// The mapping unique identifier. - /// + /// public Guid? MappingGuid { get; set; } - /// - /// Gets or sets the mapping unique title. - /// - /// - /// The mapping unique title. - /// + /// public string MappingTitle { get; set; } - /// - /// Gets or sets the partial mapping unique identifier. - /// - /// - /// The mapping unique identifier. - /// + /// public Guid? PartialMappingGuid { get; set; } - /// - /// Gets or sets the partial mapping unique title. - /// - /// - /// The mapping unique title. - /// + /// public string PartialMappingTitle { get; set; } - /// - /// Gets or sets the partial match result. - /// - /// - /// The request match result. - /// - public RequestMatchResult PartialMatchResult { get; set; } + /// + public IRequestMatchResult PartialMatchResult { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/IRequestMatcher.cs b/src/WireMock.Net/Matchers/Request/IRequestMatcher.cs index b4c725fa..5c2cbad1 100644 --- a/src/WireMock.Net/Matchers/Request/IRequestMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/IRequestMatcher.cs @@ -15,6 +15,6 @@ namespace WireMock.Matchers.Request /// /// A value between 0.0 - 1.0 of the similarity. /// - double GetMatchingScore([NotNull] RequestMessage requestMessage, [NotNull] RequestMatchResult requestMatchResult); + double GetMatchingScore([NotNull] IRequestMessage requestMessage, [NotNull] RequestMatchResult requestMatchResult); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs b/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs index 45254b70..0b78625d 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs @@ -7,43 +7,21 @@ namespace WireMock.Matchers.Request /// /// RequestMatchResult /// - public class RequestMatchResult : IComparable + public class RequestMatchResult : IRequestMatchResult { - /// - /// Gets or sets the match-score. - /// - /// - /// The match-score. - /// + /// public double TotalScore => MatchDetails.Sum(md => md.Score); - /// - /// Gets or sets the total number of matches. - /// - /// - /// The total number of matches. - /// + /// public int TotalNumber => MatchDetails.Count; - /// - /// Gets or sets a value indicating whether this instance is perfect match. - /// - /// - /// true if this instance is perfect match; otherwise, false. - /// + /// public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance; - /// - /// Gets the match percentage. - /// - /// - /// The match percentage. - /// + /// public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber; - /// - /// Gets the match details. - /// + /// public IList MatchDetails { get; } = new List(); /// diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs index c2234996..93fe0adb 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -99,13 +99,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = CalculateMatchScore(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double CalculateMatchScore(RequestMessage requestMessage, IMatcher matcher) + private double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher) { // Check if the matcher is a IObjectMatcher if (matcher is IObjectMatcher objectMatcher) @@ -136,7 +136,7 @@ namespace WireMock.Matchers.Request return MatchScores.Mismatch; } - private double CalculateMatchScore(RequestMessage requestMessage) + private double CalculateMatchScore(IRequestMessage requestMessage) { if (Matchers != null && Matchers.Any()) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs index 4ea2e2eb..78f7d678 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs @@ -51,13 +51,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (Matchers != null) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs index 50e681e4..f2d456c3 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs @@ -34,7 +34,7 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { if (!RequestMatchers.Any()) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs index 50e4c93c..44b57745 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs @@ -91,13 +91,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (requestMessage.Cookies == null) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs index 5cdeeacf..2b920e48 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs @@ -92,13 +92,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (requestMessage.Headers == null) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs index 3ea3ff3d..d62587e3 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs @@ -31,13 +31,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { return MatchScores.ToScore(Methods.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)); } diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs index db486063..9b8d19f7 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs @@ -84,20 +84,20 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (Funcs != null) { return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query))); } - WireMockList valuesPresentInRequestMessage = requestMessage.GetParameter(Key, IgnoreCase ?? false); + WireMockList valuesPresentInRequestMessage = ((RequestMessage) requestMessage).GetParameter(Key, IgnoreCase ?? false); if (valuesPresentInRequestMessage == null) { // Key is not present at all, just return Mismatch diff --git a/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs index 0504e5f2..c1106614 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs @@ -53,13 +53,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (Matchers != null) { diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs index f4ffde5d..2741dcc0 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs @@ -32,7 +32,7 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(); return requestMatchResult.AddScore(GetType(), score); diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs index 47432cb2..f437fbd2 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs @@ -51,13 +51,13 @@ namespace WireMock.Matchers.Request } /// - public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) + public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult) { double score = IsMatch(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double IsMatch(IRequestMessage requestMessage) { if (Matchers != null) { diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 361db0ea..85edef65 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -15,131 +15,81 @@ namespace WireMock /// /// The RequestMessage. /// - public class RequestMessage + public class RequestMessage : IRequestMessage { - /// - /// Gets the Client IP Address. - /// + /// public string ClientIP { get; } - /// - /// Gets the url (relative). - /// + /// public string Url { get; } - /// - /// Gets the AbsoluteUrl. - /// + /// public string AbsoluteUrl { get; } - /// - /// The ProxyUrl (if a proxy is used). - /// + /// public string ProxyUrl { get; set; } - /// - /// Gets the DateTime. - /// + /// public DateTime DateTime { get; set; } - /// - /// Gets the path (relative). - /// + /// public string Path { get; } - /// - /// Gets the AbsolutePath. - /// + /// public string AbsolutePath { get; } - /// - /// Gets the path segments. - /// + /// public string[] PathSegments { get; } - /// - /// Gets the absolute path segments. - /// + /// public string[] AbsolutePathSegments { get; } - /// - /// Gets the method. - /// + /// public string Method { get; } - /// - /// Gets the headers. - /// + /// public IDictionary> Headers { get; } - /// - /// Gets the cookies. - /// + /// public IDictionary Cookies { get; } - /// - /// Gets the query. - /// + /// public IDictionary> Query { get; } - /// - /// Gets the raw query. - /// + /// public string RawQuery { get; } - /// - /// The body. - /// - public BodyData BodyData { get; } + /// + public IBodyData BodyData { get; } - /// - /// The original body as string. Convenience getter for Handlebars. - /// + /// public string Body { get; } - /// - /// The body (as JSON object). Convenience getter for Handlebars. - /// + /// public object BodyAsJson { get; } - /// - /// The body (as bytearray). Convenience getter for Handlebars. - /// + /// public byte[] BodyAsBytes { get; } - /// - /// The detected body type. Convenience getter for Handlebars. - /// + /// public string DetectedBodyType { get; } - /// - /// The detected body type from the Content-Type header. Convenience getter for Handlebars. - /// + /// public string DetectedBodyTypeFromContentType { get; } - /// - /// The detected compression from the Content-Encoding header. Convenience getter for Handlebars. - /// + /// public string DetectedCompression { get; } - /// - /// Gets the Host - /// + /// public string Host { get; } - /// - /// Gets the protocol - /// + /// public string Protocol { get; } - /// - /// Gets the port - /// + /// public int Port { get; } - /// - /// Gets the origin - /// + /// public string Origin { get; } /// diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net/ResponseMessage.cs index 4a6c81a4..0c35a25d 100644 --- a/src/WireMock.Net/ResponseMessage.cs +++ b/src/WireMock.Net/ResponseMessage.cs @@ -12,41 +12,27 @@ namespace WireMock /// /// The ResponseMessage. /// - public class ResponseMessage + public class ResponseMessage : IResponseMessage { - /// - /// Gets the headers. - /// + /// public IDictionary> Headers { get; set; } = new Dictionary>(); - /// - /// Gets or sets the status code. - /// + /// public object StatusCode { get; set; } - /// - /// Gets or sets the body. - /// + /// public string BodyOriginal { get; set; } - /// - /// Gets or sets the body destination (SameAsSource, String or Bytes). - /// + /// public string BodyDestination { get; set; } - /// - /// The Body. - /// - public BodyData BodyData { get; set; } + /// + public IBodyData BodyData { get; set; } - /// - /// The FaultType. - /// + /// public FaultType FaultType { get; set; } - /// - /// Gets or sets the Fault percentage. - /// + /// public double? FaultPercentage { get; set; } /// diff --git a/src/WireMock.Net/Serialization/LogEntryMapper.cs b/src/WireMock.Net/Serialization/LogEntryMapper.cs index b20b1471..5266537a 100644 --- a/src/WireMock.Net/Serialization/LogEntryMapper.cs +++ b/src/WireMock.Net/Serialization/LogEntryMapper.cs @@ -10,7 +10,7 @@ namespace WireMock.Serialization { internal static class LogEntryMapper { - public static LogEntryModel Map(LogEntry logEntry) + public static LogEntryModel Map(ILogEntry logEntry) { var logRequestModel = new LogRequestModel { @@ -124,7 +124,7 @@ namespace WireMock.Serialization }; } - private static LogRequestMatchModel Map(RequestMatchResult matchResult) + private static LogRequestMatchModel Map(IRequestMatchResult matchResult) { if (matchResult == null) { diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index c569a52b..8665401d 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -1,984 +1,984 @@ -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using WireMock.Admin.Mappings; -using WireMock.Admin.Scenarios; -using WireMock.Admin.Settings; -using WireMock.Http; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; -using WireMock.ResponseProviders; -using WireMock.Serialization; -using WireMock.Settings; -using WireMock.Types; -using WireMock.Util; -using WireMock.Validation; - -namespace WireMock.Server -{ - /// - /// The fluent mock server. - /// - public partial class WireMockServer - { - private const int EnhancedFileSystemWatcherTimeoutMs = 1000; - private const int AdminPriority = int.MinValue; - private const int ProxyPriority = 1000; - private const string ContentTypeJson = "application/json"; - private const string AdminFiles = "/__admin/files"; - private const string AdminMappings = "/__admin/mappings"; - private const string AdminRequests = "/__admin/requests"; - private const string AdminSettings = "/__admin/settings"; - private const string AdminScenarios = "/__admin/scenarios"; - private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; - - private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true); - private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); - private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); - - private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore - }; - - private readonly JsonSerializerSettings _settingsIncludeNullValues = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Include - }; - - #region InitAdmin - private void InitAdmin() - { - // __admin/settings - Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); - Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); - - // __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(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); - - // __admin/mappings/reset - Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); - - // __admin/mappings/{guid} - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); - - // __admin/mappings/save - Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); - - // __admin/requests - Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); - Given(Request.Create().WithPath(AdminRequests).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/requests/reset - Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/request/{guid} - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); - - // __admin/requests/find - Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); - - // __admin/scenarios - Given(Request.Create().WithPath(AdminScenarios).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); - Given(Request.Create().WithPath(AdminScenarios).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); - - // __admin/scenarios/reset - Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); - - // __admin/files/{filename} - Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); - Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPut()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); - Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); - Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); - Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); - } - #endregion +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using WireMock.Admin.Mappings; +using WireMock.Admin.Scenarios; +using WireMock.Admin.Settings; +using WireMock.Http; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.ResponseProviders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; +using WireMock.Validation; - #region StaticMappings - /// - [PublicAPI] - public void SaveStaticMappings([CanBeNull] string folder = null) - { - foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface)) - { - SaveMappingToFile(mapping, folder); - } +namespace WireMock.Server +{ + /// + /// The fluent mock server. + /// + public partial class WireMockServer + { + private const int EnhancedFileSystemWatcherTimeoutMs = 1000; + private const int AdminPriority = int.MinValue; + private const int ProxyPriority = 1000; + private const string ContentTypeJson = "application/json"; + private const string AdminFiles = "/__admin/files"; + private const string AdminMappings = "/__admin/mappings"; + private const string AdminRequests = "/__admin/requests"; + private const string AdminSettings = "/__admin/settings"; + private const string AdminScenarios = "/__admin/scenarios"; + private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; + + private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true); + private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); + private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); + + private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }; + + private readonly JsonSerializerSettings _settingsIncludeNullValues = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Include + }; + + #region InitAdmin + private void InitAdmin() + { + // __admin/settings + Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); + Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); + + // __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(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); + + // __admin/mappings/reset + Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); + + // __admin/mappings/{guid} + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); + + // __admin/mappings/save + Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); + + // __admin/requests + Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); + Given(Request.Create().WithPath(AdminRequests).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/requests/reset + Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/request/{guid} + Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); + Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); + + // __admin/requests/find + Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); + + // __admin/scenarios + Given(Request.Create().WithPath(AdminScenarios).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); + Given(Request.Create().WithPath(AdminScenarios).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + + // __admin/scenarios/reset + Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + + // __admin/files/{filename} + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPut()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); + } + #endregion + + #region StaticMappings + /// + [PublicAPI] + public void SaveStaticMappings([CanBeNull] string folder = null) + { + foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface)) + { + SaveMappingToFile(mapping, folder); + } } - /// - [PublicAPI] - public void ReadStaticMappings([CanBeNull] string folder = null) - { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } - - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); - return; - } - - foreach (string filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) - { - _settings.Logger.Info("Reading Static MappingFile : '{0}'", filename); - - try - { - ReadStaticMappingAndAddOrUpdate(filename); - } - catch - { - _settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename); - } - } + /// + [PublicAPI] + public void ReadStaticMappings([CanBeNull] string folder = null) + { + if (folder == null) + { + folder = _settings.FileSystemHandler.GetMappingFolder(); + } + + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); + return; + } + + foreach (string filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) + { + _settings.Logger.Info("Reading Static MappingFile : '{0}'", filename); + + try + { + ReadStaticMappingAndAddOrUpdate(filename); + } + catch + { + _settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename); + } + } } - /// - [PublicAPI] - public void WatchStaticMappings([CanBeNull] string folder = null) - { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } - - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - return; - } - - bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; - string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; - - _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); - - var watcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) - { - IncludeSubdirectories = includeSubdirectories - }; - - watcher.Created += (sender, args) => - { - _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } - }; - watcher.Changed += (sender, args) => - { - _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } - }; - watcher.Deleted += (sender, args) => - { - _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); - - if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) - { - DeleteMapping(guidFromFilename); - } - else - { - DeleteMapping(args.FullPath); - } - }; - - watcher.EnableRaisingEvents = true; + /// + [PublicAPI] + public void WatchStaticMappings([CanBeNull] string folder = null) + { + if (folder == null) + { + folder = _settings.FileSystemHandler.GetMappingFolder(); + } + + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + return; + } + + bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; + string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; + + _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); + + var watcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) + { + IncludeSubdirectories = includeSubdirectories + }; + + watcher.Created += (sender, args) => + { + _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) + { + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); + } + }; + watcher.Changed += (sender, args) => + { + _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) + { + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); + } + }; + watcher.Deleted += (sender, args) => + { + _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); + + if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) + { + DeleteMapping(guidFromFilename); + } + else + { + DeleteMapping(args.FullPath); + } + }; + + watcher.EnableRaisingEvents = true; } - /// - [PublicAPI] - public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path) - { - Check.NotNull(path, nameof(path)); - - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - - if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) - { - var mappingModels = DeserializeJsonToArray(value); - foreach (var mappingModel in mappingModels) - { - if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path); - } - else - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path); - } - } - - return true; - } - - return false; - } - #endregion - - #region Proxy and Record - private HttpClient _httpClientForProxy; - - private void InitProxyAndRecord(IWireMockServerSettings settings) - { - _httpClientForProxy = HttpClientHelper.CreateHttpClient(settings.ProxyAndRecordSettings); - - var respondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()); - if (settings.StartAdminInterface == true) - { - respondProvider.AtPriority(ProxyPriority); - } - - respondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); - } - - private async Task ProxyAndRecordAsync(RequestMessage requestMessage, IWireMockServerSettings settings) - { - var requestUri = new Uri(requestMessage.Url); - var proxyUri = new Uri(settings.ProxyAndRecordSettings.Url); - var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); - - var responseMessage = await HttpClientHelper.SendAsync( - _httpClientForProxy, - requestMessage, - proxyUriWithRequestPathAndQuery.AbsoluteUri, - !settings.DisableJsonBodyParsing.GetValueOrDefault(false), - !settings.DisableRequestBodyDecompressing.GetValueOrDefault(false) - ); - - if (HttpStatusRangeParser.IsMatch(settings.ProxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && - (settings.ProxyAndRecordSettings.SaveMapping || settings.ProxyAndRecordSettings.SaveMappingToFile)) - { - var mapping = ToMapping(requestMessage, responseMessage, settings.ProxyAndRecordSettings.BlackListedHeaders ?? new string[] { }, settings.ProxyAndRecordSettings.BlackListedCookies ?? new string[] { }); - - if (settings.ProxyAndRecordSettings.SaveMapping) - { - _options.Mappings.TryAdd(mapping.Guid, mapping); - } - - if (settings.ProxyAndRecordSettings.SaveMappingToFile) - { - SaveMappingToFile(mapping); - } - } - - return responseMessage; - } - - private IMapping ToMapping(RequestMessage requestMessage, ResponseMessage responseMessage, string[] blacklistedHeaders, string[] blacklistedCookies) - { - var request = Request.Create(); - request.WithPath(requestMessage.Path); - request.UsingMethod(requestMessage.Method); - - requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray())); - requestMessage.Cookies.Loop((key, value) => - { - if (!blacklistedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithCookie(key, value); - } - }); - - var allBlackListedHeaders = new List(blacklistedHeaders) { "Cookie" }; - requestMessage.Headers.Loop((key, value) => - { - if (!allBlackListedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithHeader(key, value.ToArray()); - } - }); - - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.Json: - request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson)); - break; - - case BodyType.String: - request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsString)); - break; - - case BodyType.Bytes: - request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes)); - break; - } - - var response = Response.Create(responseMessage); - - return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null); - } - #endregion - - #region Settings - private ResponseMessage SettingsGet(RequestMessage requestMessage) - { - var model = new SettingsModel - { - AllowPartialMapping = _options.AllowPartialMapping, - MaxRequestLogCount = _options.MaxRequestLogCount, - RequestLogExpirationDuration = _options.RequestLogExpirationDuration, - GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, - AllowBodyForAllHttpMethods = _options.AllowBodyForAllHttpMethods - }; - - return ToJson(model); - } - - private ResponseMessage SettingsUpdate(RequestMessage requestMessage) - { - var settings = DeserializeObject(requestMessage); - _options.MaxRequestLogCount = settings.MaxRequestLogCount; - _options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; - - if (settings.AllowPartialMapping != null) - { - _options.AllowPartialMapping = settings.AllowPartialMapping.Value; - } - - if (settings.GlobalProcessingDelay != null) - { - _options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); - } - - if (settings.AllowBodyForAllHttpMethods != null) - { - _options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods.Value; - } - - return ResponseMessageBuilder.Create("Settings updated"); - } - #endregion Settings - - #region Mapping/{guid} - private ResponseMessage MappingGet(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid); - - if (mapping == null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create("Mapping not found", 404); - } - - var model = _mappingConverter.ToMappingModel(mapping); - - return ToJson(model); - } - - private ResponseMessage MappingPut(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - - var mappingModel = DeserializeObject(requestMessage); - Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); - - return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); - } - - private ResponseMessage MappingDelete(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - - if (DeleteMapping(guid)) - { - return ResponseMessageBuilder.Create("Mapping removed", 200, guid); - } - - return ResponseMessageBuilder.Create("Mapping not found", 404); - } - - private Guid ParseGuidFromRequestMessage(RequestMessage requestMessage) - { - return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); - } - #endregion Mapping/{guid} - - #region Mappings - private ResponseMessage MappingsSave(RequestMessage requestMessage) - { - SaveStaticMappings(); - - return ResponseMessageBuilder.Create("Mappings saved to disk"); - } - - private void SaveMappingToFile(IMapping mapping, string folder = null) - { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } - - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - _settings.FileSystemHandler.CreateFolder(folder); - } - - var model = _mappingConverter.ToMappingModel(mapping); - string filename = (!string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString()) + ".json"; - - string path = Path.Combine(folder, filename); - - _settings.Logger.Info("Saving Mapping file {0}", filename); - - _settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, _jsonSerializerSettings)); - } - - private static string SanitizeFileName(string name, char replaceChar = '_') - { - return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar)); - } - - private IEnumerable ToMappingModels() - { - return Mappings.Where(m => !m.IsAdminInterface).Select(_mappingConverter.ToMappingModel); - } - - private ResponseMessage MappingsGet(RequestMessage requestMessage) - { - return ToJson(ToMappingModels()); - } - - private ResponseMessage MappingsPost(RequestMessage requestMessage) - { - try - { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - if (mappingModels.Length == 1) - { - Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); - return ResponseMessageBuilder.Create("Mapping added", 201, guid); - } - - foreach (var mappingModel in mappingModels) - { - ConvertMappingAndRegisterAsRespondProvider(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? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null) - { - Check.NotNull(mappingModel, nameof(mappingModel)); - Check.NotNull(mappingModel.Request, nameof(mappingModel.Request)); - Check.NotNull(mappingModel.Response, nameof(mappingModel.Response)); - - var requestBuilder = InitRequestBuilder(mappingModel.Request, true); - if (requestBuilder == null) - { - return null; - } - - var responseBuilder = InitResponseBuilder(mappingModel.Response); - - var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); - - if (guid != null) - { - respondProvider = respondProvider.WithGuid(guid.Value); - } - else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) - { - respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); - } - - if (path != null) - { - respondProvider = respondProvider.WithPath(path); - } - - if (!string.IsNullOrEmpty(mappingModel.Title)) - { - respondProvider = respondProvider.WithTitle(mappingModel.Title); - } - - if (mappingModel.Priority != null) - { - respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); - } - - if (mappingModel.Scenario != null) - { - respondProvider = respondProvider.InScenario(mappingModel.Scenario); - respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs); - respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo); - } - - respondProvider.RespondWith(responseBuilder); - - return respondProvider.Guid; - } - - private ResponseMessage MappingsDelete(RequestMessage requestMessage) - { - if (!string.IsNullOrEmpty(requestMessage.Body)) - { - var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); - if (deletedGuids != null) - { - return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); - } - else - { - // return bad request - return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); - } - } - else - { - ResetMappings(); - - ResetScenarios(); - - return ResponseMessageBuilder.Create("Mappings deleted"); - } - } - - private IEnumerable MappingsDeleteMappingFromBody(RequestMessage requestMessage) - { - var deletedGuids = new List(); - - try - { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - foreach (var mappingModel in mappingModels) - { - if (mappingModel.Guid.HasValue) - { - if (DeleteMapping(mappingModel.Guid.Value)) - { - deletedGuids.Add(mappingModel.Guid.Value); - } - else - { - _settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}."); - } - } - } - } - catch (ArgumentException a) - { - _settings.Logger.Error("ArgumentException: {0}", a); - return null; - } - catch (Exception e) - { - _settings.Logger.Error("Exception: {0}", e); - return null; - } - - return deletedGuids; - } - - private ResponseMessage MappingsReset(RequestMessage requestMessage) - { - ResetMappings(); - - ResetScenarios(); - - string message = "Mappings reset"; - if (requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && - bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) - && reloadStaticMappings) - { - ReadStaticMappings(); - message = $"{message} and static mappings reloaded"; - } - - return ResponseMessageBuilder.Create(message); - } - #endregion Mappings - - #region Request/{guid} - private ResponseMessage RequestGet(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); - - if (entry == null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); - return ResponseMessageBuilder.Create("Request not found", 404); - } - - var model = LogEntryMapper.Map(entry); - - return ToJson(model); - } - - private ResponseMessage RequestDelete(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - - if (DeleteLogEntry(guid)) - { - return ResponseMessageBuilder.Create("Request removed"); - } - - return ResponseMessageBuilder.Create("Request not found", 404); - } - #endregion Request/{guid} - - #region Requests - private ResponseMessage RequestsGet(RequestMessage requestMessage) - { - var result = LogEntries - .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) - .Select(LogEntryMapper.Map); - - return ToJson(result); - } - - private ResponseMessage RequestsDelete(RequestMessage requestMessage) - { - ResetLogEntries(); - - return ResponseMessageBuilder.Create("Requests deleted"); - } - #endregion Requests - - #region Requests/find - private ResponseMessage RequestsFind(RequestMessage requestMessage) - { - var requestModel = DeserializeObject(requestMessage); - - var request = (Request)InitRequestBuilder(requestModel, false); - - var dict = new Dictionary(); - foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) - { - var requestMatchResult = new RequestMatchResult(); - if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) - { - dict.Add(logEntry, requestMatchResult); - } - } - - var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(LogEntryMapper.Map); - - return ToJson(result); - } - #endregion Requests/find - - #region Scenarios - private ResponseMessage ScenariosGet(RequestMessage requestMessage) - { - var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel - { - Name = s.Name, - NextState = s.NextState, - Started = s.Started, - Finished = s.Finished - }); - - return ToJson(scenariosStates, true); - } - - private ResponseMessage ScenariosReset(RequestMessage requestMessage) - { - ResetScenarios(); - - return ResponseMessageBuilder.Create("Scenarios reset"); - } - #endregion - - private IRequestBuilder InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) - { - IRequestBuilder requestBuilder = Request.Create(); - - if (requestModel.ClientIP != null) - { - if (requestModel.ClientIP is string clientIP) - { - requestBuilder = requestBuilder.WithClientIP(clientIP); - } - else - { - var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); - if (clientIPModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - } - - bool pathOrUrlmatchersValid = false; - if (requestModel.Path != null) - { - if (requestModel.Path is string path) - { - requestBuilder = requestBuilder.WithPath(path); - pathOrUrlmatchersValid = true; - } - else - { - var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); - if (pathModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlmatchersValid = true; - } - } - } - else if (requestModel.Url != null) - { - if (requestModel.Url is string url) - { - requestBuilder = requestBuilder.WithUrl(url); - pathOrUrlmatchersValid = true; - } - else - { - var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); - if (urlModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlmatchersValid = true; - } - } - } - - if (pathOrUrlRequired && !pathOrUrlmatchersValid) - { - _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); - return null; - } - - if (requestModel.Methods != null) - { - requestBuilder = requestBuilder.UsingMethod(requestModel.Methods); - } - - if (requestModel.Headers != null) - { - foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) - { - requestBuilder = requestBuilder.WithHeader( - headerModel.Name, - headerModel.IgnoreCase == true, - headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - headerModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray() - ); - } - } - - if (requestModel.Cookies != null) - { - foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) - { - requestBuilder = requestBuilder.WithCookie( - cookieModel.Name, - cookieModel.IgnoreCase == true, - cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - cookieModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - - if (requestModel.Params != null) - { - foreach (var paramModel in requestModel.Params.Where(c => c.Matchers != null)) - { - bool ignoreCase = paramModel?.IgnoreCase ?? false; - requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - - if (requestModel.Body?.Matcher != null) - { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)); - } - else if (requestModel.Body?.Matchers != null) - { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)); - } - - return requestBuilder; - } - - private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) - { - IResponseBuilder responseBuilder = Response.Create(); - - if (responseModel.Delay > 0) - { - responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); - } - - if (responseModel.UseTransformer == true) - { - responseBuilder = responseBuilder.WithTransformer(responseModel.UseTransformerForBodyAsFile == true); - } - - if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) - { - var proxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = responseModel.ProxyUrl, - ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, - WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings - { - Address = responseModel.WebProxy.Address, - UserName = responseModel.WebProxy.UserName, - Password = responseModel.WebProxy.Password - } : null - }; - - return responseBuilder.WithProxy(proxyAndRecordSettings); - } - - if (responseModel.StatusCode is string statusCodeAsString) - { - responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); - } - else if (responseModel.StatusCode != null) - { - // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. - responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); - } - - if (responseModel.Headers != null) - { - foreach (var entry in responseModel.Headers) - { - responseBuilder = entry.Value is string value ? - responseBuilder.WithHeader(entry.Key, value) : - responseBuilder.WithHeader(entry.Key, JsonUtils.ParseJTokenToObject(entry.Value)); - } - } - else if (responseModel.HeadersRaw != null) - { - foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) - { - int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); - string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); - string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); - responseBuilder = responseBuilder.WithHeader(key, value); - } - } - - if (responseModel.BodyAsBytes != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.Body != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.BodyAsJson != null) - { - responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); - } - else if (responseModel.BodyAsFile != null) - { - responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile); - } - - if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) - { - responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); - } - - return responseBuilder; - } - - private ResponseMessage ToJson(T result, bool keepNullValues = false) - { - return new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? _settingsIncludeNullValues : _jsonSerializerSettings) - }, - StatusCode = (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(ContentTypeJson) } } - }; - } - - private Encoding ToEncoding(EncodingModel encodingModel) - { - return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; - } - - private T DeserializeObject(RequestMessage requestMessage) - { - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) - { - return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString); - } - - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) - { - return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject(); - } - - return default(T); - } - - private T[] DeserializeRequestMessageToArray(RequestMessage requestMessage) - { - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) - { - var bodyAsJson = requestMessage.BodyData.BodyAsJson; - - return DeserializeObjectToArray(bodyAsJson); - } - - return default(T[]); - } - - private T[] DeserializeObjectToArray(object value) - { - if (value is JArray jArray) - { - return jArray.ToObject(); - } - - var singleResult = ((JObject)value).ToObject(); - return new[] { singleResult }; - } - - private T[] DeserializeJsonToArray(string value) - { - return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); - } - } + /// + [PublicAPI] + public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path) + { + Check.NotNull(path, nameof(path)); + + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); + + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + { + var mappingModels = DeserializeJsonToArray(value); + foreach (var mappingModel in mappingModels) + { + if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) + { + ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path); + } + else + { + ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path); + } + } + + return true; + } + + return false; + } + #endregion + + #region Proxy and Record + private HttpClient _httpClientForProxy; + + private void InitProxyAndRecord(IWireMockServerSettings settings) + { + _httpClientForProxy = HttpClientHelper.CreateHttpClient(settings.ProxyAndRecordSettings); + + var respondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()); + if (settings.StartAdminInterface == true) + { + respondProvider.AtPriority(ProxyPriority); + } + + respondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); + } + + private async Task ProxyAndRecordAsync(RequestMessage requestMessage, IWireMockServerSettings settings) + { + var requestUri = new Uri(requestMessage.Url); + var proxyUri = new Uri(settings.ProxyAndRecordSettings.Url); + var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); + + var responseMessage = await HttpClientHelper.SendAsync( + _httpClientForProxy, + requestMessage, + proxyUriWithRequestPathAndQuery.AbsoluteUri, + !settings.DisableJsonBodyParsing.GetValueOrDefault(false), + !settings.DisableRequestBodyDecompressing.GetValueOrDefault(false) + ); + + if (HttpStatusRangeParser.IsMatch(settings.ProxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && + (settings.ProxyAndRecordSettings.SaveMapping || settings.ProxyAndRecordSettings.SaveMappingToFile)) + { + var mapping = ToMapping(requestMessage, responseMessage, settings.ProxyAndRecordSettings.BlackListedHeaders ?? new string[] { }, settings.ProxyAndRecordSettings.BlackListedCookies ?? new string[] { }); + + if (settings.ProxyAndRecordSettings.SaveMapping) + { + _options.Mappings.TryAdd(mapping.Guid, mapping); + } + + if (settings.ProxyAndRecordSettings.SaveMappingToFile) + { + SaveMappingToFile(mapping); + } + } + + return responseMessage; + } + + private IMapping ToMapping(RequestMessage requestMessage, ResponseMessage responseMessage, string[] blacklistedHeaders, string[] blacklistedCookies) + { + var request = Request.Create(); + request.WithPath(requestMessage.Path); + request.UsingMethod(requestMessage.Method); + + requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray())); + requestMessage.Cookies.Loop((key, value) => + { + if (!blacklistedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) + { + request.WithCookie(key, value); + } + }); + + var allBlackListedHeaders = new List(blacklistedHeaders) { "Cookie" }; + requestMessage.Headers.Loop((key, value) => + { + if (!allBlackListedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) + { + request.WithHeader(key, value.ToArray()); + } + }); + + switch (requestMessage.BodyData?.DetectedBodyType) + { + case BodyType.Json: + request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson)); + break; + + case BodyType.String: + request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsString)); + break; + + case BodyType.Bytes: + request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes)); + break; + } + + var response = Response.Create(responseMessage); + + return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null); + } + #endregion + + #region Settings + private ResponseMessage SettingsGet(RequestMessage requestMessage) + { + var model = new SettingsModel + { + AllowPartialMapping = _options.AllowPartialMapping, + MaxRequestLogCount = _options.MaxRequestLogCount, + RequestLogExpirationDuration = _options.RequestLogExpirationDuration, + GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, + AllowBodyForAllHttpMethods = _options.AllowBodyForAllHttpMethods + }; + + return ToJson(model); + } + + private ResponseMessage SettingsUpdate(RequestMessage requestMessage) + { + var settings = DeserializeObject(requestMessage); + _options.MaxRequestLogCount = settings.MaxRequestLogCount; + _options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; + + if (settings.AllowPartialMapping != null) + { + _options.AllowPartialMapping = settings.AllowPartialMapping.Value; + } + + if (settings.GlobalProcessingDelay != null) + { + _options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); + } + + if (settings.AllowBodyForAllHttpMethods != null) + { + _options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods.Value; + } + + return ResponseMessageBuilder.Create("Settings updated"); + } + #endregion Settings + + #region Mapping/{guid} + private ResponseMessage MappingGet(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid); + + if (mapping == null) + { + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create("Mapping not found", 404); + } + + var model = _mappingConverter.ToMappingModel(mapping); + + return ToJson(model); + } + + private ResponseMessage MappingPut(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + + var mappingModel = DeserializeObject(requestMessage); + Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); + + return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); + } + + private ResponseMessage MappingDelete(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + + if (DeleteMapping(guid)) + { + return ResponseMessageBuilder.Create("Mapping removed", 200, guid); + } + + return ResponseMessageBuilder.Create("Mapping not found", 404); + } + + private Guid ParseGuidFromRequestMessage(RequestMessage requestMessage) + { + return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); + } + #endregion Mapping/{guid} + + #region Mappings + private ResponseMessage MappingsSave(RequestMessage requestMessage) + { + SaveStaticMappings(); + + return ResponseMessageBuilder.Create("Mappings saved to disk"); + } + + private void SaveMappingToFile(IMapping mapping, string folder = null) + { + if (folder == null) + { + folder = _settings.FileSystemHandler.GetMappingFolder(); + } + + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + _settings.FileSystemHandler.CreateFolder(folder); + } + + var model = _mappingConverter.ToMappingModel(mapping); + string filename = (!string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString()) + ".json"; + + string path = Path.Combine(folder, filename); + + _settings.Logger.Info("Saving Mapping file {0}", filename); + + _settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, _jsonSerializerSettings)); + } + + private static string SanitizeFileName(string name, char replaceChar = '_') + { + return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar)); + } + + private IEnumerable ToMappingModels() + { + return Mappings.Where(m => !m.IsAdminInterface).Select(_mappingConverter.ToMappingModel); + } + + private ResponseMessage MappingsGet(RequestMessage requestMessage) + { + return ToJson(ToMappingModels()); + } + + private ResponseMessage MappingsPost(RequestMessage requestMessage) + { + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + if (mappingModels.Length == 1) + { + Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); + return ResponseMessageBuilder.Create("Mapping added", 201, guid); + } + + foreach (var mappingModel in mappingModels) + { + ConvertMappingAndRegisterAsRespondProvider(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? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null) + { + Check.NotNull(mappingModel, nameof(mappingModel)); + Check.NotNull(mappingModel.Request, nameof(mappingModel.Request)); + Check.NotNull(mappingModel.Response, nameof(mappingModel.Response)); + + var requestBuilder = InitRequestBuilder(mappingModel.Request, true); + if (requestBuilder == null) + { + return null; + } + + var responseBuilder = InitResponseBuilder(mappingModel.Response); + + var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); + + if (guid != null) + { + respondProvider = respondProvider.WithGuid(guid.Value); + } + else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) + { + respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); + } + + if (path != null) + { + respondProvider = respondProvider.WithPath(path); + } + + if (!string.IsNullOrEmpty(mappingModel.Title)) + { + respondProvider = respondProvider.WithTitle(mappingModel.Title); + } + + if (mappingModel.Priority != null) + { + respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); + } + + if (mappingModel.Scenario != null) + { + respondProvider = respondProvider.InScenario(mappingModel.Scenario); + respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs); + respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo); + } + + respondProvider.RespondWith(responseBuilder); + + return respondProvider.Guid; + } + + private ResponseMessage MappingsDelete(RequestMessage requestMessage) + { + if (!string.IsNullOrEmpty(requestMessage.Body)) + { + var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); + if (deletedGuids != null) + { + return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); + } + else + { + // return bad request + return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); + } + } + else + { + ResetMappings(); + + ResetScenarios(); + + return ResponseMessageBuilder.Create("Mappings deleted"); + } + } + + private IEnumerable MappingsDeleteMappingFromBody(RequestMessage requestMessage) + { + var deletedGuids = new List(); + + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + foreach (var mappingModel in mappingModels) + { + if (mappingModel.Guid.HasValue) + { + if (DeleteMapping(mappingModel.Guid.Value)) + { + deletedGuids.Add(mappingModel.Guid.Value); + } + else + { + _settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}."); + } + } + } + } + catch (ArgumentException a) + { + _settings.Logger.Error("ArgumentException: {0}", a); + return null; + } + catch (Exception e) + { + _settings.Logger.Error("Exception: {0}", e); + return null; + } + + return deletedGuids; + } + + private ResponseMessage MappingsReset(RequestMessage requestMessage) + { + ResetMappings(); + + ResetScenarios(); + + string message = "Mappings reset"; + if (requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && + bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) + && reloadStaticMappings) + { + ReadStaticMappings(); + message = $"{message} and static mappings reloaded"; + } + + return ResponseMessageBuilder.Create(message); + } + #endregion Mappings + + #region Request/{guid} + private ResponseMessage RequestGet(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); + + if (entry == null) + { + _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); + return ResponseMessageBuilder.Create("Request not found", 404); + } + + var model = LogEntryMapper.Map(entry); + + return ToJson(model); + } + + private ResponseMessage RequestDelete(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + + if (DeleteLogEntry(guid)) + { + return ResponseMessageBuilder.Create("Request removed"); + } + + return ResponseMessageBuilder.Create("Request not found", 404); + } + #endregion Request/{guid} + + #region Requests + private ResponseMessage RequestsGet(RequestMessage requestMessage) + { + var result = LogEntries + .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) + .Select(LogEntryMapper.Map); + + return ToJson(result); + } + + private ResponseMessage RequestsDelete(RequestMessage requestMessage) + { + ResetLogEntries(); + + return ResponseMessageBuilder.Create("Requests deleted"); + } + #endregion Requests + + #region Requests/find + private ResponseMessage RequestsFind(RequestMessage requestMessage) + { + var requestModel = DeserializeObject(requestMessage); + + var request = (Request)InitRequestBuilder(requestModel, false); + + var dict = new Dictionary(); + foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) + { + var requestMatchResult = new RequestMatchResult(); + if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) + { + dict.Add(logEntry, requestMatchResult); + } + } + + var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(LogEntryMapper.Map); + + return ToJson(result); + } + #endregion Requests/find + + #region Scenarios + private ResponseMessage ScenariosGet(RequestMessage requestMessage) + { + var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel + { + Name = s.Name, + NextState = s.NextState, + Started = s.Started, + Finished = s.Finished + }); + + return ToJson(scenariosStates, true); + } + + private ResponseMessage ScenariosReset(RequestMessage requestMessage) + { + ResetScenarios(); + + return ResponseMessageBuilder.Create("Scenarios reset"); + } + #endregion + + private IRequestBuilder InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) + { + IRequestBuilder requestBuilder = Request.Create(); + + if (requestModel.ClientIP != null) + { + if (requestModel.ClientIP is string clientIP) + { + requestBuilder = requestBuilder.WithClientIP(clientIP); + } + else + { + var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); + if (clientIPModel?.Matchers != null) + { + requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + } + + bool pathOrUrlmatchersValid = false; + if (requestModel.Path != null) + { + if (requestModel.Path is string path) + { + requestBuilder = requestBuilder.WithPath(path); + pathOrUrlmatchersValid = true; + } + else + { + var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); + if (pathModel?.Matchers != null) + { + requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlmatchersValid = true; + } + } + } + else if (requestModel.Url != null) + { + if (requestModel.Url is string url) + { + requestBuilder = requestBuilder.WithUrl(url); + pathOrUrlmatchersValid = true; + } + else + { + var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); + if (urlModel?.Matchers != null) + { + requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlmatchersValid = true; + } + } + } + + if (pathOrUrlRequired && !pathOrUrlmatchersValid) + { + _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); + return null; + } + + if (requestModel.Methods != null) + { + requestBuilder = requestBuilder.UsingMethod(requestModel.Methods); + } + + if (requestModel.Headers != null) + { + foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) + { + requestBuilder = requestBuilder.WithHeader( + headerModel.Name, + headerModel.IgnoreCase == true, + headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + headerModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray() + ); + } + } + + if (requestModel.Cookies != null) + { + foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) + { + requestBuilder = requestBuilder.WithCookie( + cookieModel.Name, + cookieModel.IgnoreCase == true, + cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + cookieModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + + if (requestModel.Params != null) + { + foreach (var paramModel in requestModel.Params.Where(c => c.Matchers != null)) + { + bool ignoreCase = paramModel?.IgnoreCase ?? false; + requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + + if (requestModel.Body?.Matcher != null) + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)); + } + else if (requestModel.Body?.Matchers != null) + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)); + } + + return requestBuilder; + } + + private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) + { + IResponseBuilder responseBuilder = Response.Create(); + + if (responseModel.Delay > 0) + { + responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); + } + + if (responseModel.UseTransformer == true) + { + responseBuilder = responseBuilder.WithTransformer(responseModel.UseTransformerForBodyAsFile == true); + } + + if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) + { + var proxyAndRecordSettings = new ProxyAndRecordSettings + { + Url = responseModel.ProxyUrl, + ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, + WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings + { + Address = responseModel.WebProxy.Address, + UserName = responseModel.WebProxy.UserName, + Password = responseModel.WebProxy.Password + } : null + }; + + return responseBuilder.WithProxy(proxyAndRecordSettings); + } + + if (responseModel.StatusCode is string statusCodeAsString) + { + responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); + } + else if (responseModel.StatusCode != null) + { + // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. + responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); + } + + if (responseModel.Headers != null) + { + foreach (var entry in responseModel.Headers) + { + responseBuilder = entry.Value is string value ? + responseBuilder.WithHeader(entry.Key, value) : + responseBuilder.WithHeader(entry.Key, JsonUtils.ParseJTokenToObject(entry.Value)); + } + } + else if (responseModel.HeadersRaw != null) + { + foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); + string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); + string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); + responseBuilder = responseBuilder.WithHeader(key, value); + } + } + + if (responseModel.BodyAsBytes != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.Body != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.BodyAsJson != null) + { + responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); + } + else if (responseModel.BodyAsFile != null) + { + responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile); + } + + if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) + { + responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); + } + + return responseBuilder; + } + + private ResponseMessage ToJson(T result, bool keepNullValues = false) + { + return new ResponseMessage + { + BodyData = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? _settingsIncludeNullValues : _jsonSerializerSettings) + }, + StatusCode = (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(ContentTypeJson) } } + }; + } + + private Encoding ToEncoding(EncodingModel encodingModel) + { + return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; + } + + private T DeserializeObject(RequestMessage requestMessage) + { + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) + { + return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString); + } + + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + { + return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject(); + } + + return default(T); + } + + private T[] DeserializeRequestMessageToArray(RequestMessage requestMessage) + { + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + { + var bodyAsJson = requestMessage.BodyData.BodyAsJson; + + return DeserializeObjectToArray(bodyAsJson); + } + + return default(T[]); + } + + private T[] DeserializeObjectToArray(object value) + { + if (value is JArray jArray) + { + return jArray.ToObject(); + } + + var singleResult = ((JObject)value).ToObject(); + return new[] { singleResult }; + } + + private T[] DeserializeJsonToArray(string value) + { + return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs b/src/WireMock.Net/Server/WireMockServer.LogEntries.cs index 002fb46b..ee27b497 100644 --- a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs +++ b/src/WireMock.Net/Server/WireMockServer.LogEntries.cs @@ -1,93 +1,91 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using JetBrains.Annotations; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Matchers.Request; - -namespace WireMock.Server -{ - public partial class WireMockServer +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using JetBrains.Annotations; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.Server +{ + public partial class WireMockServer { - /// - [PublicAPI] - public event NotifyCollectionChangedEventHandler LogEntriesChanged - { - add - { - _options.LogEntries.CollectionChanged += (sender, eventRecordArgs) => - { - try - { - value(sender, eventRecordArgs); - } - catch (Exception exception) - { - _options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message); - } - }; - } - - remove => _options.LogEntries.CollectionChanged -= value; + /// + [PublicAPI] + public event NotifyCollectionChangedEventHandler LogEntriesChanged + { + add + { + _options.LogEntries.CollectionChanged += (sender, eventRecordArgs) => + { + try + { + value(sender, eventRecordArgs); + } + catch (Exception exception) + { + _options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message); + } + }; + } + + remove => _options.LogEntries.CollectionChanged -= value; } - /// - /// Gets the request logs. - /// - [PublicAPI] - public IEnumerable LogEntries => new ReadOnlyCollection(_options.LogEntries.ToList()); + /// + [PublicAPI] + public IEnumerable LogEntries => new ReadOnlyCollection(_options.LogEntries.ToList()); - /// - /// The search log-entries based on matchers. - /// - /// The matchers. - /// The . - [PublicAPI] - public IEnumerable FindLogEntries([NotNull] params IRequestMatcher[] matchers) - { - var results = new Dictionary(); - - foreach (var log in _options.LogEntries.ToList()) - { - var requestMatchResult = new RequestMatchResult(); - foreach (var matcher in matchers) - { - matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); - } - - if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect) - { - results.Add(log, requestMatchResult); - } - } - - return new ReadOnlyCollection(results.OrderBy(x => x.Value).Select(x => x.Key).ToList()); + /// + /// The search log-entries based on matchers. + /// + /// The matchers. + /// The . + [PublicAPI] + public IEnumerable FindLogEntries([NotNull] params IRequestMatcher[] matchers) + { + var results = new Dictionary(); + + foreach (var log in _options.LogEntries.ToList()) + { + var requestMatchResult = new RequestMatchResult(); + foreach (var matcher in matchers) + { + matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); + } + + if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect) + { + results.Add(log, requestMatchResult); + } + } + + return new ReadOnlyCollection(results.OrderBy(x => x.Value).Select(x => x.Key).ToList()); } - /// - [PublicAPI] - public void ResetLogEntries() - { - _options.LogEntries.Clear(); + /// + [PublicAPI] + public void ResetLogEntries() + { + _options.LogEntries.Clear(); } - /// - [PublicAPI] - public bool DeleteLogEntry(Guid guid) - { - // Check a logentry exists with the same GUID, if so, remove it. - var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid); - if (existing != null) - { - _options.LogEntries.Remove(existing); - return true; - } - - return false; - } - } + /// + [PublicAPI] + public bool DeleteLogEntry(Guid guid) + { + // Check a logentry exists with the same GUID, if so, remove it. + var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid); + if (existing != null) + { + _options.LogEntries.Remove(existing); + return true; + } + + return false; + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/BodyData.cs b/src/WireMock.Net/Util/BodyData.cs index 50c8d1d7..1e7d54e0 100644 --- a/src/WireMock.Net/Util/BodyData.cs +++ b/src/WireMock.Net/Util/BodyData.cs @@ -6,56 +6,36 @@ namespace WireMock.Util /// /// BodyData /// - public class BodyData + public class BodyData : IBodyData { - /// - /// The body encoding. - /// + /// public Encoding Encoding { get; set; } - /// - /// The body as string, this is defined when BodyAsString or BodyAsJson are not null. - /// + /// public string BodyAsString { get; set; } - /// - /// The body (as JSON object). - /// + /// public object BodyAsJson { get; set; } - /// - /// The body (as bytearray). - /// + /// public byte[] BodyAsBytes { get; set; } - /// - /// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. - /// + /// public bool? BodyAsJsonIndented { get; set; } - /// - /// Gets or sets the body as a file. - /// + /// public string BodyAsFile { get; set; } - /// - /// Is the body as file cached? - /// + /// public bool? BodyAsFileIsCached { get; set; } - /// - /// The detected body type (detection based on body content). - /// + /// public BodyType DetectedBodyType { get; set; } - /// - /// The detected body type (detection based on Content-Type). - /// + /// public BodyType DetectedBodyTypeFromContentType { get; set; } - /// - /// The detected compression. - /// + /// public string DetectedCompression { get; set; } } } \ No newline at end of file