Rename to WireMock.Net

This commit is contained in:
Stef Heyenrath
2017-01-24 22:28:08 +01:00
parent 4809fed513
commit 3cb1a6d2e1
65 changed files with 5 additions and 5 deletions

View File

@@ -0,0 +1,16 @@
namespace WireMock.Admin
{
/// <summary>
/// Body Model
/// </summary>
public class BodyModel
{
/// <summary>
/// Gets or sets the matcher.
/// </summary>
/// <value>
/// The matcher.
/// </value>
public MatcherModel Matcher { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// Cookie Model
/// </summary>
public class CookieModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IList<MatcherModel> Matchers { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
//namespace WireMock.Admin
//{
// public class FuncModel
// {
// public string Name { get; set; }
// }
//}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// Header Model
/// </summary>
public class HeaderModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IList<MatcherModel> Matchers { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System;
namespace WireMock.Admin
{
/// <summary>
/// MappingModel
/// </summary>
public class MappingModel
{
/// <summary>
/// Gets or sets the unique identifier.
/// </summary>
/// <value>
/// The unique identifier.
/// </value>
public Guid Guid { get; set; }
/// <summary>
/// Gets or sets the request.
/// </summary>
/// <value>
/// The request.
/// </value>
public RequestModel Request { get; set; }
/// <summary>
/// Gets or sets the response.
/// </summary>
/// <value>
/// The response.
/// </value>
public ResponseModel Response { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace WireMock.Admin
{
/// <summary>
/// MatcherModel
/// </summary>
public class MatcherModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the pattern.
/// </summary>
/// <value>
/// The pattern.
/// </value>
public string Pattern { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// Param Model
/// </summary>
public class ParamModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the values.
/// </summary>
/// <value>
/// The values.
/// </value>
public IList<string> Values { get; set; }
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// RequestModel
/// </summary>
public class RequestModel
{
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>
/// The URL.
/// </value>
public UrlModel Url { get; set; }
/// <summary>
/// The verbs
/// </summary>
public string[] Verbs { get; set; }
/// <summary>
/// Gets or sets the Headers.
/// </summary>
/// <value>
/// The Headers.
/// </value>
public IList<HeaderModel> Headers { get; set; }
/// <summary>
/// Gets or sets the Cookies.
/// </summary>
/// <value>
/// The Cookies.
/// </value>
public IList<CookieModel> Cookies { get; set; }
/// <summary>
/// Gets or sets the Params.
/// </summary>
/// <value>
/// The Headers.
/// </value>
public IList<ParamModel> Params { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public BodyModel Body { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// ResponseModel
/// </summary>
public class ResponseModel
{
/// <summary>
/// Gets or sets the HTTP status.
/// </summary>
/// <value>
/// The HTTP status.
/// </value>
public int StatusCode { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public string Body { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
/// <value>
/// The headers.
/// </value>
public IDictionary<string, string> Headers { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace WireMock.Admin
{
/// <summary>
/// UrlModel
/// </summary>
public class UrlModel
{
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IList<MatcherModel> Matchers { get; set; }
}
}

View File

@@ -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<ResponseMessage> _responseMessageFunc;
public DynamicResponseProvider([NotNull] Func<ResponseMessage> responseMessageFunc)
{
Check.NotNull(responseMessageFunc, nameof(responseMessageFunc));
_responseMessageFunc = responseMessageFunc;
}
public Task<ResponseMessage> ProvideResponse(RequestMessage requestMessage)
{
return Task.FromResult(_responseMessageFunc());
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
namespace WireMock.Extensions
{
/// <summary>
/// Dictionary Extensions
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Converts IDictionary to an ExpandObject.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dictionary">The dictionary.</param>
/// <returns></returns>
public static dynamic ToExpandoObject<T>(this IDictionary<string, T> dictionary)
{
dynamic expando = new ExpandoObject();
var expandoDic = (IDictionary<string, object>)expando;
// go through the items in the dictionary and copy over the key value pairs)
foreach (var kvp in dictionary)
{
// if the value can also be turned into an ExpandoObject, then do it!
var value = kvp.Value as IDictionary<string, object>;
if (value != null)
{
var expandoValue = value.ToExpandoObject();
expandoDic.Add(kvp.Key, expandoValue);
}
else if (kvp.Value is ICollection)
{
// iterate through the collection and convert any strin-object dictionaries
// along the way into expando objects
var itemList = new List<object>();
foreach (var item in (ICollection)kvp.Value)
{
var objects = item as IDictionary<string, object>;
if (objects != null)
{
var expandoItem = objects.ToExpandoObject();
itemList.Add(expandoItem);
}
else
{
itemList.Add(item);
}
}
expandoDic.Add(kvp.Key, itemList);
}
else
{
expandoDic.Add(kvp.Key, kvp.Value);
}
}
return expando;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Net;
using System.Net.Sockets;
namespace WireMock.Http
{
/// <summary>
/// The ports.
/// </summary>
public static class Ports
{
/// <summary>
/// The find free TCP port.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
/// <remarks>see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net.</remarks>
public static int FindFreeTcpPort()
{
TcpListener tcpListener = null;
try
{
tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
return ((IPEndPoint)tcpListener.LocalEndpoint).Port;
}
finally
{
tcpListener?.Stop();
}
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace WireMock.Http
{
/// <summary>
/// The tiny http server.
/// </summary>
public class TinyHttpServer
{
private readonly Action<HttpListenerContext> _httpHandler;
private readonly HttpListener _listener;
private CancellationTokenSource _cts;
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
/// <value>
/// <c>true</c> if this server is started; otherwise, <c>false</c>.
/// </value>
public bool IsStarted { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="TinyHttpServer"/> class.
/// </summary>
/// <param name="urlPrefix">
/// The url prefix.
/// </param>
/// <param name="httpHandler">
/// The http handler.
/// </param>
public TinyHttpServer(string urlPrefix, Action<HttpListenerContext> httpHandler)
{
_httpHandler = httpHandler;
// Create a listener.
_listener = new HttpListener();
_listener.Prefixes.Add(urlPrefix);
}
/// <summary>
/// Start the server.
/// </summary>
public void Start()
{
_listener.Start();
IsStarted = true;
_cts = new CancellationTokenSource();
Task.Run(
async () =>
{
using (_listener)
{
while (!_cts.Token.IsCancellationRequested)
{
HttpListenerContext context = await _listener.GetContextAsync();
_httpHandler(context);
}
}
},
_cts.Token);
}
/// <summary>
/// Stop the server.
/// </summary>
public void Stop()
{
_cts.Cancel();
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace WireMock
{
/// <summary>
/// The http listener request mapper.
/// </summary>
public class HttpListenerRequestMapper
{
/// <summary>
/// The map.
/// </summary>
/// <param name="listenerRequest">The listener request.</param>
/// <returns>The <see cref="RequestMessage"/>.</returns>
public RequestMessage Map(HttpListenerRequest listenerRequest)
{
Uri url = listenerRequest.Url;
string verb = listenerRequest.HttpMethod;
byte[] body = GetRequestBody(listenerRequest);
string bodyAsString = body != null ? listenerRequest.ContentEncoding.GetString(body) : null;
var listenerHeaders = listenerRequest.Headers;
var headers = listenerHeaders.AllKeys.ToDictionary(k => k, k => listenerHeaders[k]);
var cookies = new Dictionary<string, string>();
foreach (Cookie cookie in listenerRequest.Cookies)
cookies.Add(cookie.Name, cookie.Value);
return new RequestMessage(url, verb, body, bodyAsString, headers, cookies);
}
/// <summary>
/// The get request body.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The <see cref="string"/>.</returns>
private byte[] GetRequestBody(HttpListenerRequest request)
{
if (!request.HasEntityBody)
{
return null;
}
using (var bodyStream = request.InputStream)
{
using (var memoryStream = new MemoryStream())
{
bodyStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Linq;
using System.Net;
using System.Text;
namespace WireMock
{
/// <summary>
/// The http listener response mapper.
/// </summary>
public class HttpListenerResponseMapper
{
/// <summary>
/// The map.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
/// <param name="result">
/// The result.
/// </param>
public void Map(ResponseMessage responseMessage, HttpListenerResponse result)
{
result.StatusCode = responseMessage.StatusCode;
responseMessage.Headers.ToList().ForEach(pair => result.AddHeader(pair.Key, pair.Value));
if (responseMessage.Body != null)
{
var content = Encoding.UTF8.GetBytes(responseMessage.Body);
result.OutputStream.Write(content, 0, content.Length);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Threading.Tasks;
namespace WireMock
{
/// <summary>
/// The Response Provider interface.
/// </summary>
public interface IResponseProvider
{
/// <summary>
/// The provide response.
/// </summary>
/// <param name="requestMessage">
/// The request.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
Task<ResponseMessage> ProvideResponse(RequestMessage requestMessage);
}
}

View File

@@ -0,0 +1,54 @@
using System.Threading.Tasks;
using WireMock.Matchers.Request;
namespace WireMock
{
/// <summary>
/// The route.
/// </summary>
public class Mapping
{
/// <summary>
/// The Request matcher.
/// </summary>
public IRequestMatcher RequestMatcher { get; }
/// <summary>
/// The Provider.
/// </summary>
public IResponseProvider Provider { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="provider">The provider.</param>
public Mapping(IRequestMatcher requestMatcher, IResponseProvider provider)
{
RequestMatcher = requestMatcher;
Provider = provider;
}
/// <summary>
/// The response to.
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <returns>The <see cref="Task"/>.</returns>
public async Task<ResponseMessage> ResponseTo(RequestMessage requestMessage)
{
return await Provider.ProvideResponse(requestMessage);
}
/// <summary>
/// Determines whether the RequestMessage is handled.
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <returns>
/// <c>true</c> if RequestMessage is handled; otherwise, <c>false</c>.
/// </returns>
public bool IsRequestHandled(RequestMessage requestMessage)
{
return RequestMatcher.IsMatch(requestMessage);
}
}
}

View File

@@ -0,0 +1,23 @@
namespace WireMock.Matchers
{
/// <summary>
/// IMatcher
/// </summary>
public interface IMatcher
{
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// <c>true</c> if the specified input is match; otherwise, <c>false</c>.
/// </returns>
bool IsMatch(string input);
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
string GetPattern();
}
}

View File

@@ -0,0 +1,61 @@
using System;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// JSONPathMatcher
/// </summary>
/// <seealso cref="WireMock.Matchers.IMatcher" />
public class JsonPathMatcher : IMatcher
{
private readonly string _pattern;
/// <summary>
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
public JsonPathMatcher([NotNull] string pattern)
{
Check.NotNull(pattern, nameof(pattern));
_pattern = pattern;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// <c>true</c> if the specified input is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(string input)
{
if (input == null)
return false;
try
{
JObject o = JObject.Parse(input);
JToken token = o.SelectToken(_pattern);
return token != null;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public string GetPattern()
{
return _pattern;
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// Regular Expression Matcher
/// </summary>
/// <seealso cref="WireMock.Matchers.IMatcher" />
public class RegexMatcher : IMatcher
{
private readonly string _pattern;
private readonly Regex _expression;
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
public RegexMatcher([NotNull, RegexPattern] string pattern)
{
Check.NotNull(pattern, nameof(pattern));
_pattern = pattern;
_expression = new Regex(_pattern, RegexOptions.Compiled);
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// <c>true</c> if the specified input is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(string input)
{
if (input == null)
return false;
try
{
return _expression.IsMatch(input);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public string GetPattern()
{
return _pattern;
}
}
}

View File

@@ -0,0 +1,18 @@
namespace WireMock.Matchers.Request
{
/// <summary>
/// CompositeMatcherType
/// </summary>
public enum CompositeMatcherType
{
/// <summary>
/// And
/// </summary>
And = 0,
/// <summary>
/// Or
/// </summary>
Or = 1
}
}

View File

@@ -0,0 +1,19 @@
using JetBrains.Annotations;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The RequestMatcher interface.
/// </summary>
public interface IRequestMatcher
{
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
bool IsMatch([NotNull] RequestMessage requestMessage);
}
}

View File

@@ -0,0 +1,124 @@
using System;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher : IRequestMatcher
{
/// <summary>
/// The body.
/// </summary>
private readonly string _body;
/// <summary>
/// The body as byte[].
/// </summary>
private readonly byte[] _bodyData;
/// <summary>
/// The body function
/// </summary>
private readonly Func<string, bool> _bodyFunc;
/// <summary>
/// The body data function
/// </summary>
private readonly Func<byte[], bool> _bodyDataFunc;
/// <summary>
/// The matcher.
/// </summary>
public readonly IMatcher Matcher;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="body">
/// The body Regex pattern.
/// </param>
public RequestMessageBodyMatcher([NotNull] string body)
{
Check.NotNull(body, nameof(body));
_body = body;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="body">
/// The body Regex pattern.
/// </param>
public RequestMessageBodyMatcher([NotNull] byte[] body)
{
Check.NotNull(body, nameof(body));
_bodyData = body;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">
/// The body func.
/// </param>
public RequestMessageBodyMatcher([NotNull] Func<string, bool> func)
{
Check.NotNull(func, nameof(func));
_bodyFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">
/// The body func.
/// </param>
public RequestMessageBodyMatcher([NotNull] Func<byte[], bool> func)
{
Check.NotNull(func, nameof(func));
_bodyDataFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matcher">
/// The body matcher.
/// </param>
public RequestMessageBodyMatcher([NotNull] IMatcher matcher)
{
Check.NotNull(matcher, nameof(matcher));
Matcher = matcher;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (Matcher != null)
return Matcher.IsMatch(requestMessage.Body);
if (_body != null)
return requestMessage.Body == _body;
if (_bodyData != null)
return requestMessage.BodyAsBytes == _bodyData;
if (_bodyFunc != null)
return _bodyFunc(requestMessage.Body);
if (_bodyDataFunc != null)
return _bodyDataFunc(requestMessage.BodyAsBytes);
return false;
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The composite request matcher.
/// </summary>
public abstract class RequestMessageCompositeMatcher : IRequestMatcher
{
private readonly CompositeMatcherType _type;
/// <summary>
/// Gets the request matchers.
/// </summary>
/// <value>
/// The request matchers.
/// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{
Check.NotNull(requestMatchers, nameof(requestMatchers));
_type = type;
RequestMatchers = requestMatchers;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public virtual bool IsMatch(RequestMessage requestMessage)
{
return _type == CompositeMatcherType.And ?
RequestMatchers.All(matcher => matcher.IsMatch(requestMessage)) :
RequestMatchers.Any(matcher => matcher.IsMatch(requestMessage));
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request cookie matcher.
/// </summary>
public class RequestMessageCookieMatcher : IRequestMatcher
{
private readonly Func<IDictionary<string, string>, bool>[] _cookieFuncs;
/// <summary>
/// The name
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">The ignoreCase.</param>
public RequestMessageCookieMatcher([NotNull] string name, [NotNull] string pattern, bool ignoreCase = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(pattern, nameof(pattern));
Name = name;
Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) };
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageCookieMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
_cookieFuncs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (_cookieFuncs != null)
return _cookieFuncs.Any(cf => cf(requestMessage.Cookies));
if (requestMessage.Cookies == null)
return false;
string headerValue = requestMessage.Cookies[Name];
return Matchers.Any(m => m.IsMatch(headerValue));
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request header matcher.
/// </summary>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
private readonly Func<IDictionary<string, string>, bool>[] _headerFuncs;
/// <summary>
/// The name
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">The ignoreCase.</param>
public RequestMessageHeaderMatcher([NotNull] string name, [NotNull] string pattern, bool ignoreCase = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(pattern, nameof(pattern));
Name = name;
Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) };
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageHeaderMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
_headerFuncs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (_headerFuncs != null)
return _headerFuncs.Any(hf => hf(requestMessage.Headers));
if (requestMessage.Headers == null)
return false;
string headerValue = requestMessage.Headers[Name];
return Matchers.Any(m => m.IsMatch(headerValue));
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request parameters matcher.
/// </summary>
public class RequestMessageParamMatcher : IRequestMatcher
{
private readonly Func<IDictionary<string, WireMockList<string>>, bool>[] _funcs;
/// <summary>
/// The key
/// </summary>
public string Key { get; }
/// <summary>
/// The values
/// </summary>
public IEnumerable<string> Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="values">
/// The values.
/// </param>
public RequestMessageParamMatcher([NotNull] string key, [NotNull] IEnumerable<string> values)
{
Check.NotNull(key, nameof(key));
Check.NotNull(values, nameof(values));
Key = key;
Values = values;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageParamMatcher([NotNull] params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
_funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (_funcs != null)
return _funcs.Any(f => f(requestMessage.Query));
var values = requestMessage.GetParameter(Key);
return values?.Intersect(Values).Count() == Values.Count();
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request path matcher.
/// </summary>
public class RequestMessagePathMatcher : IRequestMatcher
{
/// <summary>
/// The matcher.
/// </summary>
public IReadOnlyList<IMatcher> Matchers { get; }
/// <summary>
/// The path functions
/// </summary>
private readonly Func<string, bool>[] _pathFuncs;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="paths">The paths.</param>
public RequestMessagePathMatcher([NotNull] params string[] paths) : this(paths.Select(path => new WildcardMatcher(path)).ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessagePathMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="funcs">The path functions.</param>
public RequestMessagePathMatcher([NotNull] params Func<string, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
_pathFuncs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (Matchers != null)
return Matchers.Any(matcher => matcher.IsMatch(requestMessage.Path));
if (_pathFuncs != null)
return _pathFuncs.Any(func => func(requestMessage.Path));
return false;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request url matcher.
/// </summary>
public class RequestMessageUrlMatcher : IRequestMatcher
{
/// <summary>
/// The matcher.
/// </summary>
public readonly IReadOnlyList<IMatcher> Matchers;
/// <summary>
/// The url functions
/// </summary>
private readonly Func<string, bool>[] _urlFuncs;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="urls">The urls.</param>
public RequestMessageUrlMatcher([NotNull] params string[] urls) : this(urls.Select(url => new WildcardMatcher(url)).ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessageUrlMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="funcs">The url functions.</param>
public RequestMessageUrlMatcher([NotNull] params Func<string, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
_urlFuncs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
if (Matchers != null)
return Matchers.Any(matcher => matcher.IsMatch(requestMessage.Path));
if (_urlFuncs != null)
return _urlFuncs.Any(func => func(requestMessage.Url));
return false;
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request verb matcher.
/// </summary>
internal class RequestMessageVerbMatcher : IRequestMatcher
{
/// <summary>
/// The verbs
/// </summary>
public string[] Verbs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageVerbMatcher"/> class.
/// </summary>
/// <param name="verbs">
/// The verb.
/// </param>
public RequestMessageVerbMatcher([NotNull] params string[] verbs)
{
Check.NotNull(verbs, nameof(verbs));
Verbs = verbs.Select(v => v.ToLower()).ToArray();
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <returns>
/// <c>true</c> if the specified RequestMessage is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(RequestMessage requestMessage)
{
return Verbs.Contains(requestMessage.Verb);
}
}
}

View File

@@ -0,0 +1,111 @@
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// WildcardMatcher
/// </summary>
/// <seealso cref="WireMock.Matchers.IMatcher" />
public class WildcardMatcher : IMatcher
{
private readonly string _pattern;
private readonly bool _ignoreCase;
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">IgnoreCase</param>
public WildcardMatcher([NotNull] string pattern, bool ignoreCase = false)
{
Check.NotNull(pattern, nameof(pattern));
_pattern = pattern;
_ignoreCase = ignoreCase;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// <c>true</c> if the specified input is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(string input)
{
return MatchWildcardString(_pattern, input);
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public string GetPattern()
{
return _pattern;
}
/// <summary>
/// Copy/paste from http://www.codeproject.com/Tips/57304/Use-wildcard-characters-and-to-compare-strings
/// </summary>
private bool MatchWildcardString([NotNull] string pattern, string input)
{
if (input != null && _ignoreCase)
input = input.ToLower();
if (_ignoreCase)
pattern = pattern.ToLower();
if (string.CompareOrdinal(pattern, input) == 0)
{
return true;
}
if (string.IsNullOrEmpty(input))
{
return string.IsNullOrEmpty(pattern.Trim('*'));
}
if (pattern.Length == 0)
{
return false;
}
if (pattern[0] == '?')
{
return MatchWildcardString(pattern.Substring(1), input.Substring(1));
}
if (pattern[pattern.Length - 1] == '?')
{
return MatchWildcardString(pattern.Substring(0, pattern.Length - 1), input.Substring(0, input.Length - 1));
}
if (pattern[0] == '*')
{
if (MatchWildcardString(pattern.Substring(1), input))
{
return true;
}
return MatchWildcardString(pattern, input.Substring(1));
}
if (pattern[pattern.Length - 1] == '*')
{
if (MatchWildcardString(pattern.Substring(0, pattern.Length - 1), input))
{
return true;
}
return MatchWildcardString(pattern, input.Substring(0, input.Length - 1));
}
if (pattern[0] == input[0])
{
return MatchWildcardString(pattern.Substring(1), input.Substring(1));
}
return false;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Xml;
using JetBrains.Annotations;
using WireMock.Validation;
using Wmhelp.XPath2;
namespace WireMock.Matchers
{
/// <summary>
/// XPath2Matcher
/// </summary>
/// <seealso cref="WireMock.Matchers.IMatcher" />
public class XPathMatcher : IMatcher
{
private readonly string _pattern;
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
public XPathMatcher([NotNull] string pattern)
{
Check.NotNull(pattern, nameof(pattern));
_pattern = pattern;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// <c>true</c> if the specified input is match; otherwise, <c>false</c>.
/// </returns>
public bool IsMatch(string input)
{
if (input == null)
return false;
try
{
var nav = new XmlDocument { InnerXml = input }.CreateNavigator();
object result = nav.XPath2Evaluate($"boolean({_pattern})");
return true.Equals(result);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public string GetPattern()
{
return _pattern;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using JetBrains.Annotations;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The BodyRequestBuilder interface.
/// </summary>
public interface IBodyRequestBuilder
{
/// <summary>
/// The with body.
/// </summary>
/// <param name="matcher">
/// The matcher.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithBody([NotNull] IMatcher matcher);
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">
/// The body.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithBody(string body);
/// <summary>
/// The with body byte[].
/// </summary>
/// <param name="body">
/// The body as byte[].
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithBody(byte[] body);
/// <summary>
/// The with body string func.
/// </summary>
/// <param name="body">
/// The body string function.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithBody(Func<string, bool> body);
/// <summary>
/// The with body byte[] func.
/// </summary>
/// <param name="body">
/// The body byte[] function.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithBody(Func<byte[], bool> body);
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The HeadersAndCookieRequestBuilder interface.
/// </summary>
public interface IHeadersAndCookiesRequestBuilder : IBodyRequestBuilder, IRequestMatcher, IParamsRequestBuilder
{
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">ignore Case</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
IHeadersAndCookiesRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true);
/// <summary>
/// The with header.
/// </summary>
/// <param name="funcs">The headers funcs.</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
IHeadersAndCookiesRequestBuilder WithHeader([NotNull] params Func<IDictionary<string, string>, bool>[] funcs);
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">ignore Case</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
IHeadersAndCookiesRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true);
/// <summary>
/// The with header.
/// </summary>
/// <param name="cookieFuncs">The funcs.</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
IHeadersAndCookiesRequestBuilder WithCookie([NotNull] params Func<IDictionary<string, string>, bool>[] cookieFuncs);
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
using WireMock.Util;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The ParametersRequestBuilder interface.
/// </summary>
public interface IParamsRequestBuilder
{
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="values">
/// The values.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
IRequestMatcher WithParam([NotNull] string key, params string[] values);
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestMatcher"/>.</returns>
IRequestMatcher WithParam([NotNull] params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs);
}
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.RequestBuilders
{
/// <summary>
/// IRequestBuilder
/// </summary>
public interface IRequestBuilder : IUrlAndPathRequestBuilder
{
}
}

View File

@@ -0,0 +1,54 @@
using System;
using JetBrains.Annotations;
using WireMock.Matchers;
namespace WireMock.RequestBuilders
{
/// <summary>
/// IUrlAndPathRequestBuilder
/// </summary>
public interface IUrlAndPathRequestBuilder : IVerbRequestBuilder
{
/// <summary>
/// The with url.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithUrl([NotNull] params IMatcher[] matchers);
/// <summary>
/// The with url.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithUrl([NotNull] params string[] urls);
/// <summary>
/// The with url.
/// </summary>
/// <param name="funcs">The url funcs.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithUrl([NotNull] params Func<string, bool>[] funcs);
/// <summary>
/// The with path.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithPath([NotNull] params IMatcher[] matchers);
/// <summary>
/// The with path.
/// </summary>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithPath([NotNull] params string[] paths);
/// <summary>
/// The with path.
/// </summary>
/// <param name="func">The path func.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
IUrlAndPathRequestBuilder WithPath([NotNull] params Func<string, bool>[] func);
}
}

View File

@@ -0,0 +1,65 @@
using JetBrains.Annotations;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The VerbRequestBuilder interface.
/// </summary>
public interface IVerbRequestBuilder : IHeadersAndCookiesRequestBuilder
{
/// <summary>
/// The using get.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingGet();
/// <summary>
/// The using post.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingPost();
/// <summary>
/// The using delete.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingDelete();
/// <summary>
/// The using put.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingPut();
/// <summary>
/// The using head.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingHead();
/// <summary>
/// The using any verb.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
IHeadersAndCookiesRequestBuilder UsingAnyVerb();
/// <summary>
/// The using verb.
/// </summary>
/// <param name="verbs">The verb.</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
IHeadersAndCookiesRequestBuilder UsingVerb([NotNull] params string[] verbs);
}
}

View File

@@ -0,0 +1,360 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The requests.
/// </summary>
public class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
private readonly IList<IRequestMatcher> _requestMatchers;
/// <summary>
/// Creates this instance.
/// </summary>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder Create()
{
return new Request(new List<IRequestMatcher>());
}
/// <summary>
/// Initializes a new instance of the <see cref="Request"/> class.
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
private Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
{
_requestMatchers = requestMatchers;
}
/// <summary>
/// Gets the request message matchers.
/// </summary>
/// <typeparam name="T">Type of IRequestMatcher</typeparam>
/// <returns>A List{T}</returns>
public IList<T> GetRequestMessageMatchers<T>() where T : IRequestMatcher
{
return new ReadOnlyCollection<T>(_requestMatchers.Where(rm => rm is T).Cast<T>().ToList());
}
/// <summary>
/// Gets the request message matcher.
/// </summary>
/// <typeparam name="T">Type of IRequestMatcher</typeparam>
/// <returns>A RequestMatcher</returns>
public T GetRequestMessageMatcher<T>() where T : IRequestMatcher
{
return _requestMatchers.Where(rm => rm is T).Cast<T>().FirstOrDefault();
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithUrl(params IMatcher[] matchers)
{
_requestMatchers.Add(new RequestMessageUrlMatcher(matchers));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithUrl(params string[] urls)
{
_requestMatchers.Add(new RequestMessageUrlMatcher(urls));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="funcs">The url func.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="matcher">The matcher.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithPath(params IMatcher[] matcher)
{
_requestMatchers.Add(new RequestMessagePathMatcher(matcher));
return this;
}
/// <summary>
/// The with path.
/// </summary>
/// <param name="paths">The path.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithPath(params string[] paths)
{
_requestMatchers.Add(new RequestMessagePathMatcher(paths));
return this;
}
/// <summary>
/// The with path.
/// </summary>
/// <param name="funcs">The path func.</param>
/// <returns>The <see cref="IUrlAndPathRequestBuilder"/>.</returns>
public IUrlAndPathRequestBuilder WithPath(params Func<string, bool>[] funcs)
{
_requestMatchers.Add(new RequestMessagePathMatcher(funcs));
return this;
}
/// <summary>
/// The using get.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingGet()
{
_requestMatchers.Add(new RequestMessageVerbMatcher("get"));
return this;
}
/// <summary>
/// The using post.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingPost()
{
_requestMatchers.Add(new RequestMessageVerbMatcher("post"));
return this;
}
/// <summary>
/// The using put.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingPut()
{
_requestMatchers.Add(new RequestMessageVerbMatcher("put"));
return this;
}
/// <summary>
/// The using delete.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingDelete()
{
_requestMatchers.Add(new RequestMessageVerbMatcher("delete"));
return this;
}
/// <summary>
/// The using head.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingHead()
{
_requestMatchers.Add(new RequestMessageVerbMatcher("head"));
return this;
}
/// <summary>
/// The using any verb.
/// </summary>
/// <returns>
/// The <see cref="IHeadersAndCookiesRequestBuilder"/>.
/// </returns>
public IHeadersAndCookiesRequestBuilder UsingAnyVerb()
{
var matchers = _requestMatchers.Where(m => m is RequestMessageVerbMatcher).ToList();
foreach (var matcher in matchers)
{
_requestMatchers.Remove(matcher);
}
return this;
}
/// <summary>
/// The using verb.
/// </summary>
/// <param name="verbs">The verbs.</param>
/// <returns>The <see cref="IHeadersAndCookiesRequestBuilder"/>.</returns>
public IHeadersAndCookiesRequestBuilder UsingVerb(params string[] verbs)
{
_requestMatchers.Add(new RequestMessageVerbMatcher(verbs));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">
/// The body.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
public IRequestMatcher WithBody(string body)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(body));
return this;
}
/// <summary>
/// The with body byte[].
/// </summary>
/// <param name="body">
/// The body as byte[].
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
public IRequestMatcher WithBody(byte[] body)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(body));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="func">
/// The body function.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
public IRequestMatcher WithBody(Func<string, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="func">
/// The body function.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
public IRequestMatcher WithBody(Func<byte[], bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="matcher">The matcher.</param>
/// <returns>
/// The <see cref="IRequestMatcher" />.
/// </returns>
public IRequestMatcher WithBody(IMatcher matcher)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matcher));
return this;
}
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="values">
/// The values.
/// </param>
/// <returns>
/// The <see cref="IRequestMatcher"/>.
/// </returns>
public IRequestMatcher WithParam(string key, params string[] values)
{
_requestMatchers.Add(new RequestMessageParamMatcher(key, values.ToList()));
return this;
}
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestMatcher"/>.</returns>
public IRequestMatcher WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
/// <returns></returns>
public IHeadersAndCookiesRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true)
{
_requestMatchers.Add(new RequestMessageHeaderMatcher(name, pattern, ignoreCase));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns></returns>
public IHeadersAndCookiesRequestBuilder WithHeader(params Func<IDictionary<string, string>, bool>[] funcs)
{
_requestMatchers.Add(new RequestMessageHeaderMatcher(funcs));
return this;
}
/// <summary>
/// With cookie.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
/// <returns></returns>
public IHeadersAndCookiesRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true)
{
_requestMatchers.Add(new RequestMessageCookieMatcher(name, pattern, ignoreCase));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns></returns>
public IHeadersAndCookiesRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock
{
/// <summary>
/// The request.
/// </summary>
public class RequestMessage
{
/// <summary>
/// Gets the url.
/// </summary>
public string Url { get; private set; }
/// <summary>
/// Gets the path.
/// </summary>
public string Path { get; }
/// <summary>
/// Gets the verb.
/// </summary>
public string Verb { get; }
/// <summary>
/// Gets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; }
/// <summary>
/// Gets the cookies.
/// </summary>
public IDictionary<string, string> Cookies { get; }
/// <summary>
/// Gets the query.
/// </summary>
public IDictionary<string, WireMockList<string>> Query { get; } = new Dictionary<string, WireMockList<string>>();
/// <summary>
/// Gets the bodyAsBytes.
/// </summary>
public byte[] BodyAsBytes { get; }
/// <summary>
/// Gets the body.
/// </summary>
public string Body { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessage"/> class.
/// </summary>
/// <param name="url">The original url.</param>
/// <param name="verb">The verb.</param>
/// <param name="bodyAsBytes">The bodyAsBytes byte[].</param>
/// <param name="body">The body string.</param>
/// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param>
public RequestMessage([NotNull] Uri url, [NotNull] string verb, [CanBeNull] byte[] bodyAsBytes = null, [CanBeNull] string body = null, [CanBeNull] IDictionary<string, string> headers = null, [CanBeNull] IDictionary<string, string> cookies = null)
{
Check.NotNull(url, nameof(url));
Check.NotNull(verb, nameof(verb));
Url = url.ToString();
Path = url.AbsolutePath;
Verb = verb.ToLower();
BodyAsBytes = bodyAsBytes;
Body = body;
Headers = headers;
Cookies = cookies;
string query = url.Query;
if (!string.IsNullOrEmpty(query))
{
if (query.StartsWith("?"))
{
query = query.Substring(1);
}
Query = query.Split('&').Aggregate(
new Dictionary<string, WireMockList<string>>(),
(dict, term) =>
{
var parts = term.Split('=');
var key = parts[0];
if (!dict.ContainsKey(key))
{
dict.Add(key, new WireMockList<string>());
}
if (parts.Length == 2)
dict[key].Add(parts[1]);
return dict;
});
}
}
/// <summary>
/// The get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The query parameter.</returns>
public List<string> GetParameter(string key)
{
return Query.ContainsKey(key) ? Query[key] : null;
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Text;
using JetBrains.Annotations;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface IBodyResponseBuilder : ITransformResponseBuilder
{
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <returns>A <see cref="ITransformResponseBuilder"/>.</returns>
ITransformResponseBuilder WithBody([NotNull] string body);
/// <summary>
/// The with body as base64.
/// </summary>
/// <param name="bodyAsbase64">The body asbase64.</param>
/// <param name="encoding">The Encoding.</param>
/// <returns>A <see cref="ITransformResponseBuilder"/>.</returns>
ITransformResponseBuilder WithBodyAsBase64([NotNull] string bodyAsbase64, [CanBeNull] Encoding encoding = null);
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The DelayResponseBuilder interface.
/// </summary>
public interface IDelayResponseBuilder : IResponseProvider
{
/// <summary>
/// The after delay.
/// </summary>
/// <param name="delay">The delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(TimeSpan delay);
}
}

View File

@@ -0,0 +1,18 @@
using JetBrains.Annotations;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The HeadersResponseBuilder interface.
/// </summary>
public interface IHeadersResponseBuilder : IBodyResponseBuilder
{
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
/// <returns>The <see cref="IHeadersResponseBuilder"/>.</returns>
IHeadersResponseBuilder WithHeader([NotNull] string name, string value);
}
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The ResponseBuilder interface.
/// </summary>
public interface IResponseBuilder : IStatusCodeResponseBuilder
{
}
}

View File

@@ -0,0 +1,40 @@
using System.Net;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The StatusCodeResponseBuilder interface.
/// </summary>
public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder
{
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">
/// The code.
/// </param>
/// <returns>The <see cref="IHeadersResponseBuilder"/>.</returns>
IHeadersResponseBuilder WithStatusCode(int code);
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">
/// The code.
/// </param>
/// <returns>The <see cref="IHeadersResponseBuilder"/>.</returns>
IHeadersResponseBuilder WithStatusCode(HttpStatusCode code);
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IHeadersResponseBuilder WithSuccess();
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IHeadersResponseBuilder WithNotFound();
}
}

View File

@@ -0,0 +1,16 @@
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface ITransformResponseBuilder : IDelayResponseBuilder
{
/// <summary>
/// The with transformer.
/// </summary>
/// <returns>
/// The <see cref="IDelayResponseBuilder"/>.
/// </returns>
IDelayResponseBuilder WithTransformer();
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using HandlebarsDotNet;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The Response.
/// </summary>
public class Response : IResponseBuilder
{
private TimeSpan _delay = TimeSpan.Zero;
private bool _useTransformer;
/// <summary>
/// Gets the response message.
/// </summary>
/// <value>
/// The response message.
/// </value>
public ResponseMessage ResponseMessage { get; }
/// <summary>
/// Creates this instance.
/// </summary>
/// <param name="responseMessage">ResponseMessage</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null)
{
var message = responseMessage ?? new ResponseMessage { StatusCode = (int)HttpStatusCode.OK };
return new Response(message);
}
/// <summary>
/// Creates this instance.
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create([NotNull] Func<ResponseMessage> func)
{
Check.NotNull(func, nameof(func));
return new Response(func());
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>A <see cref="IHeadersResponseBuilder"/>.</returns>\
[PublicAPI]
public IHeadersResponseBuilder WithStatusCode(int code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>A <see cref="IHeadersResponseBuilder"/>.</returns>
[PublicAPI]
public IHeadersResponseBuilder WithStatusCode(HttpStatusCode code)
{
return WithStatusCode((int)code);
}
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns></returns>
[PublicAPI]
public IHeadersResponseBuilder WithSuccess()
{
return WithStatusCode((int)HttpStatusCode.OK);
}
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IHeadersResponseBuilder WithNotFound()
{
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
/// <returns>The <see cref="IHeadersResponseBuilder"/>.</returns>
public IHeadersResponseBuilder WithHeader(string name, string value)
{
Check.NotNull(name, nameof(name));
ResponseMessage.AddHeader(name, value);
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <returns>A <see cref="ITransformResponseBuilder"/>.</returns>
public ITransformResponseBuilder WithBody(string body)
{
Check.NotNull(body, nameof(body));
ResponseMessage.Body = body;
return this;
}
/// <summary>
/// The with body as base64.
/// </summary>
/// <param name="bodyAsbase64">The body asbase64.</param>
/// <param name="encoding">The Encoding.</param>
/// <returns>A <see cref="ITransformResponseBuilder"/>.</returns>
public ITransformResponseBuilder WithBodyAsBase64(string bodyAsbase64, Encoding encoding = null)
{
Check.NotNull(bodyAsbase64, nameof(bodyAsbase64));
ResponseMessage.Body = (encoding ?? Encoding.UTF8).GetString(Convert.FromBase64String(bodyAsbase64));
return this;
}
/// <summary>
/// The with transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
public IDelayResponseBuilder WithTransformer()
{
_useTransformer = true;
return this;
}
/// <summary>
/// The after delay.
/// </summary>
/// <param name="delay">
/// The delay.
/// </param>
/// <returns>
/// The <see cref="IResponseProvider"/>.
/// </returns>
public IResponseBuilder WithDelay(TimeSpan delay)
{
_delay = delay;
return this;
}
/// <summary>
/// The provide response.
/// </summary>
/// <param name="requestMessage">
/// The request.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public async Task<ResponseMessage> 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);
// Headers
var newHeaders = new Dictionary<string, string>();
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;
}
else
{
responseMessage = ResponseMessage;
}
await Task.Delay(_delay);
return responseMessage;
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace WireMock
{
/// <summary>
/// The response.
/// </summary>
public class ResponseMessage
{
/// <summary>
/// Gets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; set; } = new ConcurrentDictionary<string, string>();
/// <summary>
/// Gets or sets the status code.
/// </summary>
public int StatusCode { get; set; } = 200;
/// <summary>
/// Gets or sets the body.
/// </summary>
public string Body { get; set; }
/// <summary>
/// The add header.
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <param name="value">
/// The value.
/// </param>
public void AddHeader(string name, string value)
{
Headers.Add(name, value);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace WireMock
{
/// <summary>
/// The registration callback.
/// </summary>
/// <param name="mapping">
/// The route.
/// </param>
public delegate void RegistrationCallback(Mapping mapping);
}

View File

@@ -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
{
/// <summary>
/// The fluent mock server.
/// </summary>
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<MappingModel>();
foreach (var mapping in Mappings.Where(m => !(m.Provider is DynamicResponseProvider)))
{
var request = (Request) mapping.RequestMatcher;
var urlMatchers = request.GetRequestMessageMatchers<RequestMessageUrlMatcher>();
var headerMatchers = request.GetRequestMessageMatchers<RequestMessageHeaderMatcher>();
var cookieMatchers = request.GetRequestMessageMatchers<RequestMessageCookieMatcher>();
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var verbMatcher = request.GetRequestMessageMatcher<RequestMessageVerbMatcher>();
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<MatcherModel> Map([CanBeNull] IEnumerable<IMatcher> 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>(T result)
{
return new ResponseMessage
{
Body = JsonConvert.SerializeObject(result, Formatting.Indented),
StatusCode = 200,
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
};
}
}
}

View File

@@ -0,0 +1,272 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Matchers.Request;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class FluentMockServer
{
/// <summary>
/// The _http server.
/// </summary>
private readonly TinyHttpServer _httpServer;
/// <summary>
/// The _mappings.
/// </summary>
private readonly IList<Mapping> _mappings = new List<Mapping>();
/// <summary>
/// The _request logs.
/// </summary>
private readonly IList<RequestMessage> _requestLogs = new List<RequestMessage>();
/// <summary>
/// The _request mapper.
/// </summary>
private readonly HttpListenerRequestMapper _requestMapper = new HttpListenerRequestMapper();
/// <summary>
/// The _response mapper.
/// </summary>
private readonly HttpListenerResponseMapper _responseMapper = new HttpListenerResponseMapper();
/// <summary>
/// The _sync root.
/// </summary>
private readonly object _syncRoot = new object();
/// <summary>
/// The _request processing delay.
/// </summary>
private TimeSpan _requestProcessingDelay = TimeSpan.Zero;
/// <summary>
/// Gets the port.
/// </summary>
public int Port { get; }
/// <summary>
/// Gets the request logs.
/// </summary>
public IEnumerable<RequestMessage> RequestLogs
{
get
{
lock (((ICollection)_requestLogs).SyncRoot)
{
return new ReadOnlyCollection<RequestMessage>(_requestLogs);
}
}
}
/// <summary>
/// Gets the routes.
/// </summary>
public IEnumerable<Mapping> Mappings
{
get
{
lock (((ICollection)_mappings).SyncRoot)
{
return new ReadOnlyCollection<Mapping>(_mappings);
}
}
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(int port = 0, bool ssl = false)
{
Check.Condition(port, p => p >= 0, nameof(port));
if (port == 0)
port = Ports.FindFreeTcpPort();
return new FluentMockServer(false, port, ssl);
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[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 + "/", HandleRequestAsync);
Port = port;
_httpServer.Start();
if (startAdmin)
{
InitAdmin();
}
}
/// <summary>
/// Stop this server.
/// </summary>
public void Stop()
{
_httpServer.Stop();
}
/// <summary>
/// The reset.
/// </summary>
public void Reset()
{
lock (((ICollection)_requestLogs).SyncRoot)
{
_requestLogs.Clear();
}
lock (((ICollection)_mappings).SyncRoot)
{
_mappings.Clear();
}
}
/// <summary>
/// The search logs for.
/// </summary>
/// <param name="spec">
/// The matcher.
/// </param>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// </returns>
public IEnumerable<RequestMessage> SearchLogsFor(IRequestMatcher spec)
{
lock (((ICollection)_requestLogs).SyncRoot)
{
return _requestLogs.Where(spec.IsMatch);
}
}
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">
/// The delay.
/// </param>
public void AddRequestProcessingDelay(TimeSpan delay)
{
lock (_syncRoot)
{
_requestProcessingDelay = delay;
}
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
public IRespondWithAProvider Given(IRequestMatcher requestMatcher)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher);
}
/// <summary>
/// The register mapping.
/// </summary>
/// <param name="mapping">
/// The mapping.
/// </param>
private void RegisterMapping(Mapping mapping)
{
lock (((ICollection)_mappings).SyncRoot)
{
_mappings.Add(mapping);
}
}
/// <summary>
/// The log request.
/// </summary>
/// <param name="requestMessage">
/// The request.
/// </param>
private void LogRequest(RequestMessage requestMessage)
{
lock (((ICollection)_requestLogs).SyncRoot)
{
_requestLogs.Add(requestMessage);
}
}
/// <summary>
/// The handle request.
/// </summary>
/// <param name="ctx">The HttpListenerContext.</param>
private async void HandleRequestAsync(HttpListenerContext ctx)
{
lock (_syncRoot)
{
Task.Delay(_requestProcessingDelay).Wait();
}
var request = _requestMapper.Map(ctx.Request);
LogRequest(request);
try
{
var targetRoute = _mappings.FirstOrDefault(route => route.IsRequestHandled(request));
if (targetRoute == null)
{
ctx.Response.StatusCode = 404;
byte[] content = Encoding.UTF8.GetBytes("No mapping found");
ctx.Response.OutputStream.Write(content, 0, content.Length);
}
else
{
var response = await targetRoute.ResponseTo(request);
_responseMapper.Map(response, ctx.Response);
}
}
catch (Exception ex)
{
ctx.Response.StatusCode = 500;
byte[] content = Encoding.UTF8.GetBytes(ex.ToString());
ctx.Response.OutputStream.Write(content, 0, content.Length);
}
finally
{
ctx.Response.Close();
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace WireMock.Server
{
/// <summary>
/// IRespondWithAProvider
/// </summary>
public interface IRespondWithAProvider
{
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">
/// The provider.
/// </param>
void RespondWith(IResponseProvider provider);
}
}

View File

@@ -0,0 +1,42 @@
using WireMock.Matchers.Request;
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
/// <summary>
/// The _registration callback.
/// </summary>
private readonly RegistrationCallback _registrationCallback;
/// <summary>
/// The _request matcher.
/// </summary>
private readonly IRequestMatcher _requestMatcher;
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">
/// The provider.
/// </param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(_requestMatcher, provider));
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Util
{
/// <summary>
/// A special List which overrides the ToString() to return first value.
/// </summary>
/// <typeparam name="T">The generic type</typeparam>
/// <seealso cref="List{T}" />
public class WireMockList<T> : List<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
public WireMockList()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(params T[] collection) : base(collection)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(IEnumerable<T> collection) : base(collection)
{
}
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
{
if (this != null && this.Any())
return this.First().ToString();
return base.ToString();
}
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
// Copied from https://github.com/aspnet/EntityFramework/blob/dev/src/Shared/Check.cs
namespace WireMock.Validation
{
[ExcludeFromCodeCoverage]
[DebuggerStepThrough]
internal static class Check
{
[ContractAnnotation("value:null => halt")]
public static T Condition<T>([NoEnumeration] T value, [NotNull] Predicate<T> condition, [InvokerParameterName] [NotNull] string parameterName)
{
NotNull(condition, nameof(condition));
NotNull(value, nameof(value));
if (!condition(value))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentOutOfRangeException(parameterName);
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName)
{
if (ReferenceEquals(value, null))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentNullException(parameterName);
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>(
[NoEnumeration] T value,
[InvokerParameterName] [NotNull] string parameterName,
[NotNull] string propertyName)
{
if (ReferenceEquals(value, null))
{
NotEmpty(parameterName, nameof(parameterName));
NotEmpty(propertyName, nameof(propertyName));
throw new ArgumentException(CoreStrings.ArgumentPropertyNull(propertyName, parameterName));
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static IList<T> NotEmpty<T>(IList<T> value, [InvokerParameterName] [NotNull] string parameterName)
{
NotNull(value, parameterName);
if (value.Count == 0)
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.CollectionArgumentIsEmpty(parameterName));
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName)
{
Exception e = null;
if (ReferenceEquals(value, null))
{
e = new ArgumentNullException(parameterName);
}
else if (value.Trim().Length == 0)
{
e = new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName));
}
if (e != null)
{
NotEmpty(parameterName, nameof(parameterName));
throw e;
}
return value;
}
public static string NullButNotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName)
{
if (!ReferenceEquals(value, null)
&& (value.Length == 0))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName));
}
return value;
}
public static IList<T> HasNoNulls<T>(IList<T> value, [InvokerParameterName] [NotNull] string parameterName)
where T : class
{
NotNull(value, parameterName);
if (value.Any(e => e == null))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(parameterName);
}
return value;
}
public static Type ValidEntityType(Type value, [InvokerParameterName] [NotNull] string parameterName)
{
if (!value.GetTypeInfo().IsClass)
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.InvalidEntityType(value, parameterName));
}
return value;
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using JetBrains.Annotations;
// copied from https://github.com/aspnet/EntityFramework/blob/dev/src/Microsoft.EntityFrameworkCore/Properties/CoreStrings.resx
namespace WireMock.Validation
{
[ExcludeFromCodeCoverage]
internal static class CoreStrings
{
/// <summary>
/// The property '{property}' of the argument '{argument}' cannot be null.
/// </summary>
public static string ArgumentPropertyNull([CanBeNull] string property, [CanBeNull] string argument)
{
return string.Format(CultureInfo.CurrentCulture, $"The property '{property}' of the argument '{argument}' cannot be null.", property, argument);
}
/// <summary>
/// The string argument '{argumentName}' cannot be empty.
/// </summary>
public static string ArgumentIsEmpty([CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The string argument '{argumentName}' cannot be empty.", argumentName);
}
/// <summary>
/// The entity type '{type}' provided for the argument '{argumentName}' must be a reference type.
/// </summary>
public static string InvalidEntityType([CanBeNull] Type type, [CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The entity type '{type}' provided for the argument '{argumentName}' must be a reference type.", type, argumentName);
}
/// <summary>
/// The collection argument '{argumentName}' must contain at least one element.
/// </summary>
public static string CollectionArgumentIsEmpty([CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The collection argument '{argumentName}' must contain at least one element.", argumentName);
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d3804228-91f4-4502-9595-39584e5a01ad</ProjectGuid>
<RootNamespace>WireMock</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,15 @@
,
"netstandard1.3": {
"buildOptions": { "define": [ "NETSTANDARD" ] },
"imports": [
"dotnet5.4"
],
"dependencies": {
"System.Collections.Concurrent": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Linq": "4.3.0",
"System.Net.Http": "4.3.0",
"System.Net.Sockets": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
}

View File

@@ -0,0 +1,43 @@
{
"version": "1.0.0.0",
"title": "WireMock.Net",
"description": "Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.",
"authors": [ "Alexandre Victoor", "Stef Heyenrath" ],
"packOptions": {
"summary": "Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.",
"tags": [ "tdd", "mock", "http", "wiremock", "test", "server", "unittest" ],
"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"
},
"buildOptions": {
"xmlDoc": true
},
"dependencies": {
"JetBrains.Annotations": {
"version": "10.2.1",
"type": "build"
},
"Handlebars.Net": "1.8.0",
"Newtonsoft.Json": "6.0.8",
"XPath2": "1.0.3.1"
},
"frameworks": {
"net45": {
"dependencies": {
},
"frameworkAssemblies": {
}
}
}
}