diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs index d34c5650..e312b7df 100644 --- a/examples/WireMock.Net.ConsoleApplication/Program.cs +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -18,17 +18,26 @@ namespace WireMock.Net.ConsoleApplication Console.WriteLine("FluentMockServer running at {0}", server.Port); server - .Given( - Request - .WithUrl("/*") - .UsingGet() - ) - .RespondWith( - Response - .WithStatusCode(200) - .WithHeader("Content-Type", "application/json") - .WithBody(@"{ ""msg"": ""Hello world!""}") - ); + .Given(Request.WithUrl(u => u.Contains("x")).UsingGet()) + .RespondWith(Response + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""/x with FUNC 200""}")); + + server + .Given(Request.WithUrl("/*").UsingGet()) + .RespondWith(Response + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""msg"": ""Hello world!""}") + ); + + server + .Given(Request.WithUrl("/data").UsingPost().WithBody(b => b.Contains("e"))) + .RespondWith(Response + .WithStatusCode(201) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""result"": ""data posted with FUNC 201""}")); server .Given(Request.WithUrl("/data").UsingPost()) @@ -42,12 +51,11 @@ namespace WireMock.Net.ConsoleApplication .RespondWith(Response .WithStatusCode(200) .WithHeader("Content-Type", "application/json") - .WithBody(@"{ ""result"": ""data deleted with 201""}")); + .WithBody(@"{ ""result"": ""data deleted with 200""}")); Console.WriteLine("Press any key to stop the server"); Console.ReadKey(); - Console.WriteLine("Displaying all requests"); var allRequests = server.RequestLogs; Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented)); diff --git a/src/WireMock/Http/Ports.cs b/src/WireMock/Http/Ports.cs index 4d5c2e1e..4fa6d268 100644 --- a/src/WireMock/Http/Ports.cs +++ b/src/WireMock/Http/Ports.cs @@ -1,16 +1,6 @@ -using System.Diagnostics.CodeAnalysis; -using System.Net; +using System.Net; using System.Net.Sockets; -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1633:FileMustHaveHeader", - Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1650:ElementDocumentationMustBeSpelledCorrectly", - Justification = "Reviewed. Suppression is OK here.")] - namespace WireMock.Http { /// @@ -24,15 +14,21 @@ namespace WireMock.Http /// /// The . /// - /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. - // ReSharper disable once StyleCop.SA1650 + /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. public static int FindFreeTcpPort() { - TcpListener l = new TcpListener(IPAddress.Loopback, 0); - l.Start(); - int port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; + TcpListener tcpListener = null; + try + { + tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); + + return ((IPEndPoint)tcpListener.LocalEndpoint).Port; + } + finally + { + tcpListener?.Stop(); + } } } -} +} \ No newline at end of file diff --git a/src/WireMock/Http/TinyHttpServer.cs b/src/WireMock/Http/TinyHttpServer.cs index 23f3bcbe..38b1fa26 100644 --- a/src/WireMock/Http/TinyHttpServer.cs +++ b/src/WireMock/Http/TinyHttpServer.cs @@ -53,13 +53,6 @@ namespace WireMock.Http { _httpHandler = httpHandler; - // .Net Framework is not supportted on XP or Server 2003, so no need for the check - /*if (!HttpListener.IsSupported) - { - Console.WriteLine("Windows XP SP2 or Server 2003 is required to use the HttpListener class."); - return; - }*/ - // Create a listener. _listener = new HttpListener(); _listener.Prefixes.Add(urlPrefix); diff --git a/src/WireMock/RequestBodySpec.cs b/src/WireMock/RequestBodySpec.cs index 5dc7b60f..4e4f5daf 100644 --- a/src/WireMock/RequestBodySpec.cs +++ b/src/WireMock/RequestBodySpec.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using JetBrains.Annotations; @@ -29,6 +30,11 @@ namespace WireMock /// private readonly Regex bodyRegex; + /// + /// The body function + /// + private readonly Func bodyFunc; + /// /// Initializes a new instance of the class. /// @@ -41,6 +47,18 @@ namespace WireMock bodyRegex = new Regex(body); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The body func. + /// + public RequestBodySpec([NotNull] Func func) + { + Check.NotNull(func, nameof(func)); + bodyFunc = func; + } + /// /// The is satisfied by. /// @@ -52,7 +70,7 @@ namespace WireMock /// public bool IsSatisfiedBy(RequestMessage requestMessage) { - return bodyRegex.IsMatch(requestMessage.Body); + return bodyRegex?.IsMatch(requestMessage.Body) ?? bodyFunc(requestMessage.Body); } } -} +} \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs index b1134a3c..bd7f6504 100644 --- a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs @@ -1,4 +1,6 @@ -namespace WireMock.RequestBuilders +using System; + +namespace WireMock.RequestBuilders { /// /// The BodyRequestBuilder interface. @@ -15,5 +17,16 @@ /// The . /// ISpecifyRequests WithBody(string body); + + /// + /// The with body. + /// + /// + /// The body function. + /// + /// + /// The . + /// + ISpecifyRequests WithBody(Func body); } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IHeadersRequestBuilder.cs b/src/WireMock/RequestBuilders/IHeadersRequestBuilder.cs index 7595f30a..2de9e2e5 100644 --- a/src/WireMock/RequestBuilders/IHeadersRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IHeadersRequestBuilder.cs @@ -1,4 +1,8 @@ -namespace WireMock.RequestBuilders +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace WireMock.RequestBuilders { /// /// The HeadersRequestBuilder interface. @@ -19,5 +23,16 @@ /// The . /// IHeadersRequestBuilder WithHeader(string name, string value, bool ignoreCase = true); + + /// + /// The with header. + /// + /// + /// The headers func. + /// + /// + /// The . + /// + IHeadersRequestBuilder WithHeader([NotNull] Func, bool> func); } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs b/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs index 2a639694..40a8f684 100644 --- a/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IParamsRequestBuilder.cs @@ -1,4 +1,8 @@ -namespace WireMock.RequestBuilders +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace WireMock.RequestBuilders { /// /// The ParametersRequestBuilder interface. @@ -17,6 +21,17 @@ /// /// The . /// - ISpecifyRequests WithParam(string key, params string[] values); + ISpecifyRequests WithParam([NotNull] string key, params string[] values); + + /// + /// The with parameters. + /// + /// + /// The func. + /// + /// + /// The . + /// + ISpecifyRequests WithParam([NotNull] Func>, bool> func); } } \ No newline at end of file diff --git a/src/WireMock/RequestBuilders/Request.cs b/src/WireMock/RequestBuilders/Request.cs index d0e23098..76a4d177 100644 --- a/src/WireMock/RequestBuilders/Request.cs +++ b/src/WireMock/RequestBuilders/Request.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using JetBrains.Annotations; [module: SuppressMessage("StyleCop.CSharp.ReadabilityRules", @@ -54,10 +56,25 @@ namespace WireMock.RequestBuilders /// public static IVerbRequestBuilder WithUrl(string url) { - var specs = new List(); - var requests = new Request(specs); - specs.Add(new RequestUrlSpec(url)); - return requests; + var specs = new List { new RequestUrlSpec(url) }; + + return new Request(specs); + } + + /// + /// The with url. + /// + /// + /// The url func. + /// + /// + /// The . + /// + public static IVerbRequestBuilder WithUrl(Func func) + { + var specs = new List { new RequestUrlSpec(func) }; + + return new Request(specs); } /// @@ -71,10 +88,25 @@ namespace WireMock.RequestBuilders /// public static IVerbRequestBuilder WithPath(string path) { - var specs = new List(); - var requests = new Request(specs); - specs.Add(new RequestPathSpec(path)); - return requests; + var specs = new List { new RequestPathSpec(path) }; + + return new Request(specs); + } + + /// + /// The with path. + /// + /// + /// The path func. + /// + /// + /// The . + /// + public static IVerbRequestBuilder WithPath([NotNull] Func func) + { + var specs = new List { new RequestPathSpec(func) }; + + return new Request(specs); } /// @@ -178,6 +210,21 @@ namespace WireMock.RequestBuilders return this; } + /// + /// The with body. + /// + /// + /// The body function. + /// + /// + /// The . + /// + public ISpecifyRequests WithBody(Func func) + { + _requestSpecs.Add(new RequestBodySpec(func)); + return this; + } + /// /// The with parameters. /// @@ -196,6 +243,21 @@ namespace WireMock.RequestBuilders return this; } + /// + /// The with parameters. + /// + /// + /// The func. + /// + /// + /// The . + /// + public ISpecifyRequests WithParam(Func>, bool> func) + { + _requestSpecs.Add(new RequestParamSpec(func)); + return this; + } + /// /// The with header. /// @@ -214,5 +276,20 @@ namespace WireMock.RequestBuilders _requestSpecs.Add(new RequestHeaderSpec(name, value, ignoreCase)); return this; } + + /// + /// The with header. + /// + /// + /// The func. + /// + /// + /// The . + /// + public IHeadersRequestBuilder WithHeader(Func, bool> func) + { + _requestSpecs.Add(new RequestHeaderSpec(func)); + return this; + } } } diff --git a/src/WireMock/RequestHeaderSpec.cs b/src/WireMock/RequestHeaderSpec.cs index 43e70172..b8bf9e47 100644 --- a/src/WireMock/RequestHeaderSpec.cs +++ b/src/WireMock/RequestHeaderSpec.cs @@ -1,6 +1,9 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using JetBrains.Annotations; +using WireMock.Validation; [module: SuppressMessage("StyleCop.CSharp.ReadabilityRules", @@ -33,6 +36,11 @@ namespace WireMock /// private readonly Regex patternRegex; + /// + /// The header function + /// + private readonly Func, bool> headerFunc; + /// /// Initializes a new instance of the class. /// @@ -49,6 +57,18 @@ namespace WireMock patternRegex = ignoreCase ? new Regex(pattern, RegexOptions.IgnoreCase) : new Regex(pattern); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The func. + /// + public RequestHeaderSpec([NotNull] Func, bool> func) + { + Check.NotNull(func, nameof(func)); + headerFunc = func; + } + /// /// The is satisfied by. /// @@ -58,8 +78,11 @@ namespace WireMock /// /// The . /// - public bool IsSatisfiedBy([NotNull] RequestMessage requestMessage) + public bool IsSatisfiedBy(RequestMessage requestMessage) { + if (patternRegex == null) + return headerFunc(requestMessage.Headers); + string headerValue = requestMessage.Headers[name]; return patternRegex.IsMatch(headerValue); } diff --git a/src/WireMock/RequestMessage.cs b/src/WireMock/RequestMessage.cs index 2ac15c19..224b5152 100644 --- a/src/WireMock/RequestMessage.cs +++ b/src/WireMock/RequestMessage.cs @@ -18,8 +18,7 @@ using System.Linq; SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] -// ReSharper disable ArrangeThisQualifier -// ReSharper disable InconsistentNaming + namespace WireMock { /// @@ -30,7 +29,7 @@ namespace WireMock /// /// The _params. /// - private readonly Dictionary> _params = new Dictionary>(); + private readonly IDictionary> _params = new Dictionary>(); /// /// Initializes a new instance of the class. @@ -50,7 +49,7 @@ namespace WireMock /// /// The headers. /// - public RequestMessage(string path, string query, string verb, string body, IDictionary headers) + public RequestMessage(string path, string query, string verb, string body, IDictionary headers = null) { if (!string.IsNullOrEmpty(query)) { @@ -72,12 +71,14 @@ namespace WireMock dict[key].Add(term.Split('=')[1]); return dict; }); + + Parameters = _params; } Path = path; - Headers = headers; //.ToDictionary(kv => kv.Key.ToLower(), kv => kv.Value.ToLower()); + Headers = headers; Verb = verb.ToLower(); - Body = body?.Trim() ?? string.Empty; + Body = body; } /// @@ -111,6 +112,11 @@ namespace WireMock /// public IDictionary Headers { get; } + /// + /// Gets the parameters. + /// + public IDictionary> Parameters { get; } + /// /// Gets the body. /// @@ -130,4 +136,4 @@ namespace WireMock return _params.ContainsKey(key) ? _params[key] : new List(); } } -} +} \ No newline at end of file diff --git a/src/WireMock/RequestParamSpec.cs b/src/WireMock/RequestParamSpec.cs index 065f6658..e1bc2100 100644 --- a/src/WireMock/RequestParamSpec.cs +++ b/src/WireMock/RequestParamSpec.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using JetBrains.Annotations; +using WireMock.Validation; [module: SuppressMessage("StyleCop.CSharp.ReadabilityRules", @@ -15,8 +17,7 @@ using JetBrains.Annotations; SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:FileMustHaveHeader", Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] -// ReSharper disable ArrangeThisQualifier -// ReSharper disable InconsistentNaming + namespace WireMock { /// @@ -32,7 +33,9 @@ namespace WireMock /// /// The _values. /// - private readonly List _values; + private readonly IEnumerable _values; + + private readonly Func>, bool> _func; /// /// Initializes a new instance of the class. @@ -43,12 +46,27 @@ namespace WireMock /// /// The values. /// - public RequestParamSpec(string key, List values) + public RequestParamSpec([NotNull] string key, [NotNull] IEnumerable values) { + Check.NotNull(key, nameof(key)); + Check.NotNull(values, nameof(values)); + _key = key; _values = values; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The func. + /// + public RequestParamSpec([NotNull] Func>, bool> func) + { + Check.NotNull(func, nameof(func)); + _func = func; + } + /// /// The is satisfied by. /// @@ -58,9 +76,14 @@ namespace WireMock /// /// The . /// - public bool IsSatisfiedBy([NotNull] RequestMessage requestMessage) + public bool IsSatisfiedBy(RequestMessage requestMessage) { - return requestMessage.GetParameter(_key).Intersect(_values).Count() == _values.Count; + if (_func != null) + { + return _func(requestMessage.Parameters); + } + + return requestMessage.GetParameter(_key).Intersect(_values).Count() == _values.Count(); } } -} +} \ No newline at end of file diff --git a/src/WireMock/RequestPathSpec.cs b/src/WireMock/RequestPathSpec.cs index c1e56f60..6a83848e 100644 --- a/src/WireMock/RequestPathSpec.cs +++ b/src/WireMock/RequestPathSpec.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using JetBrains.Annotations; using WireMock.Validation; @@ -15,8 +16,7 @@ using WireMock.Validation; SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:FileMustHaveHeader", Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] -// ReSharper disable ArrangeThisQualifier -// ReSharper disable InconsistentNaming + namespace WireMock { /// @@ -25,9 +25,14 @@ namespace WireMock public class RequestPathSpec : ISpecifyRequests { /// - /// The _path. + /// The pathRegex. /// - private readonly Regex _path; + private readonly Regex pathRegex; + + /// + /// The url function + /// + private readonly Func pathFunc; /// /// Initializes a new instance of the class. @@ -38,7 +43,19 @@ namespace WireMock public RequestPathSpec([NotNull, RegexPattern] string path) { Check.NotNull(path, nameof(path)); - _path = new Regex(path); + pathRegex = new Regex(path); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The url func. + /// + public RequestPathSpec([NotNull] Func func) + { + Check.NotNull(func, nameof(func)); + pathFunc = func; } /// @@ -50,9 +67,9 @@ namespace WireMock /// /// The . /// - public bool IsSatisfiedBy([NotNull] RequestMessage requestMessage) + public bool IsSatisfiedBy(RequestMessage requestMessage) { - return _path.IsMatch(requestMessage.Path); + return pathRegex?.IsMatch(requestMessage.Path) ?? pathFunc(requestMessage.Path); } } -} +} \ No newline at end of file diff --git a/src/WireMock/RequestUrlSpec.cs b/src/WireMock/RequestUrlSpec.cs index e0abe499..06f8d981 100644 --- a/src/WireMock/RequestUrlSpec.cs +++ b/src/WireMock/RequestUrlSpec.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using System.Text.RegularExpressions; using WireMock.Validation; @@ -29,6 +30,11 @@ namespace WireMock /// private readonly Regex urlRegex; + /// + /// The url function + /// + private readonly Func urlFunc; + /// /// Initializes a new instance of the class. /// @@ -41,6 +47,18 @@ namespace WireMock urlRegex = new Regex(url); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The url func. + /// + public RequestUrlSpec(Func func) + { + Check.NotNull(func, nameof(func)); + urlFunc = func; + } + /// /// The is satisfied by. /// @@ -52,7 +70,7 @@ namespace WireMock /// public bool IsSatisfiedBy(RequestMessage requestMessage) { - return urlRegex.IsMatch(requestMessage.Url); + return urlRegex?.IsMatch(requestMessage.Url) ?? urlFunc(requestMessage.Url); } } -} +} \ No newline at end of file diff --git a/src/WireMock/ResponseMessage.cs b/src/WireMock/ResponseMessage.cs index 24963cbe..4836aa17 100644 --- a/src/WireMock/ResponseMessage.cs +++ b/src/WireMock/ResponseMessage.cs @@ -28,16 +28,6 @@ namespace WireMock /// private readonly IDictionary _headers = new ConcurrentDictionary(); - /// - /// The status code. - /// - private volatile int statusCode = 200; - - /// - /// The body. - /// - private volatile string body; - /// /// Gets the headers. /// @@ -46,34 +36,12 @@ namespace WireMock /// /// Gets or sets the status code. /// - public int StatusCode - { - get - { - return statusCode; - } - - set - { - statusCode = value; - } - } + public int StatusCode { get; set; } = 200; /// /// Gets or sets the body. /// - public string Body - { - get - { - return body; - } - - set - { - body = value; - } - } + public string Body { get; set; } /// /// The add header. diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs index 2c15b19a..05f85155 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs @@ -86,7 +86,7 @@ namespace WireMock.Net.Tests Check.That(_server.RequestLogs).HasSize(1); var requestLogged = _server.RequestLogs.First(); Check.That(requestLogged.Verb).IsEqualTo("get"); - Check.That(requestLogged.Body).IsEmpty(); + Check.That(requestLogged.Body).IsNull(); } [Test] diff --git a/test/WireMock.Net.Tests/RequestsTests.cs b/test/WireMock.Net.Tests/RequestsTests.cs index 9d138923..d754f90a 100644 --- a/test/WireMock.Net.Tests/RequestsTests.cs +++ b/test/WireMock.Net.Tests/RequestsTests.cs @@ -265,6 +265,19 @@ namespace WireMock.Net.Tests Check.That(spec.IsSatisfiedBy(request)).IsTrue(); } + [Test] + public void Should_specify_requests_matching_given_params_func() + { + // given + var spec = Request.WithPath("/foo").WithParam(p => p.ContainsKey("bar") && (p["bar"].Contains("1") || p["bar"].Contains("2"))); + + // when + var request = new RequestMessage("/foo", "bar=1&bar=2", "Get", "Hello world!", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + [Test] public void Should_exclude_requests_not_matching_given_params() {