diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index a2a8b6f7..6171262f 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -1,97 +1,202 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0BB8B634-407A-4610-A91F-11586990767A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net", "src\WireMock.Net\WireMock.Net.csproj", "{D3804228-91F4-4502-9595-39584E5A01AD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Abstractions", "src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj", "{B6269AAC-170A-4346-8B9A-579DED3D9A94}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.RestClient", "src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj", "{B6269AAC-170A-43D6-8B9A-579DED3D9A96}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.StandAlone", "src\WireMock.Net.StandAlone\WireMock.Net.StandAlone.csproj", "{B6269AAC-170A-43D5-8B9A-579DED3D9A95}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Tests", "test\WireMock.Net.Tests\WireMock.Net.Tests.csproj", "{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{985E0ADB-D4B4-473A-AA40-567E279B7946}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - azure-pipelines-ci-linux.yml = azure-pipelines-ci-linux.yml - azure-pipelines-ci.yml = azure-pipelines-ci.yml - azure-pipelines-linux.yml = azure-pipelines-linux.yml - azure-pipelines-nuget.yml = azure-pipelines-nuget.yml - build-info.md = build-info.md - CHANGELOG.md = CHANGELOG.md - Directory.Build.props = Directory.Build.props - GitHubReleaseNotes.txt = GitHubReleaseNotes.txt - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp3", "examples\WireMock.Net.Console.NETCoreApp3\WireMock.Net.Console.NETCoreApp3.csproj", "{8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp2", "examples\WireMock.Net.Console.Proxy.NETCoreApp2\WireMock.Net.Console.Proxy.NETCoreApp2.csproj", "{41C19451-E980-4ED4-A011-DA7A1C23FC05}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}" -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 - {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.Build.0 = Release|Any CPU - {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.Build.0 = Release|Any CPU - {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = Release|Any CPU - {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.Build.0 = Release|Any CPU - {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Release|Any CPU.Build.0 = Release|Any CPU - {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Release|Any CPU.Build.0 = Release|Any CPU - {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D3804228-91F4-4502-9595-39584E5A01AD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} - {B6269AAC-170A-4346-8B9A-579DED3D9A94} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} - {B6269AAC-170A-43D6-8B9A-579DED3D9A96} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} - {B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} - {31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A} - {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5} = {985E0ADB-D4B4-473A-AA40-567E279B7946} - {41C19451-E980-4ED4-A011-DA7A1C23FC05} = {985E0ADB-D4B4-473A-AA40-567E279B7946} - {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0BB8B634-407A-4610-A91F-11586990767A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net", "src\WireMock.Net\WireMock.Net.csproj", "{D3804228-91F4-4502-9595-39584E5A01AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Abstractions", "src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj", "{B6269AAC-170A-4346-8B9A-579DED3D9A94}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.RestClient", "src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj", "{B6269AAC-170A-43D6-8B9A-579DED3D9A96}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.StandAlone", "src\WireMock.Net.StandAlone\WireMock.Net.StandAlone.csproj", "{B6269AAC-170A-43D5-8B9A-579DED3D9A95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Tests", "test\WireMock.Net.Tests\WireMock.Net.Tests.csproj", "{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{985E0ADB-D4B4-473A-AA40-567E279B7946}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + azure-pipelines-ci-linux.yml = azure-pipelines-ci-linux.yml + azure-pipelines-ci.yml = azure-pipelines-ci.yml + azure-pipelines-linux.yml = azure-pipelines-linux.yml + azure-pipelines-nuget.yml = azure-pipelines-nuget.yml + build-info.md = build-info.md + CHANGELOG.md = CHANGELOG.md + Directory.Build.props = Directory.Build.props + GitHubReleaseNotes.txt = GitHubReleaseNotes.txt + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp3", "examples\WireMock.Net.Console.NETCoreApp3\WireMock.Net.Console.NETCoreApp3.csproj", "{8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp2", "examples\WireMock.Net.Console.Proxy.NETCoreApp2\WireMock.Net.Console.Proxy.NETCoreApp2.csproj", "{41C19451-E980-4ED4-A011-DA7A1C23FC05}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp", "examples\WireMock.Net.Console.NETCoreApp\WireMock.Net.Console.NETCoreApp.csproj", "{FE281639-B014-4C8A-96FA-141164A74713}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp", "examples\WireMock.Net.Console.Record.NETCoreApp\WireMock.Net.Console.Proxy.NETCoreApp.csproj", "{1995E414-F197-4AB4-90C2-68D806B5AF59}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.StandAlone.NETCoreApp", "examples\WireMock.Net.StandAlone.NETCoreApp\WireMock.Net.StandAlone.NETCoreApp.csproj", "{10E16614-61CA-48D8-8BDD-664C13913DED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.StandAlone.Net452", "examples\WireMock.Net.StandAlone.Net452\WireMock.Net.StandAlone.Net452.csproj", "{668F689E-57B4-422E-8846-C0FF643CA999}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication.NETCore2", "examples\WireMock.Net.WebApplication\WireMock.Net.WebApplication.NETCore2.csproj", "{049539C1-7A66-4559-AD7A-B1C73B97CBB0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.Proxy.Net452", "examples\WireMock.Net.Console.Proxy.Net452\WireMock.Net.Console.Proxy.Net452.csproj", "{26433A8F-BF01-4962-97EB-81BFFBB61096}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.StandAlone.Net461", "examples\WireMock.Net.StandAlone.Net461\WireMock.Net.StandAlone.Net461.csproj", "{3C279524-DB73-4DE3-BEF1-F2B2958C9F65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "examples\Wiremock.Net.Service\WireMock.Net.Service.csproj", "{7F0B2446-0363-4720-AF46-F47F83B557DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.Net461.Classic", "examples\WireMock.Net.Console.Net461.Classic\WireMock.Net.Console.Net461.Classic.csproj", "{1261BB9B-A7D4-456C-8985-3CE560361B8E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.Net452.Classic", "examples\WireMock.Net.Console.Net452.Classic\WireMock.Net.Console.Net452.Classic.csproj", "{668F689E-57B4-422E-8846-C0FF643CA268}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp2", "examples\WireMock.Net.Console.NETCoreApp2\WireMock.Net.Console.NETCoreApp2.csproj", "{83645809-9E01-4E81-8733-BA9497554ABF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.RequestLogTest", "examples\WireMock.Net.Console.RequestLogTest\WireMock.Net.Console.RequestLogTest.csproj", "{A9D039B9-7509-4CF1-9EFD-87EB82998575}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Client.Net472", "examples\WireMock.Net.Client.Net472\WireMock.Net.Client.Net472.csproj", "{02082E34-DEF2-47D0-AF0B-3326FAA908CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{D3804228-91F4-4502-9595-39584E5AADAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser.ConsoleApp", "examples\WireMock.Net.OpenApiParser.ConsoleApp\WireMock.Net.OpenApiParser.ConsoleApp.csproj", "{5C09FB93-1535-4F92-AF26-21E8A061EE4A}" +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 + {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.Build.0 = Release|Any CPU + {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.Build.0 = Release|Any CPU + {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = Release|Any CPU + {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.Build.0 = Release|Any CPU + {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5}.Release|Any CPU.Build.0 = Release|Any CPU + {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41C19451-E980-4ED4-A011-DA7A1C23FC05}.Release|Any CPU.Build.0 = Release|Any CPU + {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.Build.0 = Release|Any CPU + {FE281639-B014-4C8A-96FA-141164A74713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE281639-B014-4C8A-96FA-141164A74713}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE281639-B014-4C8A-96FA-141164A74713}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE281639-B014-4C8A-96FA-141164A74713}.Release|Any CPU.Build.0 = Release|Any CPU + {1995E414-F197-4AB4-90C2-68D806B5AF59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1995E414-F197-4AB4-90C2-68D806B5AF59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1995E414-F197-4AB4-90C2-68D806B5AF59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1995E414-F197-4AB4-90C2-68D806B5AF59}.Release|Any CPU.Build.0 = Release|Any CPU + {10E16614-61CA-48D8-8BDD-664C13913DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10E16614-61CA-48D8-8BDD-664C13913DED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10E16614-61CA-48D8-8BDD-664C13913DED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10E16614-61CA-48D8-8BDD-664C13913DED}.Release|Any CPU.Build.0 = Release|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA999}.Debug|Any CPU.Build.0 = Debug|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA999}.Release|Any CPU.ActiveCfg = Release|Any CPU + {668F689E-57B4-422E-8846-C0FF643CA999}.Release|Any CPU.Build.0 = Release|Any CPU + {049539C1-7A66-4559-AD7A-B1C73B97CBB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {049539C1-7A66-4559-AD7A-B1C73B97CBB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {049539C1-7A66-4559-AD7A-B1C73B97CBB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {049539C1-7A66-4559-AD7A-B1C73B97CBB0}.Release|Any CPU.Build.0 = Release|Any CPU + {26433A8F-BF01-4962-97EB-81BFFBB61096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26433A8F-BF01-4962-97EB-81BFFBB61096}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26433A8F-BF01-4962-97EB-81BFFBB61096}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26433A8F-BF01-4962-97EB-81BFFBB61096}.Release|Any CPU.Build.0 = Release|Any CPU + {3C279524-DB73-4DE3-BEF1-F2B2958C9F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C279524-DB73-4DE3-BEF1-F2B2958C9F65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C279524-DB73-4DE3-BEF1-F2B2958C9F65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C279524-DB73-4DE3-BEF1-F2B2958C9F65}.Release|Any CPU.Build.0 = Release|Any CPU + {7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU + {1261BB9B-A7D4-456C-8985-3CE560361B8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1261BB9B-A7D4-456C-8985-3CE560361B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1261BB9B-A7D4-456C-8985-3CE560361B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1261BB9B-A7D4-456C-8985-3CE560361B8E}.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 + {83645809-9E01-4E81-8733-BA9497554ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83645809-9E01-4E81-8733-BA9497554ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83645809-9E01-4E81-8733-BA9497554ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83645809-9E01-4E81-8733-BA9497554ABF}.Release|Any CPU.Build.0 = Release|Any CPU + {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.Build.0 = Release|Any CPU + {02082E34-DEF2-47D0-AF0B-3326FAA908CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02082E34-DEF2-47D0-AF0B-3326FAA908CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02082E34-DEF2-47D0-AF0B-3326FAA908CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02082E34-DEF2-47D0-AF0B-3326FAA908CE}.Release|Any CPU.Build.0 = Release|Any CPU + {D3804228-91F4-4502-9595-39584E5AADAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5AADAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5AADAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3804228-91F4-4502-9595-39584E5AADAD}.Release|Any CPU.Build.0 = Release|Any CPU + {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D3804228-91F4-4502-9595-39584E5A01AD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {B6269AAC-170A-4346-8B9A-579DED3D9A94} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {B6269AAC-170A-43D6-8B9A-579DED3D9A96} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A} + {8C424EAF-8269-46A2-9FF1-F6D4EADB5CD5} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {41C19451-E980-4ED4-A011-DA7A1C23FC05} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {FE281639-B014-4C8A-96FA-141164A74713} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {1995E414-F197-4AB4-90C2-68D806B5AF59} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {10E16614-61CA-48D8-8BDD-664C13913DED} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {668F689E-57B4-422E-8846-C0FF643CA999} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {049539C1-7A66-4559-AD7A-B1C73B97CBB0} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {26433A8F-BF01-4962-97EB-81BFFBB61096} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {3C279524-DB73-4DE3-BEF1-F2B2958C9F65} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {1261BB9B-A7D4-456C-8985-3CE560361B8E} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {668F689E-57B4-422E-8846-C0FF643CA268} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {83645809-9E01-4E81-8733-BA9497554ABF} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {02082E34-DEF2-47D0-AF0B-3326FAA908CE} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {D3804228-91F4-4502-9595-39584E5AADAD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} + EndGlobalSection +EndGlobal diff --git a/examples/WireMock.Net.Client/WireMock.Net.Client.csproj b/examples/WireMock.Net.Client/WireMock.Net.Client.csproj index da065585..59ae7f93 100644 --- a/examples/WireMock.Net.Client/WireMock.Net.Client.csproj +++ b/examples/WireMock.Net.Client/WireMock.Net.Client.csproj @@ -14,7 +14,6 @@ - \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NETCoreApp2/Program.cs b/examples/WireMock.Net.Console.NETCoreApp2/Program.cs new file mode 100644 index 00000000..36f57470 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp2/Program.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Linq; +using System.Reflection; +using log4net; +using log4net.Config; +using log4net.Repository; +using WireMock.Net.ConsoleApplication; + +namespace WireMock.Net.Console.NETCoreApp2 +{ + static class Program + { + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + + static void Main(params string[] args) + { + XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); + + foreach (var file in Directory.GetFiles("__admin").Where(f => !f.StartsWith("wiremock"))) + { + File.Delete(file); + } + + MainApp.Run(); + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj b/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj index 83d0db3f..b92b24a1 100644 --- a/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj +++ b/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj @@ -9,7 +9,6 @@ - @@ -22,6 +21,10 @@ + + + + @@ -49,6 +52,9 @@ PreserveNewest + + PreserveNewest + \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/wiremock-petstore-openapi3.json b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/wiremock-petstore-openapi3.json new file mode 100644 index 00000000..e8005075 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/wiremock-petstore-openapi3.json @@ -0,0 +1,602 @@ +[ + { + "Guid": "9d2fde55-e420-4724-bf40-616e8aeaf53e", + "Request": { + "Path": "/pet", + "Methods": [ + "PUT" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "1f7acffa-05f4-4640-bda4-4c71c8d5e6e3", + "Request": { + "Path": "/pet", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "a04ed51d-ad5b-4c69-b22f-d0eaeea18bc1", + "Request": { + "Path": "/pet/findByStatus", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "status", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": [ + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + } + ], + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "8d5df48a-05fb-4861-816f-3f77adf5562f", + "Request": { + "Path": "/pet/findByTags", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "tags", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": [ + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + } + ], + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "065f790b-125c-419e-8fbd-3616bf09b142", + "Request": { + "Path": "/pet/42", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "737aaddd-5bab-489d-914b-deb4ba773539", + "Request": { + "Path": "/pet/42", + "Methods": [ + "POST" + ], + "Params": [ + { + "Name": "name", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + }, + { + "Name": "status", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "0619896c-c3b3-4a30-903e-59792134898c", + "Request": { + "Path": "/pet/42", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "9aaa6a6d-ca4b-4da3-8a9a-844e3af02272", + "Request": { + "Path": "/pet/42/uploadImage", + "Methods": [ + "POST" + ], + "Params": [ + { + "Name": "additionalMetadata", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "code": 42, + "type": "example-string", + "message": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "085ef9c2-425d-45c2-9311-d3e4697c407f", + "Request": { + "Path": "/store/inventory", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": {}, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "a68c50e9-418c-45e7-8340-c0426cf5b87c", + "Request": { + "Path": "/store/order", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "petId": 42, + "quantity": 42, + "shipDate": "2020-06-16T12:54:18.885+00:00", + "status": "example-string", + "complete": true + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "16061ffc-97a5-4419-874f-66d857998f76", + "Request": { + "Path": "/store/order/42", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "petId": 42, + "quantity": 42, + "shipDate": "2020-06-16T12:54:18.887+00:00", + "status": "example-string", + "complete": true + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "55c071d3-eeb5-4a9c-8692-486585e45e2e", + "Request": { + "Path": "/store/order/42", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "2d9d895d-58e9-4734-92f4-903e2b364dda", + "Request": { + "Path": "/user", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 0, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "c17eeba0-da58-4128-9ae6-33a6a31971e2", + "Request": { + "Path": "/user/createWithList", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "c95da645-9449-438e-a8c8-0278ea514558", + "Request": { + "Path": "/user/login", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "username", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + }, + { + "Name": "password", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": "example-string", + "Headers": { + "X-Rate-Limit": "example-string", + "X-Expires-After": "example-string", + "Content-Type": "application/json" + } + } + }, + { + "Guid": "bd05033f-1189-4d89-8cdc-f681399a46b3", + "Request": { + "Path": "/user/logout", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 0 + } + }, + { + "Guid": "7c1d49d4-e9f0-49b9-b898-f8c7d55ae472", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "1b7c4cd8-b251-480a-982e-c42d40dbfd4e", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "PUT" + ] + }, + "Response": { + "StatusCode": 0 + } + }, + { + "Guid": "8a86e775-4bf9-490d-a52f-641458c256f7", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + } +] \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs new file mode 100644 index 00000000..c82e73a3 --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs @@ -0,0 +1,35 @@ +using Microsoft.OpenApi.Readers; +using Newtonsoft.Json; +using System; +using System.IO; + +namespace WireMock.Net.OpenApiParser.ConsoleApp +{ + class Program + { + private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.Indented + }; + + static void Main(string[] args) + { + Run.RunServer("petstore-openapi3.json"); + + //IWireMockOpenApiParser parser = new WireMockOpenApiParser(); + + //var petStoreModels = parser.FromStream(File.OpenRead("petstore-openapi3.json"), out OpenApiDiagnostic diagnostic1); + //string petStoreJson = JsonConvert.SerializeObject(petStoreModels, Settings); + // File.WriteAllText("../../../wiremock-petstore-openapi3.json", petStoreJson); + + //Run.RunServer(petStoreModels); + + //var mappingModels2 = parser.FromStream(File.OpenRead("infura.yaml"), out OpenApiDiagnostic diagnostic2); + //Console.WriteLine(JsonConvert.SerializeObject(diagnostic2, Settings)); + + //string json2 = JsonConvert.SerializeObject(mappingModels2, Settings); + //Console.WriteLine(json2); + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/Run.cs b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Run.cs new file mode 100644 index 00000000..84a9ab4b --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Run.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WireMock.Admin.Mappings; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; +using WireMock.Net.OpenApiParser.Extensions; + +namespace WireMock.Net.OpenApiParser.ConsoleApp +{ + public static class Run + { + public static void RunServer(string path) + { + string url1 = "http://localhost:9091/"; + + var server = WireMockServer.Start(new WireMockServerSettings + { + AllowCSharpCodeMatcher = true, + Urls = new[] { url1 }, + StartAdminInterface = true, + ReadStaticMappings = false, + WatchStaticMappings = false, + WatchStaticMappingsInSubdirectories = false, + Logger = new WireMockConsoleLogger(), + }); + Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); + + server.SetBasicAuthentication("a", "b"); + + server.WithMappingFromOpenApiFile(path, out var diag); + + Console.WriteLine("Press any key to stop the server"); + System.Console.ReadKey(); + server.Stop(); + } + + public static void RunServer(IEnumerable mappings) + { + string url1 = "http://localhost:9091/"; + + var server = WireMockServer.Start(new WireMockServerSettings + { + AllowCSharpCodeMatcher = true, + Urls = new[] { url1 }, + StartAdminInterface = true, + ReadStaticMappings = false, + WatchStaticMappings = false, + WatchStaticMappingsInSubdirectories = false, + Logger = new WireMockConsoleLogger(), + }); + Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); + + server.SetBasicAuthentication("a", "b"); + + server.WithMapping(mappings.ToArray()); + + Console.WriteLine("Press any key to stop the server"); + System.Console.ReadKey(); + server.Stop(); + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj b/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj new file mode 100644 index 00000000..9d93515e --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj @@ -0,0 +1,26 @@ + + + + Exe + netcoreapp2.1 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/infura.yaml b/examples/WireMock.Net.OpenApiParser.ConsoleApp/infura.yaml new file mode 100644 index 00000000..e3f1ded6 --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/infura.yaml @@ -0,0 +1,337 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: "Infura API" + description: APIs for the Ethereum community by the Infura team, a project of ConsenSys + contact: + name: Infura Team + email: infura@infura.io + url: https://infura.io +host: api.infura.io +basePath: / +schemes: + - https +consumes: + - application/json +produces: + - application/json +paths: + /v1/jsonrpc/{network}/methods: + get: + description: | + The JSON-RPC methods supported by the `/v1/jsonrpc/{network}/{method}` (GET) and `/v1/jsonrpc/{network}` (POST) endpoints. + parameters: + - name: network + in: path + description: Ethereum network in lowercase + required: true + type: string + enum: + - mainnet + - ropsten + - kovan + - rinkeby + responses: + 200: + description: Methods response + schema: + $ref: '#/definitions/MethodsResponse' + 500: + description: Server error + /v1/jsonrpc/{network}/{method}: + get: + description: | + A request using an "HTTP GET-compatible" (non-state-changing) JSON-RPC method. Most Ethereum JSON-RPC methods can be described in this way, since they query the blockchain for various pieces of information. Use the `/v1/jsonrpc/{network}/methods` endpoint to get the list of permitted methods. + parameters: + - name: network + in: path + description: Ethereum network in lowercase + required: true + type: string + enum: + - mainnet + - ropsten + - kovan + - rinkeby + - name: method + in: path + description: JSON-RPC method. Use the `/v1/jsonrpc/{network}/methods` endpoint to get the list of permitted methods. + required: true + type: string + - name: params + in: query + description: This is the `params` field that would normally be part of the JSON-RPC POST body. Use the exact same format. If it's omitted, it will default to an empty array. + required: false + type: array + items: + type: string + responses: + 200: + description: JSON-RPC response + schema: + $ref: '#/definitions/JSONRPCResponse' + 400: + description: Bad JSON in `params` query parameter + 404: + description: JSON-RPC method is not a valid GET method + 500: + description: Server error + 502: + description: Ethereum client error + /v1/jsonrpc/{network}: + post: + description: | + A request using an "HTTP POST-compatible" (state-changing) JSON-RPC method. Use the `/v1/jsonrpc/{network}/methods` endpoint to get the list of permitted methods. Use the regular Ethereum JSON-RPC format for the POST body. + parameters: + - name: network + in: path + description: Ethereum network in lowercase + required: true + type: string + enum: + - mainnet + - ropsten + - kovan + - rinkeby + - name: payload + in: body + description: Regular JSON-RPC payload (POST body) + required: true + schema: + $ref: '#/definitions/JSONRPCRequest' + responses: + 200: + description: JSON-RPC response + schema: + $ref: '#/definitions/JSONRPCResponse' + 400: + description: Bad JSON in POST body or missing Content-Type header + 404: + description: JSON-RPC method is not a valid POST method + 500: + description: Server error + 502: + description: Ethereum client error + /v1/ticker/symbols: + get: + description: | + Get a list of supported symbols (currency pairs), including fiat, crypto, and tokens + responses: + 200: + description: Symbols response + schema: + $ref: '#/definitions/SymbolsResponse' + /v1/ticker/{symbol}: + get: + description: | + Get pricing (ticker) data for various currency pairs (fiat, crypto, and tokens) using data from several exchanges. This endpoint shows the price at the exchange with the most volume for the symbol. Use the `/v1/ticker/symbols` endpoint for the full list of supported symbols. + parameters: + - name: symbol + in: path + description: Ticker symbol (currency pair) + required: true + type: string + responses: + 200: + description: Ticker response + schema: + $ref: '#/definitions/TickerResponse' + /v1/ticker/{symbol}/full: + get: + description: | + Get pricing (ticker) data for various currency pairs (fiat, crypto, and tokens) using data from several exchanges. This endpoint shows the price at various exchanges where the symbol is traded. Use the `/v1/ticker/symbols` endpoint for the full list of supported symbols. + parameters: + - name: symbol + in: path + description: Ticker symbol (currency pair) + required: true + type: string + responses: + 200: + description: Full ticker response + schema: + $ref: '#/definitions/TickerFullResponse' + /v1/blacklist: + get: + description: | + Return a blacklist of phishing sites. This list is maintained by GitHub user 409H at https://github.com/409H/EtherAddressLookup/blob/master/blacklists/domains.json . + responses: + 200: + description: List of blacklisted phishing domains + schema: + type: array + items: + type: string + 502: + description: GitHub is having issues + /v2/blacklist: + get: + description: | + Return a blacklist of phishing sites, as well as a whitelist and a fuzzylist. This list is maintained by the MetaMask project at https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json . + responses: + 200: + description: Phishing blacklist, whitelist, and fuzzylist + schema: + $ref: '#/definitions/BlacklistResponse' + 502: + description: GitHub is having issues + +definitions: + MethodsResponse: + type: object + properties: + get: + type: array + description: List of methods supported by the /v1/jsonrpc/{network}/{method} endpoint (GET) + items: + type: string + post: + type: array + description: List of methods supported by the /v1/jsonrpc/{network} endpoint (POST) + items: + type: string + required: + - get + - post + JSONRPCRequest: + type: object + properties: + jsonrpc: + type: string + description: JSON-RPC version + enum: + - "2.0" + id: + type: integer + description: JSON-RPC request ID + method: + type: string + description: Ethereum JSON-RPC method + enum: + - eth_sendRawTransaction + - eth_estimateGas + - eth_submitWork + - eth_submitHashrate + params: + type: array + description: JSON-RPC parameters (can be empty) + required: + - jsonrpc + - id + - method + - params + JSONRPCResponse: + type: object + properties: + jsonrpc: + type: string + description: JSON-RPC version + enum: + - "2.0" + id: + type: integer + description: JSON-RPC request ID + result: + type: string + description: JSON-RPC result (can also be an object) + required: + - jsonrpc + - id + SymbolsResponse: + type: object + properties: + symbols: + type: array + description: List of supported symbols (currency pairs) + items: + type: string + TickerResponse: + type: object + properties: + base: + type: string + description: Currency pair base + quote: + type: string + description: Currency pair quote + bid: + type: number + description: Bid at the exchange with the most volume + ask: + type: number + description: Ask at the exchange with the most volume + exchange: + type: string + description: The exchange with the most volume + volume: + type: number + description: Volume at the exchange with the most volume + num_exchanges: + type: integer + description: Number of exchanges queried + total_volume: + type: number + description: Total volume across all exchanges queried + timestamp: + type: integer + description: Unix timestamp + TickerFullResponse: + type: object + properties: + base: + type: string + description: Currency pair base + quote: + type: string + description: Currency pair quote + tickers: + type: array + description: List of tickers at various exchanges + items: + type: object + properties: + bid: + type: number + description: Bid + ask: + type: number + description: Ask + exchange: + type: string + description: Exchange + volume: + type: number + description: Volume + timestamp: + type: integer + description: Unix timestamp + BlacklistResponse: + type: object + properties: + version: + type: integer + description: Version + tolerance: + type: integer + description: Tolerance + fuzzylist: + description: Fuzzylist + type: array + items: + type: string + whitelist: + description: Whitelist + type: array + items: + type: string + blacklist: + description: Blacklist + type: array + items: + type: string + required: + - version + - tolerance + - fuzzylist + - whitelist + - blacklist \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore-openapi3.json b/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore-openapi3.json new file mode 100644 index 00000000..9f4c4596 --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore-openapi3.json @@ -0,0 +1,840 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) ", + "termsOfService": "http://swagger.io/terms/", + "contact": { "email": "apiteam@swagger.io" }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.4" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ { "url": "/api/v3" } ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Operations about user" + }, + { + "name": "user", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ "pet" ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Pet" } } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } + } + }, + "400": { "description": "Invalid ID supplied" }, + "404": { "description": "Pet not found" }, + "405": { "description": "Validation exception" } + }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + }, + "post": { + "tags": [ "pet" ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Pet" } } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } + } + }, + "405": { "description": "Invalid input" } + }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ "pet" ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ "available", "pending", "sold" ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/Pet" } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/Pet" } + } + } + } + }, + "400": { "description": "Invalid status value" } + }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ "pet" ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { "type": "string" } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/Pet" } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/Pet" } + } + } + } + }, + "400": { "description": "Invalid tag value" } + }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ "pet" ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } + } + }, + "400": { "description": "Invalid ID supplied" }, + "404": { "description": "Pet not found" } + }, + "security": [ + { "api_key": [] }, + { "petstore_auth": [ "write:pets", "read:pets" ] } + ] + }, + "post": { + "tags": [ "pet" ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { "type": "string" } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { "type": "string" } + } + ], + "responses": { "405": { "description": "Invalid input" } }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + }, + "delete": { + "tags": [ "pet" ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { "type": "string" } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { "400": { "description": "Invalid pet value" } }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ "pet" ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } + } + }, + "security": [ { "petstore_auth": [ "write:pets", "read:pets" ] } ] + } + }, + "/store/inventory": { + "get": { + "tags": [ "store" ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ { "api_key": [] } ] + } + }, + "/store/order": { + "post": { + "tags": [ "store" ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/Order" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/Order" } }, + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Order" } } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } } + }, + "405": { "description": "Invalid input" } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ "store" ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/Order" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } + } + }, + "400": { "description": "Invalid ID supplied" }, + "404": { "description": "Order not found" } + } + }, + "delete": { + "tags": [ "store" ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { "description": "Invalid ID supplied" }, + "404": { "description": "Order not found" } + } + } + }, + "/user": { + "post": { + "tags": [ "user" ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/User" } } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/User" } } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ "user" ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/User" } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/User" } } + } + }, + "default": { "description": "successful operation" } + } + } + }, + "/user/login": { + "get": { + "tags": [ "user" ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { "type": "string" } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when toekn expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { "schema": { "type": "string" } }, + "application/json": { "schema": { "type": "string" } } + } + }, + "400": { "description": "Invalid username/password supplied" } + } + } + }, + "/user/logout": { + "get": { + "tags": [ "user" ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { "default": { "description": "successful operation" } } + } + }, + "/user/{username}": { + "get": { + "tags": [ "user" ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/User" } } + } + }, + "400": { "description": "Invalid username supplied" }, + "404": { "description": "User not found" } + } + }, + "put": { + "tags": [ "user" ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/User" } }, + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/User" } } + } + }, + "responses": { "default": { "description": "successful operation" } } + }, + "delete": { + "tags": [ "user" ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "400": { "description": "Invalid username supplied" }, + "404": { "description": "User not found" } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ "placed", "approved", "delivered" ] + }, + "complete": { "type": "boolean" } + }, + "xml": { "name": "order" } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { "$ref": "#/components/schemas/Address" } + } + }, + "xml": { "name": "customer" } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { "name": "address" } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { "name": "category" } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { "name": "user" } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { "type": "string" } + }, + "xml": { "name": "tag" } + }, + "Pet": { + "required": [ "name", "photoUrls" ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { "$ref": "#/components/schemas/Category" }, + "photoUrls": { + "type": "array", + "xml": { "wrapped": true }, + "items": { + "type": "string", + "xml": { "name": "photoUrl" } + } + }, + "tags": { + "type": "array", + "xml": { "wrapped": true }, + "items": { "$ref": "#/components/schemas/Tag" } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ "available", "pending", "sold" ] + } + }, + "xml": { "name": "pet" } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { "type": "string" }, + "message": { "type": "string" } + }, + "xml": { "name": "##default" } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, + "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/User" } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore.yml b/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore.yml new file mode 100644 index 00000000..9383878a --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/petstore.yml @@ -0,0 +1,730 @@ +swagger: '2.0' +info: + description: 'This is a sample server Petstore server. Copied from https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/test/resources/2_0/petstore.yaml.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache-2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +host: petstore.swagger.io +basePath: /v2 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' +schemes: + - http +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + produces: + - application/xml + - application/json + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + collectionFormat: csv + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.' + operationId: findPetsByTags + produces: + - application/xml + - application/json + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + type: array + items: + type: string + collectionFormat: csv + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + type: integer + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + consumes: + - application/x-www-form-urlencoded + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: integer + format: int64 + - name: name + in: formData + description: Updated name of the pet + required: false + type: string + - name: status + in: formData + description: Updated status of the pet + required: false + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + produces: + - application/xml + - application/json + parameters: + - name: api_key + in: header + required: false + type: string + - name: petId + in: path + description: Pet id to delete + required: true + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + consumes: + - multipart/form-data + produces: + - application/json + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + type: integer + format: int64 + - name: additionalMetadata + in: formData + description: Additional data to pass to server + required: false + type: string + - name: file + in: formData + description: file to upload + required: false + type: file + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + produces: + - application/json + parameters: [] + responses: + '200': + description: successful operation + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: order placed for purchasing the pet + required: true + schema: + $ref: '#/definitions/Order' + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid Order + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions' + operationId: getOrderById + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + type: integer + maximum: 5 + minimum: 1 + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: deleteOrder + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Created user object + required: true + schema: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: query + description: The user name for login + required: true + type: string + - name: password + in: query + description: The password for login in clear text + required: true + type: string + responses: + '200': + description: successful operation + schema: + type: string + headers: + X-Rate-Limit: + type: integer + format: int32 + description: calls per hour allowed by the user + X-Expires-After: + type: string + format: date-time + description: date in UTC when toekn expires + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + produces: + - application/xml + - application/json + parameters: [] + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing.' + required: true + type: string + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + type: string + - in: body + name: body + description: Updated user object + required: true + schema: + $ref: '#/definitions/User' + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + flow: implicit + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header +definitions: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + #issue: https://github.com/swagger-api/swagger-codegen/issues/7980 + Amount: + type: object + description: > + some description + properties: + value: + format: double + type: number + minimum: 0.01 + maximum: 1000000000000000 + description: > + some description + currency: + $ref: '#/definitions/Currency' + required: + - value + - currency + Currency: + type: string + pattern: '^[A-Z]{3,3}$' + description: > + some description +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/wiremock-petstore-openapi3.json b/examples/WireMock.Net.OpenApiParser.ConsoleApp/wiremock-petstore-openapi3.json new file mode 100644 index 00000000..aa4400aa --- /dev/null +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/wiremock-petstore-openapi3.json @@ -0,0 +1,602 @@ +[ + { + "Guid": "532889c2-f84d-4dc8-b847-9ea2c6aca7d5", + "Request": { + "Path": "/pet", + "Methods": [ + "PUT" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "6e8cd307-28aa-411f-a7df-3a197a7a53b1", + "Request": { + "Path": "/pet", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "edf4cf89-2bfb-4207-bb97-d739834319bb", + "Request": { + "Path": "/pet/findByStatus", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "status", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": [ + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + } + ], + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "8c7a6324-1108-4df6-8fd6-53e062222724", + "Request": { + "Path": "/pet/findByTags", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "tags", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": [ + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + }, + { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": [ + "example-string", + "example-string", + "example-string" + ], + "tags": [ + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + }, + { + "id": 42, + "name": "example-string" + } + ], + "status": "example-string" + } + ], + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "2335189b-89f0-4559-a980-7930a9543df8", + "Request": { + "Path": "/pet/42", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "name": "example-string", + "category": { + "id": 42, + "name": "example-string" + }, + "photoUrls": "example-string", + "tags": "example-string", + "status": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "7d3ca67b-a4eb-4108-8f3c-9ba63b42e6d4", + "Request": { + "Path": "/pet/42", + "Methods": [ + "POST" + ], + "Params": [ + { + "Name": "name", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + }, + { + "Name": "status", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "96e43f42-deb0-4dd7-a137-3f69eb3eb884", + "Request": { + "Path": "/pet/42", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "ba7719dc-25a0-4481-b013-ed3bdf095472", + "Request": { + "Path": "/pet/42/uploadImage", + "Methods": [ + "POST" + ], + "Params": [ + { + "Name": "additionalMetadata", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "code": 42, + "type": "example-string", + "message": "example-string" + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "79f902a1-797e-4edd-ad30-91fcbe6ae065", + "Request": { + "Path": "/store/inventory", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": {}, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "65abf25e-30cf-483e-b1ef-333095e13e9b", + "Request": { + "Path": "/store/order", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "petId": 42, + "quantity": 42, + "shipDate": "2020-06-17T09:56:32.715+00:00", + "status": "example-string", + "complete": true + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "5f0eba3c-1bda-48f9-a554-042a4b9f0cb6", + "Request": { + "Path": "/store/order/42", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "petId": 42, + "quantity": 42, + "shipDate": "2020-06-17T09:56:32.716+00:00", + "status": "example-string", + "complete": true + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "55307736-9c16-42be-a1a4-723c01f3502a", + "Request": { + "Path": "/store/order/42", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "6c3be046-6880-414e-bbe5-0f57e901b96c", + "Request": { + "Path": "/user", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 0, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "e973fb9b-b596-4605-abd1-bd99fb718b58", + "Request": { + "Path": "/user/createWithList", + "Methods": [ + "POST" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "2d1fd4ed-e186-42d4-b2ea-5cc5cd46e931", + "Request": { + "Path": "/user/login", + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "username", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + }, + { + "Name": "password", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "example-string" + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": "example-string", + "Headers": { + "X-Rate-Limit": "example-string", + "X-Expires-After": "example-string", + "Content-Type": "application/json" + } + } + }, + { + "Guid": "916e527a-36cc-44fb-983f-16e3a5eb2e4c", + "Request": { + "Path": "/user/logout", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 0 + } + }, + { + "Guid": "b4a95c37-df9e-4844-8b46-3a84fce23ce2", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "GET" + ] + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "id": 42, + "username": "example-string", + "firstName": "example-string", + "lastName": "example-string", + "email": "example-string", + "password": "example-string", + "phone": "example-string", + "userStatus": 42 + }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "e6e77a13-f470-4543-bbff-483a4d7454b3", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "PUT" + ] + }, + "Response": { + "StatusCode": 0 + } + }, + { + "Guid": "c371d65b-896a-4782-bde5-00af37e401f2", + "Request": { + "Path": "/user/example-string", + "Methods": [ + "DELETE" + ] + }, + "Response": { + "StatusCode": 200 + } + } +] \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs new file mode 100644 index 00000000..fe24f15e --- /dev/null +++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using JetBrains.Annotations; +using WireMock.Admin.Mappings; + +namespace WireMock.Server +{ + /// + /// The fluent mock server interface. + /// + public interface IWireMockServer : IDisposable + { + /// + /// Gets a value indicating whether this server is started. + /// + bool IsStarted { get; } + + //IEnumerable LogEntries { get; } + + /// + /// Gets the mappings as MappingModels. + /// + IEnumerable MappingModels { get; } + + //IEnumerable Mappings { get; } + + /// + /// Gets the ports. + /// + List Ports { get; } + + /// + /// Gets the urls. + /// + string[] Urls { get; } + + //ConcurrentDictionary Scenarios { get; } + + /// + /// Occurs when [log entries changed]. + /// + event NotifyCollectionChangedEventHandler LogEntriesChanged; + + /// + /// Adds the catch all mapping. + /// + void AddCatchAllMapping(); + + /// + /// The add request processing delay. + /// + /// The delay. + void AddGlobalProcessingDelay(TimeSpan delay); + + /// + /// Allows the partial mapping. + /// + void AllowPartialMapping(bool allow = true); + + /// + /// Deletes a LogEntry. + /// + /// The unique identifier. + bool DeleteLogEntry(Guid guid); + + /// + /// Deletes the mapping. + /// + /// The unique identifier. + bool DeleteMapping(Guid guid); + + //IEnumerable FindLogEntries([NotNull] params IRequestMatcher[] matchers); + + //IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false); + + /// + /// Reads a static mapping file and adds or updates the mapping. + /// + /// The path. + bool ReadStaticMappingAndAddOrUpdate([NotNull] string path); + + /// + /// Reads the static mappings from a folder. + /// + /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + void ReadStaticMappings([CanBeNull] string folder = null); + + /// + /// Removes the basic authentication. + /// + void RemoveBasicAuthentication(); + + /// + /// Resets LogEntries and Mappings. + /// + void Reset(); + + /// + /// Resets the Mappings. + /// + void ResetMappings(); + + /// + /// Resets all Scenarios. + /// + void ResetScenarios(); + + /// + /// Resets the LogEntries. + /// + void ResetLogEntries(); + + /// + /// Saves the static mappings. + /// + /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + void SaveStaticMappings([CanBeNull] string folder = null); + + /// + /// Sets the basic authentication. + /// + /// The username. + /// The password. + void SetBasicAuthentication([NotNull] string username, [NotNull] string password); + + /// + /// Sets the maximum RequestLog count. + /// + /// The maximum RequestLog count. + void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount); + + /// + /// Sets RequestLog expiration in hours. + /// + /// The RequestLog expiration in hours. + void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration); + + /// + /// Stop this server. + /// + void Stop(); + + /// + /// Watches the static mappings for changes. + /// + /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + void WatchStaticMappings([CanBeNull] string folder = null); + + /// + /// Register the mappings (via ). + /// + /// The MappingModels + IWireMockServer WithMapping(params MappingModel[] mappings); + + /// + /// Register the mappings (via json string). + /// + /// The mapping(s) as json string. + IWireMockServer WithMapping(string mappings); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj index 71818640..b91bb5af 100644 --- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj +++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs new file mode 100644 index 00000000..2307f7fb --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs @@ -0,0 +1,92 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using WireMock.Net.OpenApiParser.Types; + +namespace WireMock.Net.OpenApiParser.Extensions +{ + internal static class OpenApiSchemaExtensions + { + /// + /// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger + /// + public static bool TryGetXNullable(this OpenApiSchema schema, out bool value) + { + value = false; + + if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean) + { + value = openApiBoolean.Value; + return true; + } + + return false; + } + + public static SchemaType GetSchemaType(this OpenApiSchema schema) + { + switch (schema?.Type) + { + case "object": + return SchemaType.Object; + + case "array": + return SchemaType.Array; + + case "integer": + return SchemaType.Integer; + + case "number": + return SchemaType.Number; + + case "boolean": + return SchemaType.Boolean; + + case "string": + return SchemaType.String; + + case "file": + return SchemaType.File; + + default: + return SchemaType.Unknown; + } + } + + public static SchemaFormat GetSchemaFormat(this OpenApiSchema schema) + { + switch (schema?.Format) + { + case "float": + return SchemaFormat.Float; + + case "double": + return SchemaFormat.Double; + + case "int32": + return SchemaFormat.Int32; + + case "int64": + return SchemaFormat.Int64; + + case "date": + return SchemaFormat.Date; + + case "date-time": + return SchemaFormat.DateTime; + + case "password": + return SchemaFormat.Password; + + case "byte": + return SchemaFormat.Byte; + + case "binary": + return SchemaFormat.Binary; + + default: + return SchemaFormat.Undefined; + } + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs new file mode 100644 index 00000000..eb3eedd0 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using WireMock.Server; + +namespace WireMock.Net.OpenApiParser.Extensions +{ + public static class WireMockServerExtensions + { + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. + /// + /// The WireMockServer instance + /// Path containing OpenAPI file to parse and use the mappings. + /// Returns diagnostic object containing errors detected during parsing + public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + var mappings = new WireMockOpenApiParser().FromFile(path, out diagnostic); + + return server.WithMapping(mappings.ToArray()); + } + + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. + /// + /// The WireMockServer instance + /// Stream containing OpenAPI description to parse and use the mappings. + /// Returns diagnostic object containing errors detected during parsing + public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic) + { + var mappings = new WireMockOpenApiParser().FromStream(stream, out diagnostic); + + return server.WithMapping(mappings.ToArray()); + } + + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 document. + /// + /// The WireMockServer instance + /// The OpenAPI document to use as mappings. + public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document) + { + var mappings = new WireMockOpenApiParser().FromDocument(document); + + return server.WithMapping(mappings.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs new file mode 100644 index 00000000..9cc3cb40 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using WireMock.Admin.Mappings; + +namespace WireMock.Net.OpenApiParser +{ + /// + /// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels. + /// + public interface IWireMockOpenApiParser + { + /// + /// Generate from an . + /// + /// The source OpenApiDocument + /// MappingModel + IEnumerable FromDocument(OpenApiDocument document); + + /// + /// Generate from a . + /// + /// The source stream + /// OpenApiDiagnostic output + /// MappingModel + IEnumerable FromStream(Stream stream, out OpenApiDiagnostic diagnostic); + + /// + /// Generate from a file-path. + /// + /// The path to read the OpenApi/Swagger/V2/V3 or Raml file. + /// OpenApiDiagnostic output + /// MappingModel + IEnumerable FromFile(string path, out OpenApiDiagnostic diagnostic); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs b/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs new file mode 100644 index 00000000..20e6b017 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs @@ -0,0 +1,25 @@ +namespace WireMock.Net.OpenApiParser.Types +{ + internal enum SchemaFormat + { + Float, + + Double, + + Int32, + + Int64, + + Date, + + DateTime, + + Password, + + Byte, + + Binary, + + Undefined + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs b/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs new file mode 100644 index 00000000..f6152347 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs @@ -0,0 +1,21 @@ +namespace WireMock.Net.OpenApiParser.Types +{ + internal enum SchemaType + { + Object, + + Array, + + String, + + Integer, + + Number, + + Boolean, + + File, + + Unknown + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs new file mode 100644 index 00000000..d7ef7435 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; + +namespace WireMock.Net.OpenApiParser.Utils +{ + internal static class DateTimeUtils + { + public static string ToRfc3339DateTime(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo); + } + + public static string ToRfc3339Date(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs new file mode 100644 index 00000000..87f1b25b --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.OpenApi.Models; +using WireMock.Net.OpenApiParser.Extensions; +using WireMock.Net.OpenApiParser.Types; + +namespace WireMock.Net.OpenApiParser.Utils +{ + internal static class ExampleValueGenerator + { + public static object GetExampleValue(OpenApiSchema schema) + { + switch (schema?.GetSchemaType()) + { + case SchemaType.Boolean: + return true; + + case SchemaType.Integer: + return 42; + + case SchemaType.Number: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Float: + return 4.2f; + + default: + return 4.2d; + } + + default: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Date: + return DateTimeUtils.ToRfc3339Date(DateTime.UtcNow); + + case SchemaFormat.DateTime: + return DateTimeUtils.ToRfc3339DateTime(DateTime.UtcNow); + + case SchemaFormat.Byte: + return new byte[] { 48, 49, 50 }; + + case SchemaFormat.Binary: + return "example-object"; + + default: + return "example-string"; + } + } + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj new file mode 100644 index 00000000..0f05e471 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -0,0 +1,37 @@ + + + + An OpenApi (swagger) parser to generate MappingModel or mapping.json file. + 1.2.4-preview-01 + net46;netstandard2.0 + true + wiremock;openapi;OAS;converter;parser;openapiparser + {D3804228-91F4-4502-9595-39584E5AADAD} + true + ../WireMock.Net/WireMock.Net.ruleset + true + ../WireMock.Net/WireMock.Net.snk + true + + + + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs new file mode 100644 index 00000000..44fed945 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using JetBrains.Annotations; +using Microsoft.OpenApi; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Newtonsoft.Json.Linq; +using RamlToOpenApiConverter; +using WireMock.Admin.Mappings; +using WireMock.Net.OpenApiParser.Extensions; +using WireMock.Net.OpenApiParser.Types; +using WireMock.Net.OpenApiParser.Utils; + +namespace WireMock.Net.OpenApiParser +{ + /// + /// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels. + /// + public class WireMockOpenApiParser : IWireMockOpenApiParser + { + private const int ArrayItems = 3; + + private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); + + /// + [PublicAPI] + public IEnumerable FromFile(string path, out OpenApiDiagnostic diagnostic) + { + OpenApiDocument document; + if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) + { + diagnostic = new OpenApiDiagnostic(); + document = new RamlConverter().ConvertToOpenApiDocument(path); + } + else + { + var reader = new OpenApiStreamReader(); + document = reader.Read(File.OpenRead(path), out diagnostic); + } + + return FromDocument(document); + } + + /// + [PublicAPI] + public IEnumerable FromStream(Stream stream, out OpenApiDiagnostic diagnostic) + { + return FromDocument(_reader.Read(stream, out diagnostic)); + } + + /// + [PublicAPI] + public IEnumerable FromDocument(OpenApiDocument openApiDocument) + { + return MapPaths(openApiDocument.Paths); + } + + private static IEnumerable MapPaths(OpenApiPaths paths) + { + return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x); + } + + private static IEnumerable MapPath(string path, OpenApiPathItem pathItem) + { + return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value)); + } + + private static MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation) + { + var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query); + var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path); + var response = operation.Responses.FirstOrDefault(); + TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType); + var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema; + var responseExample = responseContent?.Example; + + var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema); + + if (int.TryParse(response.Key, out var httpStatusCode)) + { + httpStatusCode = 200; + } + + return new MappingModel + { + Guid = Guid.NewGuid(), + Request = new RequestModel + { + Methods = new[] { httpMethod }, + Path = MapPathWithParameters(path, pathParameters), + Params = MapQueryParameters(queryParameters) + }, + Response = new ResponseModel + { + StatusCode = httpStatusCode, + Headers = MapHeaders(responseContentType, response.Value?.Headers), + BodyAsJson = body + } + }; + } + + private static bool TryGetContent(IDictionary contents, out OpenApiMediaType openApiMediaType, out string contentType) + { + openApiMediaType = null; + contentType = null; + + if (contents == null || contents.Values.Count == 0) + { + return false; + } + + if (contents.TryGetValue("application/json", out var content)) + { + openApiMediaType = content; + contentType = "application/json"; + } + else + { + var first = contents.FirstOrDefault(); + openApiMediaType = first.Value; + contentType = first.Key; + } + + return true; + } + + private static object MapSchemaToObject(OpenApiSchema schema, string name = null) + { + if (schema == null) + { + return null; + } + + switch (schema.GetSchemaType()) + { + case SchemaType.Array: + var jArray = new JArray(); + for (int i = 0; i < ArrayItems; i++) + { + if (schema.Items.Properties.Count > 0) + { + var arrayItem = new JObject(); + foreach (var property in schema.Items.Properties) + { + var objectValue = MapSchemaToObject(property.Value, property.Key); + if (objectValue is JProperty jp) + { + arrayItem.Add(jp); + } + else + { + arrayItem.Add(new JProperty(property.Key, objectValue)); + } + } + + jArray.Add(arrayItem); + } + else + { + jArray.Add(MapSchemaToObject(schema.Items, name)); + } + } + + return jArray; + + case SchemaType.Boolean: + case SchemaType.Integer: + case SchemaType.Number: + case SchemaType.String: + return ExampleValueGenerator.GetExampleValue(schema); + + case SchemaType.Object: + var propertyAsJObject = new JObject(); + foreach (var schemaProperty in schema.Properties) + { + string propertyName = schemaProperty.Key; + var openApiSchema = schemaProperty.Value; + if (openApiSchema.GetSchemaType() == SchemaType.Object) + { + var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key); + if (mapped is JProperty jp) + { + propertyAsJObject.Add(jp); + } + } + else + { + bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); + + propertyAsJObject.Add(new JProperty(propertyName, ExampleValueGenerator.GetExampleValue(openApiSchema))); + } + } + + return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject; + + default: + return null; + } + } + + private static string MapPathWithParameters(string path, IEnumerable parameters) + { + if (parameters == null) + { + return path; + } + + string newPath = path; + foreach (var parameter in parameters) + { + newPath = newPath.Replace($"{{{parameter.Name}}}", ExampleValueGenerator.GetExampleValue(parameter.Schema).ToString()); + } + + return newPath; + } + + private static JToken MapOpenApiAnyToJToken(IOpenApiAny any) + { + if (any == null) + { + return null; + } + + using (var outputString = new StringWriter()) + { + var writer = new OpenApiJsonWriter(outputString); + any.Write(writer, OpenApiSpecVersion.OpenApi3_0); + + return JObject.Parse(outputString.ToString()); + } + } + + private static IDictionary MapHeaders(string responseContentType, IDictionary headers) + { + var mappedHeaders = headers.ToDictionary(item => item.Key, item => ExampleValueGenerator.GetExampleValue(null)); + if (!string.IsNullOrEmpty(responseContentType)) + { + if (!mappedHeaders.ContainsKey("Content-Type")) + { + mappedHeaders.Add("Content-Type", responseContentType); + } + else + { + mappedHeaders["Content-Type"] = responseContentType; + } + } + + return mappedHeaders.Keys.Any() ? mappedHeaders : null; + } + + private static IList MapQueryParameters(IEnumerable queryParameters) + { + var list = queryParameters + .Select(qp => new ParamModel + { + Name = qp.Name, + Matchers = new[] + { + new MatcherModel + { + Name = "ExactMatcher", + Pattern = GetDefaultValueAsStringForSchemaType(qp.Schema) + } + } + }) + .ToList(); + + return list.Any() ? list : null; + } + + private static string GetDefaultValueAsStringForSchemaType(OpenApiSchema schema) + { + var value = ExampleValueGenerator.GetExampleValue(schema); + + switch (value) + { + case string valueAsString: + return valueAsString; + + default: + return value.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj index 956021c3..664a896d 100644 --- a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj +++ b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj @@ -36,7 +36,7 @@ - + all diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 9dec89f0..c569a52b 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -109,14 +109,11 @@ namespace WireMock.Server Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); - } + } #endregion - + #region StaticMappings - /// - /// Saves the static mappings. - /// - /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + /// [PublicAPI] public void SaveStaticMappings([CanBeNull] string folder = null) { @@ -124,12 +121,9 @@ namespace WireMock.Server { SaveMappingToFile(mapping, folder); } - } - - /// - /// Reads the static mappings from a folder. - /// - /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + } + + /// [PublicAPI] public void ReadStaticMappings([CanBeNull] string folder = null) { @@ -157,12 +151,9 @@ namespace WireMock.Server _settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename); } } - } - - /// - /// Watches the static mappings for changes. - /// - /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings + } + + /// [PublicAPI] public void WatchStaticMappings([CanBeNull] string folder = null) { @@ -218,12 +209,9 @@ namespace WireMock.Server }; watcher.EnableRaisingEvents = true; - } - - /// - /// Reads a static mapping file and adds or updates the mapping. - /// - /// The path. + } + + /// [PublicAPI] public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path) { @@ -233,16 +221,16 @@ namespace WireMock.Server if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) { - var mappingModels = DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + var mappingModels = DeserializeJsonToArray(value); foreach (var mappingModel in mappingModels) { if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) { - DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path); + ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path); } else { - DeserializeAndAddOrUpdateMapping(mappingModel, null, path); + ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path); } } @@ -409,7 +397,7 @@ namespace WireMock.Server Guid guid = ParseGuidFromRequestMessage(requestMessage); var mappingModel = DeserializeObject(requestMessage); - Guid? guidFromPut = DeserializeAndAddOrUpdateMapping(mappingModel, guid); + Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); } @@ -484,13 +472,13 @@ namespace WireMock.Server var mappingModels = DeserializeRequestMessageToArray(requestMessage); if (mappingModels.Length == 1) { - Guid? guid = DeserializeAndAddOrUpdateMapping(mappingModels[0]); + Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); return ResponseMessageBuilder.Create("Mapping added", 201, guid); } foreach (var mappingModel in mappingModels) { - DeserializeAndAddOrUpdateMapping(mappingModel); + ConvertMappingAndRegisterAsRespondProvider(mappingModel); } return ResponseMessageBuilder.Create("Mappings added", 201); @@ -507,7 +495,7 @@ namespace WireMock.Server } } - private Guid? DeserializeAndAddOrUpdateMapping(MappingModel mappingModel, Guid? guid = null, string path = null) + private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null) { Check.NotNull(mappingModel, nameof(mappingModel)); Check.NotNull(mappingModel.Request, nameof(mappingModel.Request)); @@ -987,5 +975,10 @@ namespace WireMock.Server var singleResult = ((JObject)value).ToObject(); return new[] { singleResult }; } + + private T[] DeserializeJsonToArray(string value) + { + return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + } } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs b/src/WireMock.Net/Server/WireMockServer.LogEntries.cs index 340307f1..002fb46b 100644 --- a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs +++ b/src/WireMock.Net/Server/WireMockServer.LogEntries.cs @@ -12,10 +12,8 @@ using WireMock.Matchers.Request; namespace WireMock.Server { public partial class WireMockServer - { - /// - /// Occurs when [log entries changed]. - /// + { + /// [PublicAPI] public event NotifyCollectionChangedEventHandler LogEntriesChanged { @@ -35,14 +33,14 @@ namespace WireMock.Server } remove => _options.LogEntries.CollectionChanged -= value; - } - + } + /// /// Gets the request logs. /// [PublicAPI] - public IEnumerable LogEntries => new ReadOnlyCollection(_options.LogEntries.ToList()); - + public IEnumerable LogEntries => new ReadOnlyCollection(_options.LogEntries.ToList()); + /// /// The search log-entries based on matchers. /// @@ -68,21 +66,16 @@ namespace WireMock.Server } return new ReadOnlyCollection(results.OrderBy(x => x.Value).Select(x => x.Key).ToList()); - } - - /// - /// Resets the LogEntries. - /// + } + + /// [PublicAPI] public void ResetLogEntries() { _options.LogEntries.Clear(); - } - - /// - /// Deletes a LogEntry. - /// - /// The unique identifier. + } + + /// [PublicAPI] public bool DeleteLogEntry(Guid guid) { diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 6f7d8c7d..3baeeac1 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -1,493 +1,480 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using JetBrains.Annotations; -using Newtonsoft.Json; -using WireMock.Admin.Mappings; -using WireMock.Exceptions; -using WireMock.Handlers; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.Owin; -using WireMock.RequestBuilders; -using WireMock.ResponseProviders; -using WireMock.Serialization; -using WireMock.Settings; -using WireMock.Validation; - -namespace WireMock.Server -{ - /// - /// The fluent mock server. - /// - public partial class WireMockServer : IDisposable - { - private const int ServerStartDelayInMs = 100; - - private readonly IWireMockServerSettings _settings; - private readonly IOwinSelfHost _httpServer; - private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); - private readonly MappingConverter _mappingConverter; - private readonly MatcherMapper _matcherMapper; - - /// - /// Gets a value indicating whether this server is started. - /// - [PublicAPI] - public bool IsStarted => _httpServer != null && _httpServer.IsStarted; - - /// - /// Gets the ports. - /// - [PublicAPI] - public List Ports { get; } - - /// - /// Gets the urls. - /// - [PublicAPI] - public string[] Urls { get; } - - /// - /// Gets the mappings. - /// - [PublicAPI] - public IEnumerable Mappings => _options.Mappings.Values.ToArray(); - - /// - /// Gets the mappings as MappingModels. - /// - [PublicAPI] - public IEnumerable MappingModels => ToMappingModels(); - - /// - /// Gets the scenarios. - /// - [PublicAPI] - public ConcurrentDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); - - #region IDisposable Members - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_httpServer != null) - { - _httpServer.StopAsync(); - } - } - #endregion - - #region Start/Stop - /// - /// Starts the specified settings. - /// - /// The WireMockServerSettings. - /// The . - [PublicAPI] - public static WireMockServer Start([NotNull] IWireMockServerSettings settings) - { - Check.NotNull(settings, nameof(settings)); - - return new WireMockServer(settings); - } - - /// - /// Start this WireMockServer. - /// - /// The port. - /// The SSL support. - /// The . - [PublicAPI] - public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false) - { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = ssl - }); - } - - /// - /// Start this WireMockServer. - /// - /// The urls to listen on. - /// The . - [PublicAPI] - public static WireMockServer Start(params string[] urls) - { - Check.NotNullOrEmpty(urls, nameof(urls)); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls - }); - } - - /// - /// Start this WireMockServer with the admin interface. - /// - /// The port. - /// The SSL support. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false) - { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = ssl, - StartAdminInterface = true - }); - } - - /// - /// Start this WireMockServer with the admin interface. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(params string[] urls) - { - Check.NotNullOrEmpty(urls, nameof(urls)); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true - }); - } - - /// - /// Start this WireMockServer with the admin interface and read static mappings. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) - { - Check.NotNullOrEmpty(urls, nameof(urls)); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true, - ReadStaticMappings = true - }); - } - protected WireMockServer(IWireMockServerSettings settings) - { - _settings = settings; - - // Set default values if not provided - _settings.Logger = settings.Logger ?? new WireMockNullLogger(); - _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); - - _settings.Logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)"); - _settings.Logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); - - HostUrlOptions urlOptions; - if (settings.Urls != null) - { - urlOptions = new HostUrlOptions - { - Urls = settings.Urls - }; - } - else - { - urlOptions = new HostUrlOptions - { - UseSSL = settings.UseSSL == true, - Port = settings.Port - }; - } - - _options.FileSystemHandler = _settings.FileSystemHandler; - _options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit; - _options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit; - _options.Logger = _settings.Logger; - _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; - - _matcherMapper = new MatcherMapper(_settings); - _mappingConverter = new MappingConverter(_matcherMapper); - -#if USE_ASPNETCORE - _httpServer = new AspNetCoreSelfHost(_options, urlOptions); -#else - _httpServer = new OwinSelfHost(_options, urlOptions); -#endif - var startTask = _httpServer.StartAsync(); - - using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) - { - while (!_httpServer.IsStarted) - { - // Throw exception if service start fails - if (_httpServer.RunningException != null) - { - throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); - } - - if (ctsStartTimeout.IsCancellationRequested) - { - // In case of an aggregate exception, throw the exception. - if (startTask.Exception != null) - { - throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); - } - - // Else throw TimeoutException - throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); - } - - ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); - } - - Urls = _httpServer.Urls.ToArray(); - Ports = _httpServer.Ports; - } - - if (settings.AllowBodyForAllHttpMethods == true) - { - _options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods; - _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); - } - - if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) - { - _options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse; - _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); - } - - if (settings.AllowPartialMapping == true) - { - AllowPartialMapping(); - } - - if (settings.StartAdminInterface == true) - { - if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) - { - SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); - } - - InitAdmin(); - } - - if (settings.ReadStaticMappings == true) - { - ReadStaticMappings(); - } - - if (settings.WatchStaticMappings == true) - { - WatchStaticMappings(); - } - - if (settings.ProxyAndRecordSettings != null) - { - InitProxyAndRecord(settings); - } - - if (settings.RequestLogExpirationDuration != null) - { - SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); - } - - if (settings.MaxRequestLogCount != null) - { - SetMaxRequestLogCount(settings.MaxRequestLogCount); - } - } - - /// - /// Stop this server. - /// - [PublicAPI] - public void Stop() - { - var result = _httpServer?.StopAsync(); - result?.Wait(); // wait for stop to actually happen - } - #endregion - - /// - /// Adds the catch all mapping. - /// - [PublicAPI] - public void AddCatchAllMapping() - { - Given(Request.Create().WithPath("/*").UsingAnyMethod()) - .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) - .AtPriority(1000) - .RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404))); - } - - /// - /// Resets LogEntries and Mappings. - /// - [PublicAPI] - public void Reset() - { - ResetLogEntries(); - - ResetMappings(); - } - - /// - /// Resets the Mappings. - /// - [PublicAPI] - public void ResetMappings() - { - foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) - { - _options.Mappings.TryRemove(nonAdmin.Key, out _); - } - } - - /// - /// Deletes the mapping. - /// - /// The unique identifier. - [PublicAPI] - public bool DeleteMapping(Guid guid) - { - // Check a mapping exists with the same GUID, if so, remove it. - if (_options.Mappings.ContainsKey(guid)) - { - return _options.Mappings.TryRemove(guid, out _); - } - - return false; - } - - private bool DeleteMapping(string path) - { - // Check a mapping exists with the same path, if so, remove it. - var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); - return DeleteMapping(mapping.Key); - } - - /// - /// The add request processing delay. - /// - /// The delay. - [PublicAPI] - public void AddGlobalProcessingDelay(TimeSpan delay) - { - _options.RequestProcessingDelay = delay; - } - - /// - /// Allows the partial mapping. - /// - [PublicAPI] - public void AllowPartialMapping(bool allow = true) - { - _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); - _options.AllowPartialMapping = allow; - } - - /// - /// Sets the basic authentication. - /// - /// The username. - /// The password. - [PublicAPI] - public void SetBasicAuthentication([NotNull] string username, [NotNull] string password) - { - Check.NotNull(username, nameof(username)); - Check.NotNull(password, nameof(password)); - - string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)); - _options.AuthorizationMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, "^(?i)BASIC " + authorization + "$"); - } - - /// - /// Removes the basic authentication. - /// - [PublicAPI] - public void RemoveBasicAuthentication() - { - _options.AuthorizationMatcher = null; - } - - /// - /// Sets the maximum RequestLog count. - /// - /// The maximum RequestLog count. - [PublicAPI] - public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) - { - _options.MaxRequestLogCount = maxRequestLogCount; - } - - /// - /// Sets RequestLog expiration in hours. - /// - /// The RequestLog expiration in hours. - [PublicAPI] - public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) - { - _options.RequestLogExpirationDuration = requestLogExpirationDuration; - } - - /// - /// Resets the Scenarios. - /// - [PublicAPI] - public void ResetScenarios() - { - _options.Scenarios.Clear(); - } - - /// - /// The given. - /// - /// The request matcher. - /// Optional boolean to indicate if this mapping should be saved as static mapping file. - /// The . - [PublicAPI] - public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false) - { - return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile); - } - - private void RegisterMapping(IMapping mapping, bool saveToFile) - { - // Check a mapping exists with the same Guid, if so, replace it. - if (_options.Mappings.ContainsKey(mapping.Guid)) - { - _options.Mappings[mapping.Guid] = mapping; - } - else - { - _options.Mappings.TryAdd(mapping.Guid, mapping); - } - - if (saveToFile) - { - SaveMappingToFile(mapping); - } - } - } +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using JetBrains.Annotations; +using Newtonsoft.Json; +using WireMock.Admin.Mappings; +using WireMock.Exceptions; +using WireMock.Handlers; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Owin; +using WireMock.RequestBuilders; +using WireMock.ResponseProviders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Validation; + +namespace WireMock.Server +{ + /// + /// The fluent mock server. + /// + public partial class WireMockServer : IWireMockServer + { + private const int ServerStartDelayInMs = 100; + + private readonly IWireMockServerSettings _settings; + private readonly IOwinSelfHost _httpServer; + private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); + private readonly MappingConverter _mappingConverter; + private readonly MatcherMapper _matcherMapper; + + /// + [PublicAPI] + public bool IsStarted => _httpServer != null && _httpServer.IsStarted; + + /// + [PublicAPI] + public List Ports { get; } + + /// + [PublicAPI] + public string[] Urls { get; } + + /// + /// Gets the mappings. + /// + [PublicAPI] + public IEnumerable Mappings => _options.Mappings.Values.ToArray(); + + /// + [PublicAPI] + public IEnumerable MappingModels => ToMappingModels(); + + /// + /// Gets the scenarios. + /// + [PublicAPI] + public ConcurrentDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); + + #region IDisposable Members + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_httpServer != null) + { + _httpServer.StopAsync(); + } + } + #endregion + + #region Start/Stop + /// + /// Starts this WireMockServer with the specified settings. + /// + /// The WireMockServerSettings. + /// The . + [PublicAPI] + public static WireMockServer Start([NotNull] IWireMockServerSettings settings) + { + Check.NotNull(settings, nameof(settings)); + + return new WireMockServer(settings); + } + + /// + /// Start this WireMockServer. + /// + /// The port. + /// The SSL support. + /// The . + [PublicAPI] + public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false) + { + return new WireMockServer(new WireMockServerSettings + { + Port = port, + UseSSL = ssl + }); + } + + /// + /// Start this WireMockServer. + /// + /// The urls to listen on. + /// The . + [PublicAPI] + public static WireMockServer Start(params string[] urls) + { + Check.NotNullOrEmpty(urls, nameof(urls)); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls + }); + } + + /// + /// Start this WireMockServer with the admin interface. + /// + /// The port. + /// The SSL support. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false) + { + return new WireMockServer(new WireMockServerSettings + { + Port = port, + UseSSL = ssl, + StartAdminInterface = true + }); + } + + /// + /// Start this WireMockServer with the admin interface. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(params string[] urls) + { + Check.NotNullOrEmpty(urls, nameof(urls)); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls, + StartAdminInterface = true + }); + } + + /// + /// Start this WireMockServer with the admin interface and read static mappings. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) + { + Check.NotNullOrEmpty(urls, nameof(urls)); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls, + StartAdminInterface = true, + ReadStaticMappings = true + }); + } + protected WireMockServer(IWireMockServerSettings settings) + { + _settings = settings; + + // Set default values if not provided + _settings.Logger = settings.Logger ?? new WireMockNullLogger(); + _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); + + _settings.Logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)"); + _settings.Logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); + + HostUrlOptions urlOptions; + if (settings.Urls != null) + { + urlOptions = new HostUrlOptions + { + Urls = settings.Urls + }; + } + else + { + urlOptions = new HostUrlOptions + { + UseSSL = settings.UseSSL == true, + Port = settings.Port + }; + } + + _options.FileSystemHandler = _settings.FileSystemHandler; + _options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit; + _options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit; + _options.Logger = _settings.Logger; + _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; + + _matcherMapper = new MatcherMapper(_settings); + _mappingConverter = new MappingConverter(_matcherMapper); + +#if USE_ASPNETCORE + _httpServer = new AspNetCoreSelfHost(_options, urlOptions); +#else + _httpServer = new OwinSelfHost(_options, urlOptions); +#endif + var startTask = _httpServer.StartAsync(); + + using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) + { + while (!_httpServer.IsStarted) + { + // Throw exception if service start fails + if (_httpServer.RunningException != null) + { + throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); + } + + if (ctsStartTimeout.IsCancellationRequested) + { + // In case of an aggregate exception, throw the exception. + if (startTask.Exception != null) + { + throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); + } + + // Else throw TimeoutException + throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); + } + + ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); + } + + Urls = _httpServer.Urls.ToArray(); + Ports = _httpServer.Ports; + } + + if (settings.AllowBodyForAllHttpMethods == true) + { + _options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods; + _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); + } + + if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) + { + _options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse; + _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); + } + + if (settings.AllowPartialMapping == true) + { + AllowPartialMapping(); + } + + if (settings.StartAdminInterface == true) + { + if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) + { + SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); + } + + InitAdmin(); + } + + if (settings.ReadStaticMappings == true) + { + ReadStaticMappings(); + } + + if (settings.WatchStaticMappings == true) + { + WatchStaticMappings(); + } + + if (settings.ProxyAndRecordSettings != null) + { + InitProxyAndRecord(settings); + } + + if (settings.RequestLogExpirationDuration != null) + { + SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); + } + + if (settings.MaxRequestLogCount != null) + { + SetMaxRequestLogCount(settings.MaxRequestLogCount); + } + } + + /// + [PublicAPI] + public void Stop() + { + var result = _httpServer?.StopAsync(); + result?.Wait(); // wait for stop to actually happen + } + #endregion + + /// + [PublicAPI] + public void AddCatchAllMapping() + { + Given(Request.Create().WithPath("/*").UsingAnyMethod()) + .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) + .AtPriority(1000) + .RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404))); + } + + /// + [PublicAPI] + public void Reset() + { + ResetLogEntries(); + + ResetMappings(); + } + + /// + [PublicAPI] + public void ResetMappings() + { + foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) + { + _options.Mappings.TryRemove(nonAdmin.Key, out _); + } + } + + /// + [PublicAPI] + public bool DeleteMapping(Guid guid) + { + // Check a mapping exists with the same GUID, if so, remove it. + if (_options.Mappings.ContainsKey(guid)) + { + return _options.Mappings.TryRemove(guid, out _); + } + + return false; + } + + private bool DeleteMapping(string path) + { + // Check a mapping exists with the same path, if so, remove it. + var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); + return DeleteMapping(mapping.Key); + } + + /// + [PublicAPI] + public void AddGlobalProcessingDelay(TimeSpan delay) + { + _options.RequestProcessingDelay = delay; + } + + /// + [PublicAPI] + public void AllowPartialMapping(bool allow = true) + { + _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); + _options.AllowPartialMapping = allow; + } + + /// + [PublicAPI] + public void SetBasicAuthentication([NotNull] string username, [NotNull] string password) + { + Check.NotNull(username, nameof(username)); + Check.NotNull(password, nameof(password)); + + string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)); + _options.AuthorizationMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, "^(?i)BASIC " + authorization + "$"); + } + + /// + [PublicAPI] + public void RemoveBasicAuthentication() + { + _options.AuthorizationMatcher = null; + } + + /// + [PublicAPI] + public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) + { + _options.MaxRequestLogCount = maxRequestLogCount; + } + + /// + [PublicAPI] + public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) + { + _options.RequestLogExpirationDuration = requestLogExpirationDuration; + } + + /// + [PublicAPI] + public void ResetScenarios() + { + _options.Scenarios.Clear(); + } + + /// + [PublicAPI] + public IWireMockServer WithMapping(params MappingModel[] mappings) + { + foreach (var mapping in mappings) + { + ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid()); + } + + return this; + } + + /// + [PublicAPI] + public IWireMockServer WithMapping(string mappings) + { + var mappingModels = DeserializeJsonToArray(mappings); + foreach (var mappingModel in mappingModels) + { + ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); + } + + return this; + } + + /// + /// The given. + /// + /// The request matcher. + /// Optional boolean to indicate if this mapping should be saved as static mapping file. + /// The . + [PublicAPI] + public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false) + { + return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile); + } + + private void RegisterMapping(IMapping mapping, bool saveToFile) + { + // Check a mapping exists with the same Guid, if so, replace it. + if (_options.Mappings.ContainsKey(mapping.Guid)) + { + _options.Mappings[mapping.Guid] = mapping; + } + else + { + _options.Mappings.TryAdd(mapping.Guid, mapping); + } + + if (saveToFile) + { + SaveMappingToFile(mapping); + } + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index d3d63717..22135bd3 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -51,7 +51,7 @@ - + diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs index e654e655..8653b913 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs @@ -12,7 +12,6 @@ using WireMock.Admin.Settings; using WireMock.Client; using WireMock.Handlers; using WireMock.Logging; -using WireMock.Matchers; using WireMock.Models; using WireMock.Server; using WireMock.Settings; diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index d72167ce..b0dce878 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.IO; -using System.IO.Compression; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/test/WireMock.Net.Tests/WithMapping/WireMockServerWithMappingTests.cs b/test/WireMock.Net.Tests/WithMapping/WireMockServerWithMappingTests.cs new file mode 100644 index 00000000..089dd7b7 --- /dev/null +++ b/test/WireMock.Net.Tests/WithMapping/WireMockServerWithMappingTests.cs @@ -0,0 +1,125 @@ +using System; +using FluentAssertions; +using WireMock.Admin.Mappings; +using WireMock.Server; +using Xunit; + +namespace WireMock.Net.Tests.WithMapping +{ + public class WireMockServerWithMappingTests + { + [Fact] + public void WireMockServer_WithMappingAsModel_Should_Add_Mapping() + { + // Arrange + var guid = Guid.NewGuid(); + var pattern = "hello wiremock"; + var path = "/foo"; + var response = "OK"; + var mapping = new MappingModel + { + Guid = guid, + Request = new RequestModel + { + Path = path, + Body = new BodyModel + { + Matcher = new MatcherModel + { + Name = "ExactMatcher", + Pattern = pattern + } + } + }, + Response = new ResponseModel + { + Body = response, + StatusCode = 201 + } + }; + + var server = WireMockServer.Start(); + + // Act + server.WithMapping(mapping); + + // Assert + server.MappingModels.Should().HaveCount(1).And.Contain(m => + m.Guid == guid && + //((PathModel)m.Request.Path).Matchers.OfType().First().GetPatterns().First() == "/foo*" + // m.Request.Body.Matchers.OfType().First().GetPatterns().First() == pattern && + m.Response.Body == response && + (int)m.Response.StatusCode == 201 + ); + } + + [Fact] + public void WireMockServer_WithMappingAsJson_Should_Add_Mapping() + { + // Arrange + var mapping = @"{ + ""Guid"": ""532889c2-f84d-4dc8-b847-9ea2c6aca7d5"", + ""Request"": { + ""Path"": ""/pet"", + ""Methods"": [ + ""PUT"" + ] + }, + ""Response"": { + ""StatusCode"": 201, + ""BodyAsJson"": { + ""id"": 42, + }, + ""Headers"": { + ""Content-Type"": ""application/json"" + } + } + }"; + + var server = WireMockServer.Start(); + + // Act + server.WithMapping(mapping); + + // Assert + server.MappingModels.Should().HaveCount(1).And.Contain(m => + m.Guid == Guid.Parse("532889c2-f84d-4dc8-b847-9ea2c6aca7d5") && + (int)m.Response.StatusCode == 201 + ); + } + + [Fact] + public void WireMockServer_WithMappingsAsJson_Should_Add_Mapping() + { + // Arrange + var mapping = @"[ + { + ""Guid"": ""532889c2-f84d-4dc8-b847-9ea2c6aca7d1"", + ""Request"": { + ""Path"": ""/pet1"" + }, + ""Response"": { + ""StatusCode"": 201 + } + }, + { + ""Guid"": ""532889c2-f84d-4dc8-b847-9ea2c6aca7d2"", + ""Request"": { + ""Path"": ""/pet2"" + }, + ""Response"": { + ""StatusCode"": 202 + } + } + ]"; + + var server = WireMockServer.Start(); + + // Act + server.WithMapping(mapping); + + // Assert + server.MappingModels.Should().HaveCount(2); + } + } +} \ No newline at end of file