From eeaeeb2c61d4bc06a11afaf8be801bef7a5ce17e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 17 Jan 2017 22:44:21 +0100 Subject: [PATCH] Initial code (copy) --- README.md | 2 +- WireMock.Net Solution.sln | 56 ++++ .../Program.cs | 44 +++ .../Properties/AssemblyInfo.cs | 19 ++ .../WireMock.Net.ConsoleApp.xproj | 21 ++ .../project.json | 19 ++ .../App.config | 6 + .../Program.cs | 42 +++ .../Properties/AssemblyInfo.cs | 35 ++ .../WireMock.Net.ConsoleApplication.csproj | 62 ++++ .../packages.config | 4 + src/WireMock/CompositeRequestSpec.cs | 57 ++++ src/WireMock/FluentMockServer.cs | 311 ++++++++++++++++++ src/WireMock/Http/Ports.cs | 38 +++ src/WireMock/Http/TinyHttpServer.cs | 98 ++++++ src/WireMock/HttpListenerRequestMapper.cs | 68 ++++ src/WireMock/HttpListenerResponseMapper.cs | 38 +++ src/WireMock/IProvideResponses.cs | 27 ++ src/WireMock/ISpecifyRequests.cs | 26 ++ src/WireMock/Request.cs | 138 ++++++++ src/WireMock/RequestBodySpec.cs | 54 +++ src/WireMock/RequestHeaderSpec.cs | 64 ++++ src/WireMock/RequestParamSpec.cs | 65 ++++ src/WireMock/RequestPathSpec.cs | 54 +++ src/WireMock/RequestSpecBuilder.cs | 123 +++++++ src/WireMock/RequestUrlSpec.cs | 54 +++ src/WireMock/RequestVerbSpec.cs | 54 +++ src/WireMock/Requests.cs | 205 ++++++++++++ src/WireMock/Response.cs | 92 ++++++ src/WireMock/ResponseBuilder.cs | 64 ++++ src/WireMock/Responses.cs | 143 ++++++++ src/WireMock/Route.cs | 78 +++++ src/WireMock/RouteRegistrationCallback.cs | 17 + src/WireMock/WildcardPatternMatcher.cs | 74 +++++ src/WireMock/WireMock.xproj | 19 ++ src/WireMock/netstandard1.3.txt | 15 + src/WireMock/project.json | 37 +++ .../FluentMockServerTests.cs | 225 +++++++++++++ .../Http/TinyHttpServerTests.cs | 39 +++ .../HttpListenerRequestMapperTests.cs | 166 ++++++++++ .../HttpListenerResponseMapperTests.cs | 126 +++++++ .../Properties/AssemblyInfo.cs | 36 ++ test/WireMock.Net.Tests/RequestTests.cs | 42 +++ test/WireMock.Net.Tests/RequestsTests.cs | 215 ++++++++++++ .../WildcardPatternMatcherTests.cs | 48 +++ .../WireMock.Net.Tests.csproj | 85 +++++ test/WireMock.Net.Tests/packages.config | 7 + 47 files changed, 3311 insertions(+), 1 deletion(-) create mode 100644 WireMock.Net Solution.sln create mode 100644 examples/WireMock.Net.ConsoleApp.netcore/Program.cs create mode 100644 examples/WireMock.Net.ConsoleApp.netcore/Properties/AssemblyInfo.cs create mode 100644 examples/WireMock.Net.ConsoleApp.netcore/WireMock.Net.ConsoleApp.xproj create mode 100644 examples/WireMock.Net.ConsoleApp.netcore/project.json create mode 100644 examples/WireMock.Net.ConsoleApplication/App.config create mode 100644 examples/WireMock.Net.ConsoleApplication/Program.cs create mode 100644 examples/WireMock.Net.ConsoleApplication/Properties/AssemblyInfo.cs create mode 100644 examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj create mode 100644 examples/WireMock.Net.ConsoleApplication/packages.config create mode 100644 src/WireMock/CompositeRequestSpec.cs create mode 100644 src/WireMock/FluentMockServer.cs create mode 100644 src/WireMock/Http/Ports.cs create mode 100644 src/WireMock/Http/TinyHttpServer.cs create mode 100644 src/WireMock/HttpListenerRequestMapper.cs create mode 100644 src/WireMock/HttpListenerResponseMapper.cs create mode 100644 src/WireMock/IProvideResponses.cs create mode 100644 src/WireMock/ISpecifyRequests.cs create mode 100644 src/WireMock/Request.cs create mode 100644 src/WireMock/RequestBodySpec.cs create mode 100644 src/WireMock/RequestHeaderSpec.cs create mode 100644 src/WireMock/RequestParamSpec.cs create mode 100644 src/WireMock/RequestPathSpec.cs create mode 100644 src/WireMock/RequestSpecBuilder.cs create mode 100644 src/WireMock/RequestUrlSpec.cs create mode 100644 src/WireMock/RequestVerbSpec.cs create mode 100644 src/WireMock/Requests.cs create mode 100644 src/WireMock/Response.cs create mode 100644 src/WireMock/ResponseBuilder.cs create mode 100644 src/WireMock/Responses.cs create mode 100644 src/WireMock/Route.cs create mode 100644 src/WireMock/RouteRegistrationCallback.cs create mode 100644 src/WireMock/WildcardPatternMatcher.cs create mode 100644 src/WireMock/WireMock.xproj create mode 100644 src/WireMock/netstandard1.3.txt create mode 100644 src/WireMock/project.json create mode 100644 test/WireMock.Net.Tests/FluentMockServerTests.cs create mode 100644 test/WireMock.Net.Tests/Http/TinyHttpServerTests.cs create mode 100644 test/WireMock.Net.Tests/HttpListenerRequestMapperTests.cs create mode 100644 test/WireMock.Net.Tests/HttpListenerResponseMapperTests.cs create mode 100644 test/WireMock.Net.Tests/Properties/AssemblyInfo.cs create mode 100644 test/WireMock.Net.Tests/RequestTests.cs create mode 100644 test/WireMock.Net.Tests/RequestsTests.cs create mode 100644 test/WireMock.Net.Tests/WildcardPatternMatcherTests.cs create mode 100644 test/WireMock.Net.Tests/WireMock.Net.Tests.csproj create mode 100644 test/WireMock.Net.Tests/packages.config diff --git a/README.md b/README.md index 05e27efc..8d04b786 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # WireMock.Net -A C# .NET version based on http://wiremock.org +A C# .NET version based on https://github.com/alexvictoor/mock4net which tries to mimic the functionality from http://WireMock.org diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln new file mode 100644 index 00000000..56694f12 --- /dev/null +++ b/WireMock.Net Solution.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF242EDF-7133-4277-9A0C-18744DE08707}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{197A0EE3-94E5-4807-BBCF-2F1BCA28A6AE}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WireMock", "src\WireMock\WireMock.xproj", "{D3804228-91F4-4502-9595-39584E5A01AD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F0C22C47-DF71-463C-9B04-B4E0F3B8708A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ConsoleApplication", "examples\WireMock.Net.ConsoleApplication\WireMock.Net.ConsoleApplication.csproj", "{668F689E-57B4-422E-8846-C0FF643CA268}" + ProjectSection(ProjectDependencies) = postProject + {D3804228-91F4-4502-9595-39584E5A01AD} = {D3804228-91F4-4502-9595-39584E5A01AD} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{890A1DED-C229-4FA1-969E-AAC3BBFC05E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Tests", "test\WireMock.Net.Tests\WireMock.Net.Tests.csproj", "{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}" + ProjectSection(ProjectDependencies) = postProject + {D3804228-91F4-4502-9595-39584E5A01AD} = {D3804228-91F4-4502-9595-39584E5A01AD} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.Build.0 = Release|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA268}.Debug|Any CPU.Build.0 = Debug|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA268}.Release|Any CPU.ActiveCfg = Release|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA268}.Release|Any CPU.Build.0 = Release|Any CPU + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D3804228-91F4-4502-9595-39584E5A01AD} = {EF242EDF-7133-4277-9A0C-18744DE08707} + {668F689E-57B4-422E-8846-C0FF643CA268} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A} + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25} = {890A1DED-C229-4FA1-969E-AAC3BBFC05E5} + EndGlobalSection +EndGlobal diff --git a/examples/WireMock.Net.ConsoleApp.netcore/Program.cs b/examples/WireMock.Net.ConsoleApp.netcore/Program.cs new file mode 100644 index 00000000..8f65a78c --- /dev/null +++ b/examples/WireMock.Net.ConsoleApp.netcore/Program.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WireMock.Net.ConsoleApp +{ + public class Program + { + public static void Main(string[] args) + { + int port; + if (args.Length == 0 || !int.TryParse(args[0], out port)) + port = 8080; + + var server = FluentMockServer.Start(port); + Console.WriteLine("FluentMockServer running at {0}", server.Port); + + server + .Given( + Requests + .WithUrl("/*") + .UsingGet() + ) + .RespondWith( + Responses + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""msg"": ""Hello world!""}") + ); + + 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)); + + Console.WriteLine("Press any key to quit"); + Console.ReadKey(); + } + } +} diff --git a/examples/WireMock.Net.ConsoleApp.netcore/Properties/AssemblyInfo.cs b/examples/WireMock.Net.ConsoleApp.netcore/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..c860343e --- /dev/null +++ b/examples/WireMock.Net.ConsoleApp.netcore/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WireMock.Net.ConsoleApp")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bb7ad978-5c03-4d76-a903-3865802b45eb")] diff --git a/examples/WireMock.Net.ConsoleApp.netcore/WireMock.Net.ConsoleApp.xproj b/examples/WireMock.Net.ConsoleApp.netcore/WireMock.Net.ConsoleApp.xproj new file mode 100644 index 00000000..cb9dfd3b --- /dev/null +++ b/examples/WireMock.Net.ConsoleApp.netcore/WireMock.Net.ConsoleApp.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + bb7ad978-5c03-4d76-a903-3865802b45eb + WireMock.Net.ConsoleApp + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/examples/WireMock.Net.ConsoleApp.netcore/project.json b/examples/WireMock.Net.ConsoleApp.netcore/project.json new file mode 100644 index 00000000..cd924dee --- /dev/null +++ b/examples/WireMock.Net.ConsoleApp.netcore/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.1.0" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + } +} diff --git a/examples/WireMock.Net.ConsoleApplication/App.config b/examples/WireMock.Net.ConsoleApplication/App.config new file mode 100644 index 00000000..88fa4027 --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs new file mode 100644 index 00000000..969eee4a --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -0,0 +1,42 @@ +using System; +using Newtonsoft.Json; + +namespace WireMock.Net.ConsoleApplication +{ + static class Program + { + static void Main(string[] args) + { + int port; + if (args.Length == 0 || !int.TryParse(args[0], out port)) + port = 8080; + + var server = FluentMockServer.Start(port); + Console.WriteLine("FluentMockServer running at {0}", server.Port); + + server + .Given( + Requests + .WithUrl("/*") + .UsingGet() + ) + .RespondWith( + Responses + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(@"{ ""msg"": ""Hello world!""}") + ); + + 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)); + + Console.WriteLine("Press any key to quit"); + Console.ReadKey(); + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/Properties/AssemblyInfo.cs b/examples/WireMock.Net.ConsoleApplication/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5b156822 --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mock4Net.Core.ConsoleApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Mock4Net.Core.ConsoleApplication")] +[assembly: AssemblyCopyright("Copyright © Stef Heyenrath 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("668f689e-57b4-422e-8846-c0ff643ca268")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj new file mode 100644 index 00000000..0b2e0676 --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.ConsoleApplication.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {668F689E-57B4-422E-8846-C0FF643CA268} + Exe + Properties + WireMock.Net.ConsoleApplication + WireMock.Net.ConsoleApplication + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + ..\..\src\WireMock\bin\$(Configuration)\net45\WireMock.dll + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/packages.config b/examples/WireMock.Net.ConsoleApplication/packages.config new file mode 100644 index 00000000..9d64bf36 --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/WireMock/CompositeRequestSpec.cs b/src/WireMock/CompositeRequestSpec.cs new file mode 100644 index 00000000..18e278f2 --- /dev/null +++ b/src/WireMock/CompositeRequestSpec.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The composite request spec. + /// + public class CompositeRequestSpec : ISpecifyRequests + { + /// + /// The _request specs. + /// + private readonly IEnumerable _requestSpecs; + + /// + /// Initializes a new instance of the class. + /// The constructor. + /// + /// + /// The request specs. + /// + public CompositeRequestSpec(IEnumerable requestSpecs) + { + _requestSpecs = requestSpecs; + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return _requestSpecs.All(spec => spec.IsSatisfiedBy(request)); + } + } +} diff --git a/src/WireMock/FluentMockServer.cs b/src/WireMock/FluentMockServer.cs new file mode 100644 index 00000000..8c30bbe4 --- /dev/null +++ b/src/WireMock/FluentMockServer.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using WireMock.Http; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The fluent mock server. + /// + public class FluentMockServer + { + /// + /// The _http server. + /// + private readonly TinyHttpServer _httpServer; + + /// + /// The _routes. + /// + private readonly IList _routes = new List(); + + /// + /// The _request logs. + /// + private readonly IList _requestLogs = new List(); + + /// + /// The _request mapper. + /// + private readonly HttpListenerRequestMapper _requestMapper = new HttpListenerRequestMapper(); + + /// + /// The _response mapper. + /// + private readonly HttpListenerResponseMapper _responseMapper = new HttpListenerResponseMapper(); + + /// + /// The _sync root. + /// + private readonly object _syncRoot = new object(); + + /// + /// The _request processing delay. + /// + private TimeSpan _requestProcessingDelay = TimeSpan.Zero; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The port. + /// + /// + /// The SSL support. + /// + private FluentMockServer(int port, bool ssl) + { + string protocol = ssl ? "https" : "http"; + _httpServer = new TinyHttpServer(protocol + "://localhost:" + port + "/", HandleRequest); + Port = port; + _httpServer.Start(); + } + + /// + /// The RespondWithAProvider interface. + /// + public interface IRespondWithAProvider + { + /// + /// The respond with. + /// + /// + /// The provider. + /// + void RespondWith(IProvideResponses provider); + } + + /// + /// Gets the port. + /// + public int Port { get; } + + /// + /// Gets the request logs. + /// + public IEnumerable RequestLogs + { + get + { + lock (((ICollection)_requestLogs).SyncRoot) + { + return new ReadOnlyCollection(_requestLogs); + } + } + } + + /// + /// The start. + /// + /// + /// The port. + /// + /// + /// The SSL support. + /// + /// + /// The . + /// + public static FluentMockServer Start(int port = 0, bool ssl = false) + { + if (port == 0) + { + port = Ports.FindFreeTcpPort(); + } + + return new FluentMockServer(port, ssl); + } + + /// + /// The reset. + /// + public void Reset() + { + lock (((ICollection)_requestLogs).SyncRoot) + { + _requestLogs.Clear(); + } + + lock (((ICollection)_routes).SyncRoot) + { + _routes.Clear(); + } + } + + /// + /// The search logs for. + /// + /// + /// The spec. + /// + /// + /// The . + /// + public IEnumerable SearchLogsFor(ISpecifyRequests spec) + { + lock (((ICollection)_requestLogs).SyncRoot) + { + return _requestLogs.Where(spec.IsSatisfiedBy); + } + } + + /// + /// The add request processing delay. + /// + /// + /// The delay. + /// + public void AddRequestProcessingDelay(TimeSpan delay) + { + lock (_syncRoot) + { + _requestProcessingDelay = delay; + } + } + + /// + /// The stop. + /// + public void Stop() + { + _httpServer.Stop(); + } + + /// + /// The given. + /// + /// + /// The request spec. + /// + /// + /// The . + /// + public IRespondWithAProvider Given(ISpecifyRequests requestSpec) + { + return new RespondWithAProvider(RegisterRoute, requestSpec); + } + + /// + /// The register route. + /// + /// + /// The route. + /// + private void RegisterRoute(Route route) + { + lock (((ICollection)_routes).SyncRoot) + { + _routes.Add(route); + } + } + + /// + /// The log request. + /// + /// + /// The request. + /// + private void LogRequest(Request request) + { + lock (((ICollection)_requestLogs).SyncRoot) + { + _requestLogs.Add(request); + } + } + + /// + /// The handle request. + /// + /// + /// The context. + /// + private async void HandleRequest(HttpListenerContext ctx) + { + lock (_syncRoot) + { + Task.Delay(_requestProcessingDelay).Wait(); + } + + var request = _requestMapper.Map(ctx.Request); + LogRequest(request); + var targetRoute = _routes.FirstOrDefault(route => route.IsRequestHandled(request)); + if (targetRoute == null) + { + ctx.Response.StatusCode = 404; + var content = Encoding.UTF8.GetBytes("Mock Server: page not found"); + ctx.Response.OutputStream.Write(content, 0, content.Length); + } + else + { + var response = await targetRoute.ResponseTo(request); + + _responseMapper.Map(response, ctx.Response); + } + + ctx.Response.Close(); + } + + /// + /// The respond with a provider. + /// + private class RespondWithAProvider : IRespondWithAProvider + { + /// + /// The _registration callback. + /// + private readonly RegistrationCallback _registrationCallback; + + /// + /// The _request spec. + /// + private readonly ISpecifyRequests _requestSpec; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The registration callback. + /// + /// + /// The request spec. + /// + public RespondWithAProvider(RegistrationCallback registrationCallback, ISpecifyRequests requestSpec) + { + _registrationCallback = registrationCallback; + _requestSpec = requestSpec; + } + + /// + /// The respond with. + /// + /// + /// The provider. + /// + public void RespondWith(IProvideResponses provider) + { + _registrationCallback(new Route(_requestSpec, provider)); + } + } + } +} diff --git a/src/WireMock/Http/Ports.cs b/src/WireMock/Http/Ports.cs new file mode 100644 index 00000000..4d5c2e1e --- /dev/null +++ b/src/WireMock/Http/Ports.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.CodeAnalysis; +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 +{ + /// + /// The ports. + /// + public static class Ports + { + /// + /// The find free TCP port. + /// + /// + /// The . + /// + /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. + // ReSharper disable once StyleCop.SA1650 + public static int FindFreeTcpPort() + { + TcpListener l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + } +} diff --git a/src/WireMock/Http/TinyHttpServer.cs b/src/WireMock/Http/TinyHttpServer.cs new file mode 100644 index 00000000..23f3bcbe --- /dev/null +++ b/src/WireMock/Http/TinyHttpServer.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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.Http +{ + /// + /// The tiny http server. + /// + public class TinyHttpServer + { + /// + /// The _http handler. + /// + private readonly Action _httpHandler; + + /// + /// The _listener. + /// + private readonly HttpListener _listener; + + /// + /// The cancellation token source. + /// + private CancellationTokenSource _cts; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The url prefix. + /// + /// + /// The http handler. + /// + public TinyHttpServer(string urlPrefix, Action httpHandler) + { + _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); + } + + /// + /// The start. + /// + public void Start() + { + _listener.Start(); + _cts = new CancellationTokenSource(); + Task.Run( + async () => + { + using (_listener) + { + while (!_cts.Token.IsCancellationRequested) + { + HttpListenerContext context = await _listener.GetContextAsync(); + _httpHandler(context); + } + } + }, + _cts.Token); + } + + /// + /// The stop. + /// + public void Stop() + { + _cts.Cancel(); + } + } +} diff --git a/src/WireMock/HttpListenerRequestMapper.cs b/src/WireMock/HttpListenerRequestMapper.cs new file mode 100644 index 00000000..d31588ec --- /dev/null +++ b/src/WireMock/HttpListenerRequestMapper.cs @@ -0,0 +1,68 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] +// ReSharper disable ArrangeThisQualifier +namespace WireMock +{ + /// + /// The http listener request mapper. + /// + public class HttpListenerRequestMapper + { + /// + /// The map. + /// + /// + /// The listener request. + /// + /// + /// The . + /// + public Request Map(HttpListenerRequest listenerRequest) + { + var path = listenerRequest.Url.AbsolutePath; + var query = listenerRequest.Url.Query; + var verb = listenerRequest.HttpMethod; + var body = GetRequestBody(listenerRequest); + var listenerHeaders = listenerRequest.Headers; + var headers = listenerHeaders.AllKeys.ToDictionary(k => k, k => listenerHeaders[k]); + + return new Request(path, query, verb, body, headers); + } + + /// + /// The get request body. + /// + /// + /// The request. + /// + /// + /// The . + /// + private string GetRequestBody(HttpListenerRequest request) + { + if (!request.HasEntityBody) + { + return null; + } + + using (var body = request.InputStream) + { + using (var reader = new StreamReader(body, request.ContentEncoding)) + { + return reader.ReadToEnd(); + } + } + } + } +} diff --git a/src/WireMock/HttpListenerResponseMapper.cs b/src/WireMock/HttpListenerResponseMapper.cs new file mode 100644 index 00000000..ad4ccfd1 --- /dev/null +++ b/src/WireMock/HttpListenerResponseMapper.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Text; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The http listener response mapper. + /// + public class HttpListenerResponseMapper + { + /// + /// The map. + /// + /// + /// The response. + /// + /// + /// The result. + /// + public void Map(Response response, HttpListenerResponse result) + { + result.StatusCode = response.StatusCode; + response.Headers.ToList().ForEach(pair => result.AddHeader(pair.Key, pair.Value)); + if (response.Body != null) + { + var content = Encoding.UTF8.GetBytes(response.Body); + result.OutputStream.Write(content, 0, content.Length); + } + } + } +} diff --git a/src/WireMock/IProvideResponses.cs b/src/WireMock/IProvideResponses.cs new file mode 100644 index 00000000..f5565c8c --- /dev/null +++ b/src/WireMock/IProvideResponses.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The ProvideResponses interface. + /// + public interface IProvideResponses + { + /// + /// The provide response. + /// + /// + /// The request. + /// + /// + /// The . + /// + Task ProvideResponse(Request request); + } +} diff --git a/src/WireMock/ISpecifyRequests.cs b/src/WireMock/ISpecifyRequests.cs new file mode 100644 index 00000000..d3515cba --- /dev/null +++ b/src/WireMock/ISpecifyRequests.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The SpecifyRequests interface. + /// + public interface ISpecifyRequests + { + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + bool IsSatisfiedBy(Request request); + } +} diff --git a/src/WireMock/Request.cs b/src/WireMock/Request.cs new file mode 100644 index 00000000..f7bc0c79 --- /dev/null +++ b/src/WireMock/Request.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[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.")] +// ReSharper disable ArrangeThisQualifier +// ReSharper disable InconsistentNaming +namespace WireMock +{ + /// + /// The request. + /// + public class Request + { + /// + /// The _params. + /// + private readonly Dictionary> _params = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + /// + /// The path. + /// + /// + /// The query. + /// + /// + /// The verb. + /// + /// + /// The body. + /// + /// + /// The headers. + /// + public Request(string path, string query, string verb, string body, IDictionary headers) + { + if (!string.IsNullOrEmpty(query)) + { + if (query.StartsWith("?")) + { + query = query.Substring(1); + } + + _params = query.Split('&').Aggregate( + new Dictionary>(), + (dict, term) => + { + var key = term.Split('=')[0]; + if (!dict.ContainsKey(key)) + { + dict.Add(key, new List()); + } + + dict[key].Add(term.Split('=')[1]); + return dict; + }); + } + + Path = path; + Headers = headers.ToDictionary(kv => kv.Key.ToLower(), kv => kv.Value.ToLower()); + Verb = verb.ToLower(); + Body = body?.Trim() ?? string.Empty; + } + + /// + /// Gets the url. + /// + public string Url + { + get + { + if (!_params.Any()) + { + return Path; + } + + return Path + "?" + string.Join("&", _params.SelectMany(kv => kv.Value.Select(value => kv.Key + "=" + value))); + } + } + + /// + /// Gets the path. + /// + public string Path { get; } + + /// + /// Gets the verb. + /// + public string Verb { get; } + + /// + /// Gets the headers. + /// + public IDictionary Headers { get; } + + /// + /// Gets the body. + /// + public string Body { get; } + + /// + /// The get parameter. + /// + /// + /// The key. + /// + /// + /// The parameter. + /// + public List GetParameter(string key) + { + if (_params.ContainsKey(key)) + { + return _params[key]; + } + + return new List(); + } + } +} diff --git a/src/WireMock/RequestBodySpec.cs b/src/WireMock/RequestBodySpec.cs new file mode 100644 index 00000000..f5388f5d --- /dev/null +++ b/src/WireMock/RequestBodySpec.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request body spec. + /// + public class RequestBodySpec : ISpecifyRequests + { + /// + /// The _body. + /// + private readonly string _body; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The body. + /// + public RequestBodySpec(string body) + { + _body = body.Trim(); + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return WildcardPatternMatcher.MatchWildcardString(_body, request.Body.Trim()); + } + } +} diff --git a/src/WireMock/RequestHeaderSpec.cs b/src/WireMock/RequestHeaderSpec.cs new file mode 100644 index 00000000..91961c01 --- /dev/null +++ b/src/WireMock/RequestHeaderSpec.cs @@ -0,0 +1,64 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request header spec. + /// + public class RequestHeaderSpec : ISpecifyRequests + { + /// + /// The _name. + /// + private readonly string _name; + + /// + /// The _pattern. + /// + private readonly string _pattern; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name. + /// + /// + /// The pattern. + /// + public RequestHeaderSpec(string name, string pattern) + { + _name = name.ToLower(); + _pattern = pattern.ToLower(); + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + string headerValue = request.Headers[_name]; + return WildcardPatternMatcher.MatchWildcardString(_pattern, headerValue); + } + } +} \ No newline at end of file diff --git a/src/WireMock/RequestParamSpec.cs b/src/WireMock/RequestParamSpec.cs new file mode 100644 index 00000000..19de981b --- /dev/null +++ b/src/WireMock/RequestParamSpec.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request parameters spec. + /// + public class RequestParamSpec : ISpecifyRequests + { + /// + /// The _key. + /// + private readonly string _key; + + /// + /// The _values. + /// + private readonly List _values; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The key. + /// + /// + /// The values. + /// + public RequestParamSpec(string key, List values) + { + _key = key; + _values = values; + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return request.GetParameter(_key).Intersect(_values).Count() == _values.Count; + } + } +} diff --git a/src/WireMock/RequestPathSpec.cs b/src/WireMock/RequestPathSpec.cs new file mode 100644 index 00000000..a5b45bea --- /dev/null +++ b/src/WireMock/RequestPathSpec.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request path spec. + /// + public class RequestPathSpec : ISpecifyRequests + { + /// + /// The _path. + /// + private readonly string _path; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The path. + /// + public RequestPathSpec(string path) + { + _path = path; + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return WildcardPatternMatcher.MatchWildcardString(_path, request.Path); + } + } +} diff --git a/src/WireMock/RequestSpecBuilder.cs b/src/WireMock/RequestSpecBuilder.cs new file mode 100644 index 00000000..f61eaa53 --- /dev/null +++ b/src/WireMock/RequestSpecBuilder.cs @@ -0,0 +1,123 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The VerbRequestBuilder interface. + /// + public interface IVerbRequestBuilder : ISpecifyRequests, IHeadersRequestBuilder + { + /// + /// The using get. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingGet(); + + /// + /// The using post. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingPost(); + + /// + /// The using put. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingPut(); + + /// + /// The using head. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingHead(); + + /// + /// The using any verb. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingAnyVerb(); + + /// + /// The using verb. + /// + /// + /// The verb. + /// + /// + /// The . + /// + IHeadersRequestBuilder UsingVerb(string verb); + } + + /// + /// The HeadersRequestBuilder interface. + /// + public interface IHeadersRequestBuilder : IBodyRequestBuilder, ISpecifyRequests, IParamsRequestBuilder + { + /// + /// The with header. + /// + /// + /// The name. + /// + /// + /// The value. + /// + /// + /// The . + /// + IHeadersRequestBuilder WithHeader(string name, string value); + } + + /// + /// The BodyRequestBuilder interface. + /// + public interface IBodyRequestBuilder + { + /// + /// The with body. + /// + /// + /// The body. + /// + /// + /// The . + /// + ISpecifyRequests WithBody(string body); + } + + /// + /// The ParametersRequestBuilder interface. + /// + public interface IParamsRequestBuilder + { + /// + /// The with parameters. + /// + /// + /// The key. + /// + /// + /// The values. + /// + /// + /// The . + /// + ISpecifyRequests WithParam(string key, params string[] values); + } +} diff --git a/src/WireMock/RequestUrlSpec.cs b/src/WireMock/RequestUrlSpec.cs new file mode 100644 index 00000000..b3ea9f78 --- /dev/null +++ b/src/WireMock/RequestUrlSpec.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request url spec. + /// + public class RequestUrlSpec : ISpecifyRequests + { + /// + /// The _url. + /// + private readonly string _url; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The url. + /// + public RequestUrlSpec(string url) + { + _url = url; + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return WildcardPatternMatcher.MatchWildcardString(_url, request.Url); + } + } +} diff --git a/src/WireMock/RequestVerbSpec.cs b/src/WireMock/RequestVerbSpec.cs new file mode 100644 index 00000000..cb362cf9 --- /dev/null +++ b/src/WireMock/RequestVerbSpec.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The request verb spec. + /// + internal class RequestVerbSpec : ISpecifyRequests + { + /// + /// The _verb. + /// + private readonly string _verb; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The verb. + /// + public RequestVerbSpec(string verb) + { + _verb = verb.ToLower(); + } + + /// + /// The is satisfied by. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsSatisfiedBy(Request request) + { + return request.Verb == _verb; + } + } +} diff --git a/src/WireMock/Requests.cs b/src/WireMock/Requests.cs new file mode 100644 index 00000000..bd5b2fe4 --- /dev/null +++ b/src/WireMock/Requests.cs @@ -0,0 +1,205 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1126:PrefixCallsCorrectly", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The requests. + /// + public class Requests : CompositeRequestSpec, IVerbRequestBuilder, IHeadersRequestBuilder, IParamsRequestBuilder + { + /// + /// The _request specs. + /// + private readonly IList _requestSpecs; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The request specs. + /// + private Requests(IList requestSpecs) : base(requestSpecs) + { + _requestSpecs = requestSpecs; + } + + /// + /// The with url. + /// + /// + /// The url. + /// + /// + /// The . + /// + public static IVerbRequestBuilder WithUrl(string url) + { + var specs = new List(); + var requests = new Requests(specs); + specs.Add(new RequestUrlSpec(url)); + return requests; + } + + /// + /// The with path. + /// + /// + /// The path. + /// + /// + /// The . + /// + public static IVerbRequestBuilder WithPath(string path) + { + var specs = new List(); + var requests = new Requests(specs); + specs.Add(new RequestPathSpec(path)); + return requests; + } + + /// + /// The using get. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingGet() + { + _requestSpecs.Add(new RequestVerbSpec("get")); + return this; + } + + /// + /// The using post. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingPost() + { + _requestSpecs.Add(new RequestVerbSpec("post")); + return this; + } + + /// + /// The using put. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingPut() + { + _requestSpecs.Add(new RequestVerbSpec("put")); + return this; + } + + /// + /// The using head. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingHead() + { + _requestSpecs.Add(new RequestVerbSpec("head")); + return this; + } + + /// + /// The using any verb. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingAnyVerb() + { + return this; + } + + /// + /// The using verb. + /// + /// + /// The verb. + /// + /// + /// The . + /// + public IHeadersRequestBuilder UsingVerb(string verb) + { + _requestSpecs.Add(new RequestVerbSpec(verb)); + return this; + } + + /// + /// The with body. + /// + /// + /// The body. + /// + /// + /// The . + /// + public ISpecifyRequests WithBody(string body) + { + _requestSpecs.Add(new RequestBodySpec(body)); + return this; + } + + /// + /// The with parameters. + /// + /// + /// The key. + /// + /// + /// The values. + /// + /// + /// The . + /// + public ISpecifyRequests WithParam(string key, params string[] values) + { + _requestSpecs.Add(new RequestParamSpec(key, values.ToList())); + return this; + } + + /// + /// The with header. + /// + /// + /// The name. + /// + /// + /// The value. + /// + /// + /// The . + /// + public IHeadersRequestBuilder WithHeader(string name, string value) + { + _requestSpecs.Add(new RequestHeaderSpec(name, value)); + return this; + } + } +} diff --git a/src/WireMock/Response.cs b/src/WireMock/Response.cs new file mode 100644 index 00000000..ef134c3a --- /dev/null +++ b/src/WireMock/Response.cs @@ -0,0 +1,92 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The response. + /// + public class Response + { + /// + /// The _headers. + /// + private readonly IDictionary _headers = new ConcurrentDictionary(); + + /// + /// The status code. + /// + private volatile int statusCode = 200; + + /// + /// The body. + /// + private volatile string body; + + /// + /// Gets the headers. + /// + public IDictionary Headers => _headers; + + /// + /// Gets or sets the status code. + /// + public int StatusCode + { + get + { + return statusCode; + } + + set + { + statusCode = value; + } + } + + /// + /// Gets or sets the body. + /// + public string Body + { + get + { + return body; + } + + set + { + body = value; + } + } + + /// + /// The add header. + /// + /// + /// The name. + /// + /// + /// The value. + /// + public void AddHeader(string name, string value) + { + _headers.Add(name, value); + } + } +} \ No newline at end of file diff --git a/src/WireMock/ResponseBuilder.cs b/src/WireMock/ResponseBuilder.cs new file mode 100644 index 00000000..9c23a1d5 --- /dev/null +++ b/src/WireMock/ResponseBuilder.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The HeadersResponseBuilder interface. + /// + public interface IHeadersResponseBuilder : IBodyResponseBuilder + { + /// + /// The with header. + /// + /// + /// The name. + /// + /// + /// The value. + /// + /// + /// The . + /// + IHeadersResponseBuilder WithHeader(string name, string value); + } + + /// + /// The BodyResponseBuilder interface. + /// + public interface IBodyResponseBuilder : IDelayResponseBuilder + { + /// + /// The with body. + /// + /// + /// The body. + /// + /// + /// The . + /// + IDelayResponseBuilder WithBody(string body); + } + + /// + /// The DelayResponseBuilder interface. + /// + public interface IDelayResponseBuilder : IProvideResponses + { + /// + /// The after delay. + /// + /// + /// The delay. + /// + /// + /// The . + /// + IProvideResponses AfterDelay(TimeSpan delay); + } +} diff --git a/src/WireMock/Responses.cs b/src/WireMock/Responses.cs new file mode 100644 index 00000000..f74ba8c8 --- /dev/null +++ b/src/WireMock/Responses.cs @@ -0,0 +1,143 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The responses. + /// + public class Responses : IHeadersResponseBuilder + { + /// + /// The _response. + /// + private readonly Response _response; + + /// + /// The _delay. + /// + private TimeSpan _delay = TimeSpan.Zero; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The response. + /// + public Responses(Response response) + { + _response = response; + } + + /// + /// The with Success status code. + /// + /// The . + public static IHeadersResponseBuilder WithSuccess() + { + return WithStatusCode(200); + } + + /// + /// The with NotFound status code. + /// + /// The . + public static IHeadersResponseBuilder WithNotFound() + { + return WithStatusCode(404); + } + + /// + /// The with status code. + /// + /// + /// The code. + /// + /// + /// The . + /// + public static IHeadersResponseBuilder WithStatusCode(int code) + { + var response = new Response { StatusCode = code }; + return new Responses(response); + } + + /// + /// The provide response. + /// + /// + /// The request. + /// + /// + /// The . + /// + public async Task ProvideResponse(Request request) + { + await Task.Delay(_delay); + return _response; + } + + /// + /// The with header. + /// + /// + /// The name. + /// + /// + /// The value. + /// + /// + /// The . + /// + public IHeadersResponseBuilder WithHeader(string name, string value) + { + _response.AddHeader(name, value); + return this; + } + + /// + /// The with body. + /// + /// + /// The body. + /// + /// + /// The . + /// + public IDelayResponseBuilder WithBody(string body) + { + _response.Body = body; + return this; + } + + /// + /// The after delay. + /// + /// + /// The delay. + /// + /// + /// The . + /// + public IProvideResponses AfterDelay(TimeSpan delay) + { + _delay = delay; + return this; + } + } +} diff --git a/src/WireMock/Route.cs b/src/WireMock/Route.cs new file mode 100644 index 00000000..4f1d3f9f --- /dev/null +++ b/src/WireMock/Route.cs @@ -0,0 +1,78 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + 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 +{ + /// + /// The route. + /// + public class Route + { + /// + /// The _request spec. + /// + private readonly ISpecifyRequests _requestSpec; + + /// + /// The _provider. + /// + private readonly IProvideResponses _provider; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The request spec. + /// + /// + /// The provider. + /// + public Route(ISpecifyRequests requestSpec, IProvideResponses provider) + { + _requestSpec = requestSpec; + _provider = provider; + } + + /// + /// The response to. + /// + /// + /// The request. + /// + /// + /// The . + /// + public Task ResponseTo(Request request) + { + return _provider.ProvideResponse(request); + } + + /// + /// The is request handled. + /// + /// + /// The request. + /// + /// + /// The . + /// + public bool IsRequestHandled(Request request) + { + return _requestSpec.IsSatisfiedBy(request); + } + } +} diff --git a/src/WireMock/RouteRegistrationCallback.cs b/src/WireMock/RouteRegistrationCallback.cs new file mode 100644 index 00000000..738ea206 --- /dev/null +++ b/src/WireMock/RouteRegistrationCallback.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.CodeAnalysis; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock +{ + /// + /// The registration callback. + /// + /// + /// The route. + /// + public delegate void RegistrationCallback(Route route); +} diff --git a/src/WireMock/WildcardPatternMatcher.cs b/src/WireMock/WildcardPatternMatcher.cs new file mode 100644 index 00000000..5c54eaae --- /dev/null +++ b/src/WireMock/WildcardPatternMatcher.cs @@ -0,0 +1,74 @@ +using System.Diagnostics.CodeAnalysis; + +[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 +{ + /// + /// The wildcard pattern matcher. + /// + public static class WildcardPatternMatcher + { + /// + /// The match wildcard string. + /// + /// + /// The pattern. + /// + /// + /// The input. + /// + /// + /// The . + /// + /// + /// Copy/paste from http://www.codeproject.com/Tips/57304/Use-wildcard-characters-and-to-compare-strings + /// + public static bool MatchWildcardString(string pattern, string input) + { + 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] == '*') + { + return MatchWildcardString(pattern.Substring(1), input) || MatchWildcardString(pattern, input.Substring(1)); + } + + if (pattern[pattern.Length - 1] == '*') + { + return MatchWildcardString(pattern.Substring(0, pattern.Length - 1), input) || MatchWildcardString(pattern, input.Substring(0, input.Length - 1)); + } + + return pattern[0] == input[0] && MatchWildcardString(pattern.Substring(1), input.Substring(1)); + } + } +} diff --git a/src/WireMock/WireMock.xproj b/src/WireMock/WireMock.xproj new file mode 100644 index 00000000..6349f5bb --- /dev/null +++ b/src/WireMock/WireMock.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d3804228-91f4-4502-9595-39584e5a01ad + WireMock + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/WireMock/netstandard1.3.txt b/src/WireMock/netstandard1.3.txt new file mode 100644 index 00000000..77ffd0c5 --- /dev/null +++ b/src/WireMock/netstandard1.3.txt @@ -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" + } + } \ No newline at end of file diff --git a/src/WireMock/project.json b/src/WireMock/project.json new file mode 100644 index 00000000..834f983c --- /dev/null +++ b/src/WireMock/project.json @@ -0,0 +1,37 @@ +{ + "version": "1.0.0.9", + "title": "WireMock.Net", + "description": "xxxx", + "authors": [ "Alexandre Victoor", "Stef Heyenrath" ], + + "packOptions": { + "summary": "This is a .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality.", + "tags": [ "system", "linq", "dynamic", "core" ], + "owners": [ "Stef Heyenrath" ], + "repository": { + "type": "git", + "url": "https://github.com/StefH/WireMock.Net" + }, + "projectUrl": "https://github.com/StefH/WireMock.Net", + "licenseUrl": "https://github.com/StefH/WireMock.Net/blob/master/licence.txt", + "releaseNotes": "" + }, + + "buildOptions": { + "xmlDoc": true + }, + + "dependencies": { + "JetBrains.Annotations": { + "version": "10.2.1", + "type": "build" + } + }, + + "frameworks": { + "net45": { + "frameworkAssemblies": { + } + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs new file mode 100644 index 00000000..0093c816 --- /dev/null +++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs @@ -0,0 +1,225 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using NFluent; +using NUnit.Framework; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + 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.Net.Tests +{ + [TestFixture] + [Timeout(5000)] + public class FluentMockServerTests + { + private FluentMockServer _server; + + [Test] + public async Task Should_respond_to_request() + { + // given + _server = FluentMockServer.Start(); + + _server + .Given(Requests + .WithUrl("/foo") + .UsingGet()) + .RespondWith(Responses + .WithStatusCode(200) + .WithBody(@"{ msg: ""Hello world!""}")); + + // when + var response + = await new HttpClient().GetStringAsync("http://localhost:" + _server.Port + "/foo"); + + // then + Check.That(response).IsEqualTo(@"{ msg: ""Hello world!""}"); + } + + [Test] + public async Task Should_respond_404_for_unexpected_request() + { + // given + _server = FluentMockServer.Start(); + + // when + var response + = await new HttpClient().GetAsync("http://localhost:" + _server.Port + "/foo"); + + // then + Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound); + Check.That((int)response.StatusCode).IsEqualTo(404); + } + + [Test] + public async Task Should_record_requests_in_the_requestlogs() + { + // given + _server = FluentMockServer.Start(); + + // when + await new HttpClient().GetAsync("http://localhost:" + _server.Port + "/foo"); + + // then + Check.That(_server.RequestLogs).HasSize(1); + var requestLogged = _server.RequestLogs.First(); + Check.That(requestLogged.Verb).IsEqualTo("get"); + Check.That(requestLogged.Body).IsEmpty(); + } + + [Test] + public async Task Should_find_a_request_satisfying_a_request_spec() + { + // given + _server = FluentMockServer.Start(); + + // when + await new HttpClient().GetAsync("http://localhost:" + _server.Port + "/foo"); + await new HttpClient().GetAsync("http://localhost:" + _server.Port + "/bar"); + + // then + var result = _server.SearchLogsFor(Requests.WithUrl("/b*")); + Check.That(result).HasSize(1); + var requestLogged = result.First(); + Check.That(requestLogged.Url).IsEqualTo("/bar"); + } + + [Test] + public async Task Should_reset_requestlogs() + { + // given + _server = FluentMockServer.Start(); + + // when + await new HttpClient().GetAsync("http://localhost:" + _server.Port + "/foo"); + _server.Reset(); + + // then + Check.That(_server.RequestLogs).IsEmpty(); + } + + [Test] + public void Should_reset_routes() + { + // given + _server = FluentMockServer.Start(); + + _server + .Given(Requests + .WithUrl("/foo") + .UsingGet()) + .RespondWith(Responses + .WithStatusCode(200) + .WithBody(@"{ msg: ""Hello world!""}")); + + // when + _server.Reset(); + + // then + Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + _server.Port + "/foo")) + .ThrowsAny(); + } + + [Test] + public async Task Should_respond_a_redirect_without_body() + { + // given + _server = FluentMockServer.Start(); + + _server + .Given(Requests + .WithUrl("/foo") + .UsingGet()) + .RespondWith(Responses + .WithStatusCode(307) + .WithHeader("Location", "/bar")); + _server + .Given(Requests + .WithUrl("/bar") + .UsingGet()) + .RespondWith(Responses + .WithStatusCode(200) + .WithBody("REDIRECT SUCCESSFUL")); + + // when + var response + = await new HttpClient().GetStringAsync("http://localhost:" + _server.Port + "/foo"); + + // then + Check.That(response).IsEqualTo("REDIRECT SUCCESSFUL"); + } + + [Test] + public async Task Should_delay_responses_for_a_given_route() + { + // given + _server = FluentMockServer.Start(); + + _server + .Given(Requests + .WithUrl("/*")) + .RespondWith(Responses + .WithStatusCode(200) + .WithBody(@"{ msg: ""Hello world!""}") + .AfterDelay(TimeSpan.FromMilliseconds(2000))); + + // when + var watch = new Stopwatch(); + watch.Start(); + await new HttpClient().GetStringAsync("http://localhost:" + _server.Port + "/foo"); + watch.Stop(); + + // then + Check.That(watch.ElapsedMilliseconds).IsGreaterThan(2000); + } + + [Test] + public async Task Should_delay_responses() + { + // given + _server = FluentMockServer.Start(); + _server.AddRequestProcessingDelay(TimeSpan.FromMilliseconds(2000)); + _server + .Given(Requests + .WithUrl("/*")) + .RespondWith(Responses + .WithStatusCode(200) + .WithBody(@"{ msg: ""Hello world!""}")); + + // when + var watch = new Stopwatch(); + watch.Start(); + await new HttpClient().GetStringAsync("http://localhost:" + _server.Port + "/foo"); + watch.Stop(); + + // then + Check.That(watch.ElapsedMilliseconds).IsGreaterThan(2000); + } + + [TearDown] + public void ShutdownServer() + { + _server.Stop(); + } + } +} diff --git a/test/WireMock.Net.Tests/Http/TinyHttpServerTests.cs b/test/WireMock.Net.Tests/Http/TinyHttpServerTests.cs new file mode 100644 index 00000000..0286c67b --- /dev/null +++ b/test/WireMock.Net.Tests/Http/TinyHttpServerTests.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using NFluent; +using NUnit.Framework; +using WireMock.Http; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] + +namespace WireMock.Net.Tests.Http +{ + [TestFixture] + public class TinyHttpServerTests + { + [Test] + public void Should_Call_Handler_on_Request() + { + // given + var port = Ports.FindFreeTcpPort(); + bool called = false; + var urlPrefix = "http://localhost:" + port + "/"; + var server = new TinyHttpServer(urlPrefix, ctx => called = true); + server.Start(); + + // when + var httpClient = new HttpClient(); + httpClient.GetAsync(urlPrefix).Wait(3000); + + // then + Check.That(called).IsTrue(); + } + } +} diff --git a/test/WireMock.Net.Tests/HttpListenerRequestMapperTests.cs b/test/WireMock.Net.Tests/HttpListenerRequestMapperTests.cs new file mode 100644 index 00000000..564bd62d --- /dev/null +++ b/test/WireMock.Net.Tests/HttpListenerRequestMapperTests.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using NFluent; +using NUnit.Framework; +using WireMock.Http; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + 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.Net.Tests +{ + [TestFixture] + public class HttpListenerRequestMapperTests + { + private MapperServer _server; + + [SetUp] + public void StartListenerServer() + { + _server = MapperServer.Start(); + } + + [Test] + public async Task Should_map_uri_from_listener_request() + { + // given + var client = new HttpClient(); + + // when + await client.GetAsync(MapperServer.UrlPrefix + "toto"); + + // then + Check.That(MapperServer.LastRequest).IsNotNull(); + Check.That(MapperServer.LastRequest.Url).IsEqualTo("/toto"); + } + + [Test] + public async Task Should_map_verb_from_listener_request() + { + // given + var client = new HttpClient(); + + // when + await client.PutAsync(MapperServer.UrlPrefix, new StringContent("Hello!")); + + // then + Check.That(MapperServer.LastRequest).IsNotNull(); + Check.That(MapperServer.LastRequest.Verb).IsEqualTo("put"); + } + + [Test] + public async Task Should_map_body_from_listener_request() + { + // given + var client = new HttpClient(); + + // when + await client.PutAsync(MapperServer.UrlPrefix, new StringContent("Hello!")); + + // then + Check.That(MapperServer.LastRequest).IsNotNull(); + Check.That(MapperServer.LastRequest.Body).IsEqualTo("Hello!"); + } + + [Test] + public async Task Should_map_headers_from_listener_request() + { + // given + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-Alex", "1706"); + + // when + await client.GetAsync(MapperServer.UrlPrefix); + + // then + Check.That(MapperServer.LastRequest).IsNotNull(); + Check.That(MapperServer.LastRequest.Headers).Not.IsNullOrEmpty(); + Check.That(MapperServer.LastRequest.Headers.Contains(new KeyValuePair("x-alex", "1706"))).IsTrue(); + } + + [Test] + public async Task Should_map_params_from_listener_request() + { + // given + var client = new HttpClient(); + + // when + await client.GetAsync(MapperServer.UrlPrefix + "index.html?id=toto"); + + // then + Check.That(MapperServer.LastRequest).IsNotNull(); + Check.That(MapperServer.LastRequest.Path).EndsWith("/index.html"); + Check.That(MapperServer.LastRequest.GetParameter("id")).HasSize(1); + } + + [TearDown] + public void StopListenerServer() + { + _server.Stop(); + } + + private class MapperServer : TinyHttpServer + { + private static volatile Request _lastRequest; + + private MapperServer(string urlPrefix, Action httpHandler) : base(urlPrefix, httpHandler) + { + } + + public static Request LastRequest + { + get + { + return _lastRequest; + } + + private set + { + _lastRequest = value; + } + } + + public static string UrlPrefix { get; private set; } + + public static new MapperServer Start() + { + var port = Ports.FindFreeTcpPort(); + UrlPrefix = "http://localhost:" + port + "/"; + var server = new MapperServer( + UrlPrefix, + context => + { + LastRequest = new HttpListenerRequestMapper().Map(context.Request); + context.Response.Close(); + }); + ((TinyHttpServer)server).Start(); + return server; + } + + public new void Stop() + { + base.Stop(); + LastRequest = null; + } + } + } +} diff --git a/test/WireMock.Net.Tests/HttpListenerResponseMapperTests.cs b/test/WireMock.Net.Tests/HttpListenerResponseMapperTests.cs new file mode 100644 index 00000000..72d4358b --- /dev/null +++ b/test/WireMock.Net.Tests/HttpListenerResponseMapperTests.cs @@ -0,0 +1,126 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using NFluent; +using NUnit.Framework; +using WireMock.Http; + +[module: + SuppressMessage("StyleCop.CSharp.ReadabilityRules", + "SA1101:PrefixLocalCallsWithThis", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.NamingRules", + "SA1309:FieldNamesMustNotBeginWithUnderscore", + Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + 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.Net.Tests +{ + [TestFixture] + public class HttpListenerResponseMapperTests + { + private TinyHttpServer _server; + private Task _responseMsgTask; + + [Test] + public void Should_map_status_code_from_original_response() + { + // given + var response = new Response { StatusCode = 404 }; + var httpListenerResponse = CreateHttpListenerResponse(); + + // when + new HttpListenerResponseMapper().Map(response, httpListenerResponse); + + // then + Check.That(httpListenerResponse.StatusCode).IsEqualTo(404); + } + + [Test] + public void Should_map_headers_from_original_response() + { + // given + var response = new Response(); + response.AddHeader("cache-control", "no-cache"); + var httpListenerResponse = CreateHttpListenerResponse(); + + // when + new HttpListenerResponseMapper().Map(response, httpListenerResponse); + + // then + Check.That(httpListenerResponse.Headers).HasSize(1); + Check.That(httpListenerResponse.Headers.Keys).Contains("cache-control"); + Check.That(httpListenerResponse.Headers.Get("cache-control")).Contains("no-cache"); + } + + [Test] + public void Should_map_body_from_original_response() + { + // given + var response = new Response(); + response.Body = "Hello !!!"; + var httpListenerResponse = CreateHttpListenerResponse(); + + // when + new HttpListenerResponseMapper().Map(response, httpListenerResponse); + + // then + var responseMessage = ToResponseMessage(httpListenerResponse); + Check.That(responseMessage).IsNotNull(); + var contentTask = responseMessage.Content.ReadAsStringAsync(); + Check.That(contentTask.Result).IsEqualTo("Hello !!!"); + } + + [TearDown] + public void StopServer() + { + if (_server != null) + { + _server.Stop(); + } + } + + /// + /// Dirty HACK to get HttpListenerResponse instances + /// + /// + /// The . + /// + public HttpListenerResponse CreateHttpListenerResponse() + { + var port = Ports.FindFreeTcpPort(); + var urlPrefix = "http://localhost:" + port + "/"; + var responseReady = new AutoResetEvent(false); + HttpListenerResponse response = null; + _server = new TinyHttpServer( + urlPrefix, + context => + { + response = context.Response; + responseReady.Set(); + }); + _server.Start(); + _responseMsgTask = new HttpClient().GetAsync(urlPrefix); + responseReady.WaitOne(); + return response; + } + + public HttpResponseMessage ToResponseMessage(HttpListenerResponse listenerResponse) + { + listenerResponse.Close(); + _responseMsgTask.Wait(); + return _responseMsgTask.Result; + } + } +} diff --git a/test/WireMock.Net.Tests/Properties/AssemblyInfo.cs b/test/WireMock.Net.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..420b1ae8 --- /dev/null +++ b/test/WireMock.Net.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WireMock.Net.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WireMock.Net.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d8b56d28-33ce-4bef-97d4-7dd546e37f25")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestTests.cs new file mode 100644 index 00000000..5777f1c1 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestTests.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using NFluent; +using NUnit.Framework; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] +// ReSharper disable InconsistentNaming +namespace WireMock.Net.Tests +{ + [TestFixture] + public class RequestTests + { + [Test] + public void Should_handle_empty_query() + { + // given + var request = new Request("/foo", string.Empty, "blabla", "whatever", new Dictionary()); + + // then + Check.That(request.GetParameter("foo")).IsEmpty(); + } + + [Test] + public void Should_parse_query_params() + { + // given + var request = new Request("/foo", "foo=bar&multi=1&multi=2", "blabla", "whatever", new Dictionary()); + + // then + Check.That(request.GetParameter("foo")).Contains("bar"); + Check.That(request.GetParameter("multi")).Contains("1"); + Check.That(request.GetParameter("multi")).Contains("2"); + } + } +} diff --git a/test/WireMock.Net.Tests/RequestsTests.cs b/test/WireMock.Net.Tests/RequestsTests.cs new file mode 100644 index 00000000..d9606928 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestsTests.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using NFluent; +using NUnit.Framework; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] +// ReSharper disable InconsistentNaming +namespace WireMock.Net.Tests +{ + [TestFixture] + public class RequestsTests + { + [Test] + public void Should_specify_requests_matching_given_url() + { + // given + var spec = Requests.WithUrl("/foo"); + + // when + var request = new Request("/foo", string.Empty, "blabla", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_url_prefix() + { + // given + var spec = Requests.WithUrl("/foo*"); + + // when + var request = new Request("/foo/bar", string.Empty, "blabla", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_exclude_requests_not_matching_given_url() + { + // given + var spec = Requests.WithUrl("/foo"); + + // when + var request = new Request("/bar", string.Empty, "blabla", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + + [Test] + public void Should_specify_requests_matching_given_path() + { + // given + var spec = Requests.WithPath("/foo"); + + // when + var request = new Request("/foo", "?param=1", "blabla", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_url_and_method() + { + // given + var spec = Requests.WithUrl("/foo").UsingPut(); + + // when + var request = new Request("/foo", string.Empty, "PUT", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_exclude_requests_matching_given_url_but_not_http_method() + { + // given + var spec = Requests.WithUrl("/foo").UsingPut(); + + // when + var request = new Request("/foo", string.Empty, "POST", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + + [Test] + public void Should_exclude_requests_matching_given_http_method_but_not_url() + { + // given + var spec = Requests.WithUrl("/bar").UsingPut(); + + // when + var request = new Request("/foo", string.Empty, "PUT", "whatever", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + + [Test] + public void Should_specify_requests_matching_given_url_and_headers() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithHeader("X-toto", "tata"); + + // when + var request = new Request("/foo", string.Empty, "PUT", "whatever", new Dictionary { { "X-toto", "tata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_exclude_requests_not_matching_given_headers() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithHeader("X-toto", "tatata"); + + // when + var request = new Request("/foo", string.Empty, "PUT", "whatever", new Dictionary { { "X-toto", "tata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + + [Test] + public void Should_specify_requests_matching_given_header_prefix() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithHeader("X-toto", "tata*"); + + // when + var request = new Request("/foo", string.Empty, "PUT", "whatever", new Dictionary { { "X-toto", "tatata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_body() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithBody(" Hello world! "); + + // when + var request = new Request("/foo", string.Empty, "PUT", "Hello world!", new Dictionary { { "X-toto", "tatata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_body_as_wildcard() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithBody("H*o wor?d!"); + + // when + var request = new Request("/foo", string.Empty, "PUT", "Hello world!", new Dictionary { { "X-toto", "tatata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_exclude_requests_not_matching_given_body() + { + // given + var spec = Requests.WithUrl("/foo").UsingAnyVerb().WithBody(" Hello world! "); + + // when + var request = new Request("/foo", string.Empty, "PUT", "XXXXXXXXXXX", new Dictionary { { "X-toto", "tatata" } }); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + + [Test] + public void Should_specify_requests_matching_given_params() + { + // given + var spec = Requests.WithPath("/foo").WithParam("bar", "1", "2"); + + // when + var request = new Request("/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() + { + // given + var spec = Requests.WithPath("/foo").WithParam("bar", "1"); + + // when + var request = new Request("/foo", string.Empty, "PUT", "XXXXXXXXXXX", new Dictionary()); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + } +} diff --git a/test/WireMock.Net.Tests/WildcardPatternMatcherTests.cs b/test/WireMock.Net.Tests/WildcardPatternMatcherTests.cs new file mode 100644 index 00000000..91486bf4 --- /dev/null +++ b/test/WireMock.Net.Tests/WildcardPatternMatcherTests.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using NFluent; +using NUnit.Framework; + +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1600:ElementsMustBeDocumented", + Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] +[module: + SuppressMessage("StyleCop.CSharp.DocumentationRules", + "SA1633:FileMustHaveHeader", + Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] +// ReSharper disable InconsistentNaming +namespace WireMock.Net.Tests +{ + [TestFixture] + public class WildcardPatternMatcherTests + { + [Test] + public void Should_evaluate_patterns() + { + // Positive Tests + Check.That(WildcardPatternMatcher.MatchWildcardString("*", string.Empty)).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("?", " ")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*", "a")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*", "ab")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("?", "a")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*?", "abc")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("?*", "abc")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*abc", "abc")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*abc*", "abc")).IsTrue(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*a*bc*", "aXXXbc")).IsTrue(); + + // Negative Tests + Check.That(WildcardPatternMatcher.MatchWildcardString("*a", string.Empty)).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("a*", string.Empty)).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("?", string.Empty)).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*b*", "a")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("b*a", "ab")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("??", "a")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*?", string.Empty)).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("??*", "a")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*abc", "abX")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*abc*", "Xbc")).IsFalse(); + Check.That(WildcardPatternMatcher.MatchWildcardString("*a*bc*", "ac")).IsFalse(); + } + } +} diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj new file mode 100644 index 00000000..abf5ae38 --- /dev/null +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {D8B56D28-33CE-4BEF-97D4-7DD546E37F25} + Library + Properties + WireMock.Net.Tests + WireMock.Net.Tests + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll + True + + + ..\..\packages\Moq.4.5.30\lib\net45\Moq.dll + True + + + ..\..\packages\NFluent.1.3.1.0\lib\net40\NFluent.dll + True + + + ..\..\packages\NUnit.3.6.0\lib\net45\nunit.framework.dll + True + + + + + + + + + + + ..\..\src\WireMock\bin\$(Configuration)\net45\WireMock.dll + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WireMock.Net.Tests/packages.config b/test/WireMock.Net.Tests/packages.config new file mode 100644 index 00000000..48aed189 --- /dev/null +++ b/test/WireMock.Net.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file