diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs index 99f5a9e2..fa1df233 100644 --- a/examples/WireMock.Net.ConsoleApplication/Program.cs +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -5,7 +5,6 @@ using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; - namespace WireMock.Net.ConsoleApplication { static class Program @@ -14,9 +13,9 @@ namespace WireMock.Net.ConsoleApplication { int port; if (args.Length == 0 || !int.TryParse(args[0], out port)) - port = 8080; + port = 9090; - var server = FluentMockServer.Start(port); + var server = FluentMockServer.StartWithAdminInterface(port); Console.WriteLine("FluentMockServer running at {0}", server.Port); server @@ -24,17 +23,17 @@ namespace WireMock.Net.ConsoleApplication .RespondWith(Response.Create() .WithStatusCode(200) .WithHeader("Content-Type", "application/json") - .WithBody(@"{ ""result"": ""/x with FUNC 200""}")); + .WithBody(@"{ ""result"": ""Contains x with FUNC 200""}")); // http://localhost:8080/gffgfgf/sddsds?start=1000&stop=1&stop=2 server - .Given(Request.Create().WithUrl("/*").UsingGet()) + .Given(Request.Create().WithUrl("/*").UsingGet().WithParam("start")) .RespondWith(Response.Create() .WithStatusCode(200) .WithHeader("Content-Type", "application/json") .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}") - .WithBody(@"{""msg"": ""Hello world! : {{request.url}} : {{request.path}} : - bykey={{request.query.start}} : bykey={{request.query.stop}} byidx0={{request.query.stop.[0]}} byidx1={{request.query.stop.[1]}}""") + .WithBody(@"{""msg"": ""Hello world, {{request.url}}, {{request.path}} : + bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}""") .WithTransformer() .WithDelay(TimeSpan.FromMilliseconds(100)) ); @@ -47,7 +46,7 @@ namespace WireMock.Net.ConsoleApplication .WithBody(@"{ ""result"": ""data posted with FUNC 201""}")); server - .Given(Request.Create().WithUrl("/data", "/ax").UsingPost()) + .Given(Request.Create().WithUrl("/data", "/ax").UsingPost().WithHeader("Content-Type", "application/json*")) .RespondWith(Response.Create() .WithStatusCode(201) .WithHeader("Content-Type", "application/json") @@ -60,6 +59,13 @@ namespace WireMock.Net.ConsoleApplication .WithHeader("Content-Type", "application/json") .WithBody(@"{ ""result"": ""json posted with 201""}")); + server + .Given(Request.Create().WithUrl("/json2").UsingPost().WithBody("x")) + .RespondWith(Response.Create() + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""json posted with x - 201""}")); + server .Given(Request.Create().WithUrl("/data").UsingDelete()) .RespondWith(Response.Create() @@ -67,6 +73,11 @@ namespace WireMock.Net.ConsoleApplication .WithHeader("Content-Type", "application/json") .WithBody(@"{ ""result"": ""data deleted with 200""}")); + server + .Given(Request.Create().WithUrl("/nobody").UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200)); + Console.WriteLine("Press any key to stop the server"); Console.ReadKey(); diff --git a/src/WireMock/Admin/BodyModel.cs b/src/WireMock/Admin/BodyModel.cs new file mode 100644 index 00000000..ac0dfd0e --- /dev/null +++ b/src/WireMock/Admin/BodyModel.cs @@ -0,0 +1,16 @@ +namespace WireMock.Admin +{ + /// + /// Body Model + /// + public class BodyModel + { + /// + /// Gets or sets the matcher. + /// + /// + /// The matcher. + /// + public MatcherModel Matcher { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/CookieModel.cs b/src/WireMock/Admin/CookieModel.cs new file mode 100644 index 00000000..68807650 --- /dev/null +++ b/src/WireMock/Admin/CookieModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// Cookie Model + /// + public class CookieModel + { + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the matchers. + /// + /// + /// The matchers. + /// + public IList Matchers { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/FuncModel.cs b/src/WireMock/Admin/FuncModel.cs new file mode 100644 index 00000000..ecef8070 --- /dev/null +++ b/src/WireMock/Admin/FuncModel.cs @@ -0,0 +1,7 @@ +//namespace WireMock.Admin +//{ +// public class FuncModel +// { +// public string Name { get; set; } +// } +//} \ No newline at end of file diff --git a/src/WireMock/Admin/HeaderModel.cs b/src/WireMock/Admin/HeaderModel.cs new file mode 100644 index 00000000..262c699f --- /dev/null +++ b/src/WireMock/Admin/HeaderModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// Header Model + /// + public class HeaderModel + { + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the matchers. + /// + /// + /// The matchers. + /// + public IList Matchers { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/MappingModel.cs b/src/WireMock/Admin/MappingModel.cs new file mode 100644 index 00000000..3b5ea4ca --- /dev/null +++ b/src/WireMock/Admin/MappingModel.cs @@ -0,0 +1,34 @@ +using System; + +namespace WireMock.Admin +{ + /// + /// MappingModel + /// + public class MappingModel + { + /// + /// Gets or sets the unique identifier. + /// + /// + /// The unique identifier. + /// + public Guid Guid { get; set; } + + /// + /// Gets or sets the request. + /// + /// + /// The request. + /// + public RequestModel Request { get; set; } + + /// + /// Gets or sets the response. + /// + /// + /// The response. + /// + public ResponseModel Response { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/MatcherModel.cs b/src/WireMock/Admin/MatcherModel.cs new file mode 100644 index 00000000..56f5106d --- /dev/null +++ b/src/WireMock/Admin/MatcherModel.cs @@ -0,0 +1,24 @@ +namespace WireMock.Admin +{ + /// + /// MatcherModel + /// + public class MatcherModel + { + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the pattern. + /// + /// + /// The pattern. + /// + public string Pattern { get; set; } + } +} diff --git a/src/WireMock/Admin/ParamModel.cs b/src/WireMock/Admin/ParamModel.cs new file mode 100644 index 00000000..e4c85bc1 --- /dev/null +++ b/src/WireMock/Admin/ParamModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// Param Model + /// + public class ParamModel + { + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the values. + /// + /// + /// The values. + /// + public IList Values { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/RequestModel.cs b/src/WireMock/Admin/RequestModel.cs new file mode 100644 index 00000000..cdc2b08d --- /dev/null +++ b/src/WireMock/Admin/RequestModel.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// RequestModel + /// + public class RequestModel + { + /// + /// Gets or sets the URL. + /// + /// + /// The URL. + /// + public UrlModel Url { get; set; } + + /// + /// The verbs + /// + public string[] Verbs { get; set; } + + /// + /// Gets or sets the Headers. + /// + /// + /// The Headers. + /// + public IList Headers { get; set; } + + /// + /// Gets or sets the Cookies. + /// + /// + /// The Cookies. + /// + public IList Cookies { get; set; } + + /// + /// Gets or sets the Params. + /// + /// + /// The Headers. + /// + public IList Params { get; set; } + + /// + /// Gets or sets the body. + /// + /// + /// The body. + /// + public BodyModel Body { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/ResponseModel.cs b/src/WireMock/Admin/ResponseModel.cs new file mode 100644 index 00000000..cd7551dd --- /dev/null +++ b/src/WireMock/Admin/ResponseModel.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// ResponseModel + /// + public class ResponseModel + { + /// + /// Gets or sets the HTTP status. + /// + /// + /// The HTTP status. + /// + public int StatusCode { get; set; } + + /// + /// Gets or sets the body. + /// + /// + /// The body. + /// + public string Body { get; set; } + + /// + /// Gets or sets the headers. + /// + /// + /// The headers. + /// + public IDictionary Headers { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/Admin/UrlModel.cs b/src/WireMock/Admin/UrlModel.cs new file mode 100644 index 00000000..77164271 --- /dev/null +++ b/src/WireMock/Admin/UrlModel.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace WireMock.Admin +{ + /// + /// UrlModel + /// + public class UrlModel + { + /// + /// Gets or sets the matchers. + /// + /// + /// The matchers. + /// + public IList Matchers { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock/DynamicResponseProvider.cs b/src/WireMock/DynamicResponseProvider.cs new file mode 100644 index 00000000..9fd64afa --- /dev/null +++ b/src/WireMock/DynamicResponseProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using WireMock.Validation; + +namespace WireMock +{ + internal class DynamicResponseProvider : IResponseProvider + { + private readonly Func _responseMessageFunc; + + public DynamicResponseProvider([NotNull] Func responseMessageFunc) + { + Check.NotNull(responseMessageFunc, nameof(responseMessageFunc)); + + _responseMessageFunc = responseMessageFunc; + } + + public Task ProvideResponse(RequestMessage requestMessage) + { + return Task.FromResult(_responseMessageFunc()); + } + } +} \ No newline at end of file diff --git a/src/WireMock/IProvideResponses.cs b/src/WireMock/IResponseProvider.cs similarity index 53% rename from src/WireMock/IProvideResponses.cs rename to src/WireMock/IResponseProvider.cs index 055d5ec3..7c785ee2 100644 --- a/src/WireMock/IProvideResponses.cs +++ b/src/WireMock/IResponseProvider.cs @@ -1,17 +1,11 @@ -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; - -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1633:FileMustHaveHeader", - Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] +using System.Threading.Tasks; namespace WireMock { /// - /// The ProvideResponses interface. + /// The Response Provider interface. /// - public interface IProvideResponses + public interface IResponseProvider { /// /// The provide response. diff --git a/src/WireMock/Route.cs b/src/WireMock/Mapping.cs similarity index 82% rename from src/WireMock/Route.cs rename to src/WireMock/Mapping.cs index 474b2fab..4ae6d86a 100644 --- a/src/WireMock/Route.cs +++ b/src/WireMock/Mapping.cs @@ -6,24 +6,24 @@ namespace WireMock /// /// The route. /// - public class Route + public class Mapping { /// - /// The _request matcher. + /// The Request matcher. /// public IRequestMatcher RequestMatcher { get; } /// - /// The _provider. + /// The Provider. /// - public IProvideResponses Provider { get; } + public IResponseProvider Provider { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The request matcher. /// The provider. - public Route(IRequestMatcher requestMatcher, IProvideResponses provider) + public Mapping(IRequestMatcher requestMatcher, IResponseProvider provider) { RequestMatcher = requestMatcher; Provider = provider; diff --git a/src/WireMock/Matchers/IMatcher.cs b/src/WireMock/Matchers/IMatcher.cs index 398f2508..073043e7 100644 --- a/src/WireMock/Matchers/IMatcher.cs +++ b/src/WireMock/Matchers/IMatcher.cs @@ -13,5 +13,11 @@ /// true if the specified input is match; otherwise, false. /// bool IsMatch(string input); + + /// + /// Gets the pattern. + /// + /// Pattern + string GetPattern(); } } \ No newline at end of file diff --git a/src/WireMock/Matchers/JSONPathMatcher.cs b/src/WireMock/Matchers/JSONPathMatcher.cs index 6fa93bcf..97f85004 100644 --- a/src/WireMock/Matchers/JSONPathMatcher.cs +++ b/src/WireMock/Matchers/JSONPathMatcher.cs @@ -48,5 +48,14 @@ namespace WireMock.Matchers return false; } } + + /// + /// Gets the pattern. + /// + /// Pattern + public string GetPattern() + { + return _pattern; + } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/RegexMatcher.cs b/src/WireMock/Matchers/RegexMatcher.cs index ad634bc0..6f4c9ea0 100644 --- a/src/WireMock/Matchers/RegexMatcher.cs +++ b/src/WireMock/Matchers/RegexMatcher.cs @@ -11,6 +11,7 @@ namespace WireMock.Matchers /// public class RegexMatcher : IMatcher { + private readonly string _pattern; private readonly Regex _expression; /// @@ -21,7 +22,8 @@ namespace WireMock.Matchers { Check.NotNull(pattern, nameof(pattern)); - _expression = new Regex(pattern, RegexOptions.Compiled); + _pattern = pattern; + _expression = new Regex(_pattern, RegexOptions.Compiled); } /// @@ -45,5 +47,14 @@ namespace WireMock.Matchers return false; } } + + /// + /// Gets the pattern. + /// + /// Pattern + public string GetPattern() + { + return _pattern; + } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageBodyMatcher.cs index c67e74ff..70b3e82b 100644 --- a/src/WireMock/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageBodyMatcher.cs @@ -19,11 +19,6 @@ namespace WireMock.Matchers.Request /// private readonly byte[] _bodyData; - /// - /// The matcher. - /// - private readonly IMatcher _matcher; - /// /// The body function /// @@ -34,6 +29,11 @@ namespace WireMock.Matchers.Request /// private readonly Func _bodyDataFunc; + /// + /// The matcher. + /// + public readonly IMatcher Matcher; + /// /// Initializes a new instance of the class. /// @@ -91,7 +91,7 @@ namespace WireMock.Matchers.Request public RequestMessageBodyMatcher([NotNull] IMatcher matcher) { Check.NotNull(matcher, nameof(matcher)); - _matcher = matcher; + Matcher = matcher; } /// @@ -103,8 +103,8 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_matcher != null) - return _matcher.IsMatch(requestMessage.Body); + if (Matcher != null) + return Matcher.IsMatch(requestMessage.Body); if (_body != null) return requestMessage.Body == _body; diff --git a/src/WireMock/Matchers/Request/RequestMessageCompositeMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageCompositeMatcher.cs index 6b40aaa2..c7c94f9a 100644 --- a/src/WireMock/Matchers/Request/RequestMessageCompositeMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageCompositeMatcher.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using WireMock.Validation; namespace WireMock.Matchers.Request { /// /// The composite request matcher. /// - public class RequestMessageCompositeMatcher : IRequestMatcher + public abstract class RequestMessageCompositeMatcher : IRequestMatcher { private readonly CompositeMatcherType _type; @@ -17,15 +18,17 @@ namespace WireMock.Matchers.Request /// /// The request matchers. /// - public IEnumerable RequestMatchers { get; } + private IEnumerable RequestMatchers { get; } /// /// Initializes a new instance of the class. /// /// The request matchers. /// The CompositeMatcherType type (Defaults to 'And') - public RequestMessageCompositeMatcher([NotNull] IEnumerable requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) + protected RequestMessageCompositeMatcher([NotNull] IEnumerable requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) { + Check.NotNull(requestMatchers, nameof(requestMatchers)); + _type = type; RequestMatchers = requestMatchers; } @@ -37,11 +40,11 @@ namespace WireMock.Matchers.Request /// /// true if the specified RequestMessage is match; otherwise, false. /// - public bool IsMatch(RequestMessage requestMessage) + public virtual bool IsMatch(RequestMessage requestMessage) { return _type == CompositeMatcherType.And ? - RequestMatchers.All(spec => spec.IsMatch(requestMessage)) : - RequestMatchers.Any(spec => spec.IsMatch(requestMessage)); + RequestMatchers.All(matcher => matcher.IsMatch(requestMessage)) : + RequestMatchers.Any(matcher => matcher.IsMatch(requestMessage)); } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageCookieMatcher.cs index 690b5d3b..ce277113 100644 --- a/src/WireMock/Matchers/Request/RequestMessageCookieMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageCookieMatcher.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using WireMock.Validation; @@ -10,12 +11,21 @@ namespace WireMock.Matchers.Request /// public class RequestMessageCookieMatcher : IRequestMatcher { - private readonly string _name; + private readonly Func, bool>[] _cookieFuncs; - private readonly IMatcher _matcher; - - private readonly Func, bool> _cookieFunc; + /// + /// The name + /// + public string Name { get; } + /// + /// Gets the matchers. + /// + /// + /// The matchers. + /// + public IMatcher[] Matchers { get; } + /// /// Initializes a new instance of the class. /// @@ -27,20 +37,18 @@ namespace WireMock.Matchers.Request Check.NotNull(name, nameof(name)); Check.NotNull(pattern, nameof(pattern)); - _name = name; - _matcher = new WildcardMatcher(pattern, ignoreCase); + Name = name; + Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) }; } /// /// Initializes a new instance of the class. /// - /// - /// The func. - /// - public RequestMessageCookieMatcher([NotNull] Func, bool> func) + /// The funcs. + public RequestMessageCookieMatcher([NotNull] params Func, bool>[] funcs) { - Check.NotNull(func, nameof(func)); - _cookieFunc = func; + Check.NotNull(funcs, nameof(funcs)); + _cookieFuncs = funcs; } /// @@ -52,14 +60,14 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_cookieFunc != null) - return _cookieFunc(requestMessage.Cookies); + if (_cookieFuncs != null) + return _cookieFuncs.Any(cf => cf(requestMessage.Cookies)); if (requestMessage.Cookies == null) return false; - string headerValue = requestMessage.Cookies[_name]; - return _matcher.IsMatch(headerValue); + string headerValue = requestMessage.Cookies[Name]; + return Matchers.Any(m => m.IsMatch(headerValue)); } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageHeaderMatcher.cs index 13fbf77f..702b57e2 100644 --- a/src/WireMock/Matchers/Request/RequestMessageHeaderMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageHeaderMatcher.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using WireMock.Validation; @@ -10,20 +11,20 @@ namespace WireMock.Matchers.Request /// public class RequestMessageHeaderMatcher : IRequestMatcher { - /// - /// The name. - /// - private readonly string _name; + private readonly Func, bool>[] _headerFuncs; /// - /// The matcher. + /// The name /// - private readonly IMatcher _matcher; + public string Name { get; } /// - /// The header function + /// Gets the matchers. /// - private readonly Func, bool> _headerFunc; + /// + /// The matchers. + /// + public IMatcher[] Matchers { get; } /// /// Initializes a new instance of the class. @@ -36,20 +37,18 @@ namespace WireMock.Matchers.Request Check.NotNull(name, nameof(name)); Check.NotNull(pattern, nameof(pattern)); - _name = name; - _matcher = new WildcardMatcher(pattern, ignoreCase); + Name = name; + Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) }; } /// /// Initializes a new instance of the class. /// - /// - /// The func. - /// - public RequestMessageHeaderMatcher([NotNull] Func, bool> func) + /// The funcs. + public RequestMessageHeaderMatcher([NotNull] params Func, bool>[] funcs) { - Check.NotNull(func, nameof(func)); - _headerFunc = func; + Check.NotNull(funcs, nameof(funcs)); + _headerFuncs = funcs; } /// @@ -61,14 +60,14 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_headerFunc != null) - return _headerFunc(requestMessage.Headers); + if (_headerFuncs != null) + return _headerFuncs.Any(hf => hf(requestMessage.Headers)); if (requestMessage.Headers == null) return false; - string headerValue = requestMessage.Headers[_name]; - return _matcher.IsMatch(headerValue); + string headerValue = requestMessage.Headers[Name]; + return Matchers.Any(m => m.IsMatch(headerValue)); } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageParamMatcher.cs index 8197fb02..eebb1574 100644 --- a/src/WireMock/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageParamMatcher.cs @@ -12,17 +12,17 @@ namespace WireMock.Matchers.Request /// public class RequestMessageParamMatcher : IRequestMatcher { - /// - /// The _key. - /// - private readonly string _key; + private readonly Func>, bool>[] _funcs; /// - /// The _values. + /// The key /// - private readonly IEnumerable _values; + public string Key { get; } - private readonly Func>, bool> _func; + /// + /// The values + /// + public IEnumerable Values { get; } /// /// Initializes a new instance of the class. @@ -38,20 +38,18 @@ namespace WireMock.Matchers.Request Check.NotNull(key, nameof(key)); Check.NotNull(values, nameof(values)); - _key = key; - _values = values; + Key = key; + Values = values; } /// /// Initializes a new instance of the class. /// - /// - /// The func. - /// - public RequestMessageParamMatcher([NotNull] Func>, bool> func) + /// The funcs. + public RequestMessageParamMatcher([NotNull] params Func>, bool>[] funcs) { - Check.NotNull(func, nameof(func)); - _func = func; + Check.NotNull(funcs, nameof(funcs)); + _funcs = funcs; } /// @@ -63,12 +61,11 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_func != null) - { - return _func(requestMessage.Query); - } + if (_funcs != null) + return _funcs.Any(f => f(requestMessage.Query)); - return requestMessage.GetParameter(_key).Intersect(_values).Count() == _values.Count(); + var values = requestMessage.GetParameter(Key); + return values?.Intersect(Values).Count() == Values.Count(); } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock/Matchers/Request/RequestMessagePathMatcher.cs index 42fbfbc0..fe711ee9 100644 --- a/src/WireMock/Matchers/Request/RequestMessagePathMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessagePathMatcher.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using WireMock.Validation; @@ -12,48 +14,39 @@ namespace WireMock.Matchers.Request /// /// The matcher. /// - private readonly string _path; + public IReadOnlyList Matchers { get; } /// - /// The matcher. + /// The path functions /// - private readonly IMatcher _matcher; - - /// - /// The path function - /// - private readonly Func _pathFunc; + private readonly Func[] _pathFuncs; /// /// Initializes a new instance of the class. /// - /// The path. - public RequestMessagePathMatcher([NotNull] string path) + /// The paths. + public RequestMessagePathMatcher([NotNull] params string[] paths) : this(paths.Select(path => new WildcardMatcher(path)).ToArray()) { - Check.NotNull(path, nameof(path)); - _path = path; } /// /// Initializes a new instance of the class. /// - /// The matcher. - public RequestMessagePathMatcher([NotNull] IMatcher matcher) + /// The matchers. + public RequestMessagePathMatcher([NotNull] params IMatcher[] matchers) { - Check.NotNull(matcher, nameof(matcher)); - _matcher = matcher; + Check.NotNull(matchers, nameof(matchers)); + Matchers = matchers; } /// /// Initializes a new instance of the class. /// - /// - /// The path func. - /// - public RequestMessagePathMatcher([NotNull] Func func) + /// The path functions. + public RequestMessagePathMatcher([NotNull] params Func[] funcs) { - Check.NotNull(func, nameof(func)); - _pathFunc = func; + Check.NotNull(funcs, nameof(funcs)); + _pathFuncs = funcs; } /// @@ -65,14 +58,11 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_path != null) - return string.CompareOrdinal(_path, requestMessage.Path) == 0; + if (Matchers != null) + return Matchers.Any(matcher => matcher.IsMatch(requestMessage.Path)); - if (_matcher != null) - return _matcher.IsMatch(requestMessage.Path); - - if (_pathFunc != null) - return _pathFunc(requestMessage.Path); + if (_pathFuncs != null) + return _pathFuncs.Any(func => func(requestMessage.Path)); return false; } diff --git a/src/WireMock/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageUrlMatcher.cs index cc0c26c9..3afde1b3 100644 --- a/src/WireMock/Matchers/Request/RequestMessageUrlMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageUrlMatcher.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using WireMock.Validation; @@ -12,42 +14,39 @@ namespace WireMock.Matchers.Request /// /// The matcher. /// - private readonly IMatcher _matcher; + public readonly IReadOnlyList Matchers; /// - /// The url function + /// The url functions /// - private readonly Func _urlFunc; + private readonly Func[] _urlFuncs; /// /// Initializes a new instance of the class. /// - /// The url. - public RequestMessageUrlMatcher([NotNull] string url) : this(new WildcardMatcher(url)) + /// The urls. + public RequestMessageUrlMatcher([NotNull] params string[] urls) : this(urls.Select(url => new WildcardMatcher(url)).ToArray()) { - _matcher = new WildcardMatcher(url); } /// /// Initializes a new instance of the class. /// - /// The matcher. - public RequestMessageUrlMatcher([NotNull] IMatcher matcher) + /// The matchers. + public RequestMessageUrlMatcher([NotNull] params IMatcher[] matchers) { - Check.NotNull(matcher, nameof(matcher)); - _matcher = matcher; + Check.NotNull(matchers, nameof(matchers)); + Matchers = matchers; } /// /// Initializes a new instance of the class. /// - /// - /// The url func. - /// - public RequestMessageUrlMatcher([NotNull] Func func) + /// The url functions. + public RequestMessageUrlMatcher([NotNull] params Func[] funcs) { - Check.NotNull(func, nameof(func)); - _urlFunc = func; + Check.NotNull(funcs, nameof(funcs)); + _urlFuncs = funcs; } /// @@ -59,11 +58,11 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - if (_matcher != null) - return _matcher.IsMatch(requestMessage.Path); + if (Matchers != null) + return Matchers.Any(matcher => matcher.IsMatch(requestMessage.Path)); - if (_urlFunc != null) - return _urlFunc(requestMessage.Url); + if (_urlFuncs != null) + return _urlFuncs.Any(func => func(requestMessage.Url)); return false; } diff --git a/src/WireMock/Matchers/Request/RequestMessageVerbMatcher.cs b/src/WireMock/Matchers/Request/RequestMessageVerbMatcher.cs index 826f8617..7ef8d01b 100644 --- a/src/WireMock/Matchers/Request/RequestMessageVerbMatcher.cs +++ b/src/WireMock/Matchers/Request/RequestMessageVerbMatcher.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Linq; +using JetBrains.Annotations; using WireMock.Validation; namespace WireMock.Matchers.Request @@ -9,20 +10,20 @@ namespace WireMock.Matchers.Request internal class RequestMessageVerbMatcher : IRequestMatcher { /// - /// The _verb. + /// The verbs /// - private readonly string _verb; + public string[] Verbs { get; } /// /// Initializes a new instance of the class. /// - /// + /// /// The verb. /// - public RequestMessageVerbMatcher([NotNull] string verb) + public RequestMessageVerbMatcher([NotNull] params string[] verbs) { - Check.NotNull(verb, nameof(verb)); - _verb = verb.ToLower(); + Check.NotNull(verbs, nameof(verbs)); + Verbs = verbs.Select(v => v.ToLower()).ToArray(); } /// @@ -34,7 +35,7 @@ namespace WireMock.Matchers.Request /// public bool IsMatch(RequestMessage requestMessage) { - return requestMessage.Verb == _verb; + return Verbs.Contains(requestMessage.Verb); } } } \ No newline at end of file diff --git a/src/WireMock/Matchers/WildcardMatcher.cs b/src/WireMock/Matchers/WildcardMatcher.cs index 2db63b75..1ff651be 100644 --- a/src/WireMock/Matchers/WildcardMatcher.cs +++ b/src/WireMock/Matchers/WildcardMatcher.cs @@ -37,6 +37,15 @@ namespace WireMock.Matchers return MatchWildcardString(_pattern, input); } + /// + /// Gets the pattern. + /// + /// Pattern + public string GetPattern() + { + return _pattern; + } + /// /// Copy/paste from http://www.codeproject.com/Tips/57304/Use-wildcard-characters-and-to-compare-strings /// diff --git a/src/WireMock/Matchers/XPathMatcher.cs b/src/WireMock/Matchers/XPathMatcher.cs index caa6b7b9..b1a92c91 100644 --- a/src/WireMock/Matchers/XPathMatcher.cs +++ b/src/WireMock/Matchers/XPathMatcher.cs @@ -49,5 +49,14 @@ namespace WireMock.Matchers return false; } } + + /// + /// Gets the pattern. + /// + /// Pattern + public string GetPattern() + { + return _pattern; + } } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IHeadersAndCookiesRequestBuilder.cs b/src/WireMock/RequestBuilders/IHeadersAndCookiesRequestBuilder.cs index bfae8478..4c98d83a 100644 --- a/src/WireMock/RequestBuilders/IHeadersAndCookiesRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IHeadersAndCookiesRequestBuilder.cs @@ -22,9 +22,9 @@ namespace WireMock.RequestBuilders /// /// The with header. /// - /// The headers func. + /// The headers funcs. /// The . - IHeadersAndCookiesRequestBuilder WithHeader([NotNull] params Func, bool>[] func); + IHeadersAndCookiesRequestBuilder WithHeader([NotNull] params Func, bool>[] funcs); /// /// The with header. @@ -38,8 +38,8 @@ namespace WireMock.RequestBuilders /// /// The with header. /// - /// The func. + /// The funcs. /// The . - IHeadersAndCookiesRequestBuilder WithCookie([NotNull] params Func, bool>[] cookieFunc); + IHeadersAndCookiesRequestBuilder WithCookie([NotNull] params Func, bool>[] cookieFuncs); } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs b/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs index b445688b..3c6b563c 100644 --- a/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs @@ -28,12 +28,8 @@ namespace WireMock.RequestBuilders /// /// The with parameters. /// - /// - /// The func. - /// - /// - /// The . - /// - IRequestMatcher WithParam([NotNull] Func>, bool> func); + /// The funcs. + /// The . + IRequestMatcher WithParam([NotNull] params Func>, bool>[] funcs); } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IUrlAndPathRequestBuilder.cs b/src/WireMock/RequestBuilders/IUrlAndPathRequestBuilder.cs index bc500fb5..0f93c4e6 100644 --- a/src/WireMock/RequestBuilders/IUrlAndPathRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IUrlAndPathRequestBuilder.cs @@ -12,37 +12,37 @@ namespace WireMock.RequestBuilders /// /// The with url. /// - /// The matcher. + /// The matchers. /// The . - IUrlAndPathRequestBuilder WithUrl([NotNull] IMatcher matcher); + IUrlAndPathRequestBuilder WithUrl([NotNull] params IMatcher[] matchers); /// /// The with url. /// - /// The url. + /// The urls. /// The . - IUrlAndPathRequestBuilder WithUrl([NotNull] params string[] url); + IUrlAndPathRequestBuilder WithUrl([NotNull] params string[] urls); /// /// The with url. /// - /// The url func. + /// The url funcs. /// The . - IUrlAndPathRequestBuilder WithUrl([NotNull] params Func[] func); + IUrlAndPathRequestBuilder WithUrl([NotNull] params Func[] funcs); /// /// The with path. /// - /// The matcher. + /// The matchers. /// The . - IUrlAndPathRequestBuilder WithPath([NotNull] IMatcher matcher); + IUrlAndPathRequestBuilder WithPath([NotNull] params IMatcher[] matchers); /// /// The with path. /// - /// The path. + /// The paths. /// The . - IUrlAndPathRequestBuilder WithPath([NotNull] params string[] path); + IUrlAndPathRequestBuilder WithPath([NotNull] params string[] paths); /// /// The with path. diff --git a/src/WireMock/RequestBuilders/Request.cs b/src/WireMock/RequestBuilders/Request.cs index ea9f97c9..249ce344 100644 --- a/src/WireMock/RequestBuilders/Request.cs +++ b/src/WireMock/RequestBuilders/Request.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -32,15 +33,34 @@ namespace WireMock.RequestBuilders _requestMatchers = requestMatchers; } + /// + /// Gets the request message matchers. + /// + /// Type of IRequestMatcher + /// A List{T} + public IList GetRequestMessageMatchers() where T : IRequestMatcher + { + return new ReadOnlyCollection(_requestMatchers.Where(rm => rm is T).Cast().ToList()); + } + + /// + /// Gets the request message matcher. + /// + /// Type of IRequestMatcher + /// A RequestMatcher + public T GetRequestMessageMatcher() where T : IRequestMatcher + { + return _requestMatchers.Where(rm => rm is T).Cast().FirstOrDefault(); + } + /// /// The with url. /// - /// The matcher. + /// The matchers. /// The . - public IUrlAndPathRequestBuilder WithUrl(IMatcher matcher) + public IUrlAndPathRequestBuilder WithUrl(params IMatcher[] matchers) { - _requestMatchers.Add(new RequestMessageUrlMatcher(matcher)); - + _requestMatchers.Add(new RequestMessageUrlMatcher(matchers)); return this; } @@ -51,9 +71,7 @@ namespace WireMock.RequestBuilders /// The . public IUrlAndPathRequestBuilder WithUrl(params string[] urls) { - var or = new RequestMessageCompositeMatcher(urls.Select(url => new RequestMessageUrlMatcher(url)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessageUrlMatcher(urls)); return this; } @@ -64,9 +82,7 @@ namespace WireMock.RequestBuilders /// The . public IUrlAndPathRequestBuilder WithUrl(params Func[] funcs) { - var or = new RequestMessageCompositeMatcher(funcs.Select(func => new RequestMessageUrlMatcher(func)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessageUrlMatcher(funcs)); return this; } @@ -75,10 +91,9 @@ namespace WireMock.RequestBuilders /// /// The matcher. /// The . - public IUrlAndPathRequestBuilder WithPath(IMatcher matcher) + public IUrlAndPathRequestBuilder WithPath(params IMatcher[] matcher) { _requestMatchers.Add(new RequestMessagePathMatcher(matcher)); - return this; } @@ -89,9 +104,7 @@ namespace WireMock.RequestBuilders /// The . public IUrlAndPathRequestBuilder WithPath(params string[] paths) { - var or = new RequestMessageCompositeMatcher(paths.Select(path => new RequestMessageUrlMatcher(path)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessagePathMatcher(paths)); return this; } @@ -102,9 +115,7 @@ namespace WireMock.RequestBuilders /// The . public IUrlAndPathRequestBuilder WithPath(params Func[] funcs) { - var or = new RequestMessageCompositeMatcher(funcs.Select(func => new RequestMessageUrlMatcher(func)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessagePathMatcher(funcs)); return this; } @@ -192,9 +203,7 @@ namespace WireMock.RequestBuilders /// The . public IHeadersAndCookiesRequestBuilder UsingVerb(params string[] verbs) { - var or = new RequestMessageCompositeMatcher(verbs.Select(verb => new RequestMessageVerbMatcher(verb)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessageVerbMatcher(verbs)); return this; } @@ -292,15 +301,11 @@ namespace WireMock.RequestBuilders /// /// The with parameters. /// - /// - /// The func. - /// - /// - /// The . - /// - public IRequestMatcher WithParam(Func>, bool> func) + /// The funcs. + /// The . + public IRequestMatcher WithParam(params Func>, bool>[] funcs) { - _requestMatchers.Add(new RequestMessageParamMatcher(func)); + _requestMatchers.Add(new RequestMessageParamMatcher(funcs)); return this; } @@ -324,9 +329,7 @@ namespace WireMock.RequestBuilders /// public IHeadersAndCookiesRequestBuilder WithHeader(params Func, bool>[] funcs) { - var or = new RequestMessageCompositeMatcher(funcs.Select(func => new RequestMessageHeaderMatcher(func)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessageHeaderMatcher(funcs)); return this; } @@ -350,9 +353,7 @@ namespace WireMock.RequestBuilders /// public IHeadersAndCookiesRequestBuilder WithCookie(params Func, bool>[] funcs) { - var or = new RequestMessageCompositeMatcher(funcs.Select(func => new RequestMessageCookieMatcher(func)), CompositeMatcherType.Or); - _requestMatchers.Add(or); - + _requestMatchers.Add(new RequestMessageCookieMatcher(funcs)); return this; } } diff --git a/src/WireMock/RequestMessage.cs b/src/WireMock/RequestMessage.cs index 39429eec..e9117f59 100644 --- a/src/WireMock/RequestMessage.cs +++ b/src/WireMock/RequestMessage.cs @@ -61,7 +61,7 @@ namespace WireMock /// The body string. /// The headers. /// The cookies. - public RequestMessage([NotNull] Uri url, [NotNull] string verb, [CanBeNull] byte[] bodyAsBytes, [CanBeNull] string body, [CanBeNull] IDictionary headers = null, [CanBeNull] IDictionary cookies = null) + public RequestMessage([NotNull] Uri url, [NotNull] string verb, [CanBeNull] byte[] bodyAsBytes = null, [CanBeNull] string body = null, [CanBeNull] IDictionary headers = null, [CanBeNull] IDictionary cookies = null) { Check.NotNull(url, nameof(url)); Check.NotNull(verb, nameof(verb)); @@ -85,16 +85,19 @@ namespace WireMock Query = query.Split('&').Aggregate( new Dictionary>(), (dict, term) => + { + var parts = term.Split('='); + var key = parts[0]; + if (!dict.ContainsKey(key)) { - var key = term.Split('=')[0]; - if (!dict.ContainsKey(key)) - { - dict.Add(key, new WireMockList()); - } + dict.Add(key, new WireMockList()); + } - dict[key].Add(term.Split('=')[1]); - return dict; - }); + if (parts.Length == 2) + dict[key].Add(parts[1]); + + return dict; + }); } } @@ -105,7 +108,7 @@ namespace WireMock /// The query parameter. public List GetParameter(string key) { - return Query.ContainsKey(key) ? Query[key] : new WireMockList(); + return Query.ContainsKey(key) ? Query[key] : null; } } } \ No newline at end of file diff --git a/src/WireMock/ResponseBuilders/IDelayResponseBuilder.cs b/src/WireMock/ResponseBuilders/IDelayResponseBuilder.cs index 11b48917..03804a15 100644 --- a/src/WireMock/ResponseBuilders/IDelayResponseBuilder.cs +++ b/src/WireMock/ResponseBuilders/IDelayResponseBuilder.cs @@ -5,7 +5,7 @@ namespace WireMock.ResponseBuilders /// /// The DelayResponseBuilder interface. /// - public interface IDelayResponseBuilder : IProvideResponses + public interface IDelayResponseBuilder : IResponseProvider { /// /// The after delay. diff --git a/src/WireMock/ResponseBuilders/Response.cs b/src/WireMock/ResponseBuilders/Response.cs index ff7095c0..98b5d2b2 100644 --- a/src/WireMock/ResponseBuilders/Response.cs +++ b/src/WireMock/ResponseBuilders/Response.cs @@ -14,19 +14,39 @@ namespace WireMock.ResponseBuilders /// public class Response : IResponseBuilder { - private readonly ResponseMessage _responseMessage; private TimeSpan _delay = TimeSpan.Zero; private bool _useTransformer; + /// + /// Gets the response message. + /// + /// + /// The response message. + /// + public ResponseMessage ResponseMessage { get; } + + /// + /// Creates this instance. + /// + /// ResponseMessage + /// A . + [PublicAPI] + public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null) + { + var message = responseMessage ?? new ResponseMessage { StatusCode = (int)HttpStatusCode.OK }; + return new Response(message); + } + /// /// Creates this instance. /// /// A . [PublicAPI] - public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null) + public static IResponseBuilder Create([NotNull] Func func) { - var message = responseMessage ?? new ResponseMessage { StatusCode = (int) HttpStatusCode.OK }; - return new Response(message); + Check.NotNull(func, nameof(func)); + + return new Response(func()); } /// @@ -37,7 +57,7 @@ namespace WireMock.ResponseBuilders /// private Response(ResponseMessage responseMessage) { - _responseMessage = responseMessage; + ResponseMessage = responseMessage; } /// @@ -48,7 +68,7 @@ namespace WireMock.ResponseBuilders [PublicAPI] public IHeadersResponseBuilder WithStatusCode(int code) { - _responseMessage.StatusCode = code; + ResponseMessage.StatusCode = code; return this; } @@ -60,7 +80,7 @@ namespace WireMock.ResponseBuilders [PublicAPI] public IHeadersResponseBuilder WithStatusCode(HttpStatusCode code) { - return WithStatusCode((int) code); + return WithStatusCode((int)code); } /// @@ -70,7 +90,7 @@ namespace WireMock.ResponseBuilders [PublicAPI] public IHeadersResponseBuilder WithSuccess() { - return WithStatusCode((int) HttpStatusCode.OK); + return WithStatusCode((int)HttpStatusCode.OK); } /// @@ -93,7 +113,7 @@ namespace WireMock.ResponseBuilders { Check.NotNull(name, nameof(name)); - _responseMessage.AddHeader(name, value); + ResponseMessage.AddHeader(name, value); return this; } @@ -106,7 +126,7 @@ namespace WireMock.ResponseBuilders { Check.NotNull(body, nameof(body)); - _responseMessage.Body = body; + ResponseMessage.Body = body; return this; } @@ -120,7 +140,7 @@ namespace WireMock.ResponseBuilders { Check.NotNull(bodyAsbase64, nameof(bodyAsbase64)); - _responseMessage.Body = (encoding ?? Encoding.UTF8).GetString(Convert.FromBase64String(bodyAsbase64)); + ResponseMessage.Body = (encoding ?? Encoding.UTF8).GetString(Convert.FromBase64String(bodyAsbase64)); return this; } @@ -143,7 +163,7 @@ namespace WireMock.ResponseBuilders /// The delay. /// /// - /// The . + /// The . /// public IResponseBuilder WithDelay(TimeSpan delay) { @@ -162,29 +182,36 @@ namespace WireMock.ResponseBuilders /// public async Task ProvideResponse(RequestMessage requestMessage) { + ResponseMessage responseMessage; if (_useTransformer) { + responseMessage = new ResponseMessage { StatusCode = ResponseMessage.StatusCode }; + var template = new { request = requestMessage }; // Body - var templateBody = Handlebars.Compile(_responseMessage.Body); - _responseMessage.Body = templateBody(template); + var templateBody = Handlebars.Compile(ResponseMessage.Body); + responseMessage.Body = templateBody(template); // Headers var newHeaders = new Dictionary(); - foreach (var header in _responseMessage.Headers) + foreach (var header in ResponseMessage.Headers) { var templateHeaderKey = Handlebars.Compile(header.Key); var templateHeaderValue = Handlebars.Compile(header.Value); newHeaders.Add(templateHeaderKey(template), templateHeaderValue(template)); } - _responseMessage.Headers = newHeaders; + responseMessage.Headers = newHeaders; + } + else + { + responseMessage = ResponseMessage; } await Task.Delay(_delay); - return _responseMessage; + return responseMessage; } } } \ No newline at end of file diff --git a/src/WireMock/RouteRegistrationCallback.cs b/src/WireMock/RouteRegistrationCallback.cs index bc10b3a8..ad9d79a2 100644 --- a/src/WireMock/RouteRegistrationCallback.cs +++ b/src/WireMock/RouteRegistrationCallback.cs @@ -3,8 +3,8 @@ /// /// The registration callback. /// - /// + /// /// The route. /// - public delegate void RegistrationCallback(Route route); + public delegate void RegistrationCallback(Mapping mapping); } \ No newline at end of file diff --git a/src/WireMock/Server/FluentMockServer.Admin.cs b/src/WireMock/Server/FluentMockServer.Admin.cs new file mode 100644 index 00000000..fd6e4615 --- /dev/null +++ b/src/WireMock/Server/FluentMockServer.Admin.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Newtonsoft.Json; +using WireMock.Admin; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; + +namespace WireMock.Server +{ + /// + /// The fluent mock server. + /// + public partial class FluentMockServer + { + private void InitAdmin() + { + Given(Request.Create().WithUrl("/__admin/mappings").UsingGet()).RespondWith(new DynamicResponseProvider(MappingsGet)); + } + + private ResponseMessage MappingsGet() + { + var result = new List(); + foreach (var mapping in Mappings.Where(m => !(m.Provider is DynamicResponseProvider))) + { + var request = (Request) mapping.RequestMatcher; + var urlMatchers = request.GetRequestMessageMatchers(); + var headerMatchers = request.GetRequestMessageMatchers(); + var cookieMatchers = request.GetRequestMessageMatchers(); + var paramsMatchers = request.GetRequestMessageMatchers(); + var bodyMatcher = request.GetRequestMessageMatcher(); + var verbMatcher = request.GetRequestMessageMatcher(); + + var response = (Response) mapping.Provider; + + var model = new MappingModel + { + Guid = Guid.NewGuid(), + Request = new RequestModel + { + Url = new UrlModel + { + Matchers = urlMatchers != null ? Map(urlMatchers.Where(m => m.Matchers != null).SelectMany(m => m.Matchers)) : null + }, + Verbs = verbMatcher != null ? verbMatcher.Verbs : new [] { "any" }, + Headers = headerMatchers?.Select(hm => new HeaderModel + { + Name = hm.Name, + Matchers = Map(hm.Matchers) + }).ToList(), + Cookies = cookieMatchers?.Select(hm => new CookieModel + { + Name = hm.Name, + Matchers = Map(hm.Matchers) + }).ToList(), + Params = paramsMatchers?.Select(hm => new ParamModel + { + Name = hm.Key, + Values = hm.Values?.ToList() + }).ToList(), + Body = new BodyModel + { + Matcher = bodyMatcher != null ? Map(bodyMatcher.Matcher) : null + } + }, + Response = new ResponseModel + { + StatusCode = response.ResponseMessage.StatusCode, + Headers = response.ResponseMessage.Headers, + Body = response.ResponseMessage.Body + } + }; + + result.Add(model); + } + + return ToJson(result); + } + + private IList Map([CanBeNull] IEnumerable matchers) + { + return matchers?.Select(Map).Where(x => x != null).ToList(); + } + + private MatcherModel Map([CanBeNull] IMatcher matcher) + { + if (matcher == null) + return null; + + return new MatcherModel + { + Name = matcher.GetType().Name, + Pattern = matcher.GetPattern() + }; + } + + private ResponseMessage ToJson(T result) + { + return new ResponseMessage + { + Body = JsonConvert.SerializeObject(result, Formatting.Indented), + StatusCode = 200, + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + } + } +} \ No newline at end of file diff --git a/src/WireMock/Server/FluentMockServer.cs b/src/WireMock/Server/FluentMockServer.cs index bb4a379e..dc6172dc 100644 --- a/src/WireMock/Server/FluentMockServer.cs +++ b/src/WireMock/Server/FluentMockServer.cs @@ -16,7 +16,7 @@ namespace WireMock.Server /// /// The fluent mock server. /// - public class FluentMockServer + public partial class FluentMockServer { /// /// The _http server. @@ -24,9 +24,9 @@ namespace WireMock.Server private readonly TinyHttpServer _httpServer; /// - /// The _routes. + /// The _mappings. /// - private readonly IList _routes = new List(); + private readonly IList _mappings = new List(); /// /// The _request logs. @@ -75,13 +75,13 @@ namespace WireMock.Server /// /// Gets the routes. /// - public IEnumerable Routes + public IEnumerable Mappings { get { - lock (((ICollection)_routes).SyncRoot) + lock (((ICollection)_mappings).SyncRoot) { - return new ReadOnlyCollection(_routes); + return new ReadOnlyCollection(_mappings); } } } @@ -89,15 +89,9 @@ namespace WireMock.Server /// /// Start this FluentMockServer. /// - /// - /// The port. - /// - /// - /// The SSL support. - /// - /// - /// The . - /// + /// The port. + /// The SSL support. + /// The . [PublicAPI] public static FluentMockServer Start(int port = 0, bool ssl = false) { @@ -106,24 +100,37 @@ namespace WireMock.Server if (port == 0) port = Ports.FindFreeTcpPort(); - return new FluentMockServer(port, ssl); + return new FluentMockServer(false, port, ssl); } /// - /// Initializes a new instance of the class, and starts the server. + /// Start this FluentMockServer with the admin interface. /// - /// - /// The port. - /// - /// - /// The SSL support. - /// - private FluentMockServer(int port, bool ssl) + /// The port. + /// The SSL support. + /// The . + [PublicAPI] + public static FluentMockServer StartWithAdminInterface(int port = 0, bool ssl = false) + { + Check.Condition(port, p => p >= 0, nameof(port)); + + if (port == 0) + port = Ports.FindFreeTcpPort(); + + return new FluentMockServer(true, port, ssl); + } + + private FluentMockServer(bool startAdmin, int port, bool ssl) { string protocol = ssl ? "https" : "http"; - _httpServer = new TinyHttpServer(protocol + "://localhost:" + port + "/", HandleRequest); + _httpServer = new TinyHttpServer(protocol + "://localhost:" + port + "/", HandleRequestAsync); Port = port; _httpServer.Start(); + + if (startAdmin) + { + InitAdmin(); + } } /// @@ -144,9 +151,9 @@ namespace WireMock.Server _requestLogs.Clear(); } - lock (((ICollection)_routes).SyncRoot) + lock (((ICollection)_mappings).SyncRoot) { - _routes.Clear(); + _mappings.Clear(); } } @@ -184,28 +191,24 @@ namespace WireMock.Server /// /// The given. /// - /// - /// The request matcher. - /// - /// - /// The . - /// + /// The request matcher. + /// The . public IRespondWithAProvider Given(IRequestMatcher requestMatcher) { - return new RespondWithAProvider(RegisterRoute, requestMatcher); + return new RespondWithAProvider(RegisterMapping, requestMatcher); } /// - /// The register route. + /// The register mapping. /// - /// - /// The route. + /// + /// The mapping. /// - private void RegisterRoute(Route route) + private void RegisterMapping(Mapping mapping) { - lock (((ICollection)_routes).SyncRoot) + lock (((ICollection)_mappings).SyncRoot) { - _routes.Add(route); + _mappings.Add(mapping); } } @@ -226,10 +229,8 @@ namespace WireMock.Server /// /// The handle request. /// - /// - /// The context. - /// - private async void HandleRequest(HttpListenerContext ctx) + /// The HttpListenerContext. + private async void HandleRequestAsync(HttpListenerContext ctx) { lock (_syncRoot) { @@ -241,7 +242,7 @@ namespace WireMock.Server try { - var targetRoute = _routes.FirstOrDefault(route => route.IsRequestHandled(request)); + var targetRoute = _mappings.FirstOrDefault(route => route.IsRequestHandled(request)); if (targetRoute == null) { ctx.Response.StatusCode = 404; diff --git a/src/WireMock/Server/IRespondWithAProvider.cs b/src/WireMock/Server/IRespondWithAProvider.cs index 2c575f72..51439006 100644 --- a/src/WireMock/Server/IRespondWithAProvider.cs +++ b/src/WireMock/Server/IRespondWithAProvider.cs @@ -11,6 +11,6 @@ /// /// The provider. /// - void RespondWith(IProvideResponses provider); + void RespondWith(IResponseProvider provider); } } \ No newline at end of file diff --git a/src/WireMock/Server/RespondWithAProvider.cs b/src/WireMock/Server/RespondWithAProvider.cs index 0ea517f2..2cb78aad 100644 --- a/src/WireMock/Server/RespondWithAProvider.cs +++ b/src/WireMock/Server/RespondWithAProvider.cs @@ -15,21 +15,17 @@ namespace WireMock.Server /// /// The _request matcher. /// - private readonly IRequestMatcher _requestSpec; + private readonly IRequestMatcher _requestMatcher; /// /// Initializes a new instance of the class. /// - /// - /// The registration callback. - /// - /// - /// The request matcher. - /// - public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestSpec) + /// The registration callback. + /// The request matcher. + public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher) { _registrationCallback = registrationCallback; - _requestSpec = requestSpec; + _requestMatcher = requestMatcher; } /// @@ -38,9 +34,9 @@ namespace WireMock.Server /// /// The provider. /// - public void RespondWith(IProvideResponses provider) + public void RespondWith(IResponseProvider provider) { - _registrationCallback(new Route(_requestSpec, provider)); + _registrationCallback(new Mapping(_requestMatcher, provider)); } } } \ No newline at end of file diff --git a/src/WireMock/Util/WireMockList.cs b/src/WireMock/Util/WireMockList.cs index 4f84d8b1..73f4747a 100644 --- a/src/WireMock/Util/WireMockList.cs +++ b/src/WireMock/Util/WireMockList.cs @@ -17,6 +17,14 @@ namespace WireMock.Util { } + /// + /// Initializes a new instance of the class. + /// + /// The collection whose elements are copied to the new list. + public WireMockList(params T[] collection) : base(collection) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/src/WireMock/project.json b/src/WireMock/project.json index 7c85d66a..2cc1256e 100644 --- a/src/WireMock/project.json +++ b/src/WireMock/project.json @@ -6,13 +6,14 @@ "packOptions": { "summary": "Lightweigth Http Mocking Server for .Net, inspired by WireMock from the Java landscape.", - "tags": [ "system", "linq", "dynamic", "core" ], + "tags": [ "tdd", "mock", "http", "wiremock", "test" ], "owners": [ "Stef Heyenrath" ], "repository": { "type": "git", "url": "https://github.com/StefH/WireMock.Net" }, "projectUrl": "https://github.com/StefH/WireMock.Net", + "iconUrl": "https://raw.githubusercontent.com/StefH/WireMock.Net/master/WireMock.Net-Logo.png", "licenseUrl": "https://raw.githubusercontent.com/StefH/WireMock.Net/master/LICENSE", "releaseNotes": "First version" }, diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs index c3b720f2..b27786d8 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs @@ -30,7 +30,7 @@ namespace WireMock.Net.Tests _server.Given(Request.Create().WithUrl("/foo2").UsingGet()) .RespondWith(Response.Create().WithStatusCode(202).WithBody("2")); - var routes = _server.Routes; + var routes = _server.Mappings; Check.That(routes).HasSize(2); Check.That(routes.First().RequestMatcher).IsNotNull(); diff --git a/test/WireMock.Net.Tests/RequestMessageTests.cs b/test/WireMock.Net.Tests/RequestMessageTests.cs index 9d8e0547..2e74c704 100644 --- a/test/WireMock.Net.Tests/RequestMessageTests.cs +++ b/test/WireMock.Net.Tests/RequestMessageTests.cs @@ -12,12 +12,10 @@ namespace WireMock.Net.Tests public void Should_handle_empty_query() { // given - string bodyAsString = "whatever"; - byte[] body = Encoding.UTF8.GetBytes(bodyAsString); - var request = new RequestMessage(new Uri("http://localhost/foo"), "POST", body, bodyAsString); + var request = new RequestMessage(new Uri("http://localhost/foo"), "POST"); // then - Check.That(request.GetParameter("foo")).IsEmpty(); + Check.That(request.GetParameter("not_there")).IsNull(); } [Test] diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestTests.cs index 79cc0d0f..fb1660c1 100644 --- a/test/WireMock.Net.Tests/RequestTests.cs +++ b/test/WireMock.Net.Tests/RequestTests.cs @@ -40,6 +40,19 @@ namespace WireMock.Net.Tests Check.That(requestBuilder.IsMatch(request2)).IsTrue(); } + [Test] + public void Should_specify_requests_matching_given_urlFuncs() + { + // given + var spec = Request.Create().WithUrl(url => url.EndsWith("/foo")); + + // when + var request = new RequestMessage(new Uri("http://localhost/foo"), "blabla"); + + // then + Check.That(spec.IsMatch(request)).IsTrue(); + } + [Test] public void Should_specify_requests_matching_given_url_prefix() { @@ -394,7 +407,7 @@ namespace WireMock.Net.Tests } [Test] - public void Should_specify_requests_matching_given_params() + public void Should_specify_requests_matching_given_param() { // given var spec = Request.Create().WithPath("/foo").WithParam("bar", "1", "2"); @@ -409,7 +422,20 @@ namespace WireMock.Net.Tests } [Test] - public void Should_specify_requests_matching_given_params_func() + public void Should_specify_requests_matching_given_paramNoValue() + { + // given + var spec = Request.Create().WithPath("/foo").WithParam("bar"); + + // when + var request = new RequestMessage(new Uri("http://localhost/foo?bar"), "PUT"); + + // then + Check.That(spec.IsMatch(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_param_func() { // given var spec = Request.Create().WithPath("/foo").UsingAnyVerb().WithParam(p => p.ContainsKey("bar"));