mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-01-13 22:03:35 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
124d29c86a | ||
|
|
08117e870b | ||
|
|
ddb181cc52 | ||
|
|
10fdd24fca | ||
|
|
be2ea67b89 | ||
|
|
60eb519ae2 | ||
|
|
22ed94918a | ||
|
|
faffc56484 | ||
|
|
a5558777e2 | ||
|
|
6722ca40ba | ||
|
|
0597a73e0e | ||
|
|
0d510cdde8 | ||
|
|
52a396beef | ||
|
|
6ccfe68686 | ||
|
|
e400e92452 | ||
|
|
7a187dfb78 | ||
|
|
e6ff8776fb |
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,7 +8,7 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
@@ -17,7 +17,7 @@ A clear and concise description of what you want to happen.
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Is your feature request supported by [WireMock (java version)](https://www.wiremock.org)? Please provide details.**
|
||||
Provide relevant information if requested feature is supported in [Handlebarsjs](https://handlebarsjs.com/) but is missing in our implementation.
|
||||
Provide relevant information if requested feature is supported but is missing in this implementation.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,3 +1,28 @@
|
||||
# 1.10.1 (22 August 2025)
|
||||
- [#1349](https://github.com/wiremock/WireMock.Net/pull/1349) - Add AtPath and AtAbsolutePath to Assertions projects [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1348](https://github.com/wiremock/WireMock.Net/issues/1348) - Support AtPath and AtAbsolutePath in WireMockAssertions [feature]
|
||||
|
||||
# 1.10.0 (18 August 2025)
|
||||
- [#1344](https://github.com/wiremock/WireMock.Net/pull/1344) - Add new package WireMock.Net.Extensions.Routing which provides minimal-API-style routing for WireMock.Net [feature] contributed by [GennadyGS](https://github.com/GennadyGS)
|
||||
- [#1340](https://github.com/wiremock/WireMock.Net/issues/1340) - Feature Request: Add minimal-API-style routing as Extension Package [feature]
|
||||
|
||||
# 1.9.1 (17 August 2025)
|
||||
- [#1345](https://github.com/wiremock/WireMock.Net/pull/1345) - Add TimesInSameState to MappingModel [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1347](https://github.com/wiremock/WireMock.Net/pull/1347) - Fix generating source code for Scenario and State [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1343](https://github.com/wiremock/WireMock.Net/issues/1343) - MappingModel allows to configure the times for WillSetStateTo [feature]
|
||||
- [#1346](https://github.com/wiremock/WireMock.Net/issues/1346) - Mapping: generated C# code is missing InScenario and WillSetStateTo [bug]
|
||||
|
||||
# 1.9.0 (10 August 2025)
|
||||
- [#1334](https://github.com/wiremock/WireMock.Net/pull/1334) - Create GraphQL project [feature] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 1.8.18 (04 August 2025)
|
||||
- [#1339](https://github.com/wiremock/WireMock.Net/pull/1339) - Fixes an issue with matching JSON bodies as bytes [bug] contributed by [smfields](https://github.com/smfields)
|
||||
- [#1338](https://github.com/wiremock/WireMock.Net/issues/1338) - Specifying .WithBody(byte[]) fails to match for JSON bodies [bug]
|
||||
|
||||
# 1.8.17 (23 July 2025)
|
||||
- [#1337](https://github.com/wiremock/WireMock.Net/pull/1337) - Make CSharpCodeMatcher public [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1336](https://github.com/wiremock/WireMock.Net/issues/1336) - Is CSharpCodeMatcher actually usable? [bug]
|
||||
|
||||
# 1.8.16 (19 July 2025)
|
||||
- [#1332](https://github.com/wiremock/WireMock.Net/pull/1332) - Use correct Handlebars.Net.Helpers.Xslt (2.5.2) [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1328](https://github.com/wiremock/WireMock.Net/issues/1328) - ReflectionTypeLoadException occurs when running multiple unit tests (Nunit 3) [bug]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>1.8.16</VersionPrefix>
|
||||
<Version>1.10.1</Version>
|
||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rem https://github.com/StefH/GitHubReleaseNotes
|
||||
|
||||
SET version=1.8.16
|
||||
SET version=1.10.1
|
||||
|
||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 1.8.16 (19 July 2025)
|
||||
- #1332 Use correct Handlebars.Net.Helpers.Xslt (2.5.2) [bug]
|
||||
- #1328 ReflectionTypeLoadException occurs when running multiple unit tests (Nunit 3) [bug]
|
||||
# 1.10.1 (22 August 2025)
|
||||
- #1349 Add AtPath and AtAbsolutePath to Assertions projects [feature]
|
||||
- #1348 Support AtPath and AtAbsolutePath in WireMockAssertions [feature]
|
||||
|
||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||
@@ -51,16 +51,18 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
|
||||
| **WireMock.Net.xUnit** | [](https://www.nuget.org/packages/WireMock.Net.xUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit)
|
||||
| **WireMock.Net.TUnit** | [](https://www.nuget.org/packages/WireMock.Net.TUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.TUnit)
|
||||
| | | |
|
||||
| **WireMock.Net.Extensions.Routing** | [](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
|
||||
| **WireMock.Net.Matchers.CSharpCode** | [](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
|
||||
| **WireMock.Net.OpenApiParser** | [](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
|
||||
| **WireMock.Net.MimePart** | [](https://www.nuget.org/packages/WireMock.Net.MimePart) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart)
|
||||
| **WireMock.Net.GraphQL** | [](https://www.nuget.org/packages/WireMock.Net.GraphQL) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.GraphQL)
|
||||
| | | |
|
||||
| **WireMock.Net.RestClient** | [](https://www.nuget.org/packages/WireMock.Net.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
|
||||
| **WireMock.Org.RestClient** | [](https://www.nuget.org/packages/WireMock.Org.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
|
||||
|
||||
<br />
|
||||
|
||||
🔺 **WireMock.Net.Minimal** does not include: **WireMock.Net.MimePart**
|
||||
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart* and *WireMock.Net.GraphQL*.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -136,6 +136,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ConsoleApp.Usi
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Tests.UsingNuGet", "test\WireMock.Net.Tests.UsingNuGet\WireMock.Net.Tests.UsingNuGet.csproj", "{BBA332C6-28A9-42E7-9C4D-A0816E52A198}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.GraphQL", "src\WireMock.Net.GraphQL\WireMock.Net.GraphQL.csproj", "{B6269AAC-170A-4346-8B9A-444DED3D9A45}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Routing.Tests", "test\WireMock.Net.Extensions.Routing.Tests\WireMock.Net.Extensions.Routing.Tests.csproj", "{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Routing", "src\WireMock.Net.Extensions.Routing\WireMock.Net.Extensions.Routing.csproj", "{1E874C8F-08A2-493B-8421-619F9A6E9E77}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -326,6 +332,18 @@ Global
|
||||
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -379,6 +397,9 @@ Global
|
||||
{BFEF8990-65B3-4274-310F-7355F0B84035} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{1F80A6E6-D146-4E40-9EA8-49DB8494239F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{BBA332C6-28A9-42E7-9C4D-A0816E52A198} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||
{B6269AAC-170A-4346-8B9A-444DED3D9A45} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
|
||||
|
||||
@@ -132,7 +132,42 @@ namespace WireMock.Net.ConsoleApplication
|
||||
}
|
||||
""";
|
||||
|
||||
private const string TestSchema =
|
||||
private const string TestSchemaQueryStudents =
|
||||
"""
|
||||
type Query {
|
||||
students:[Student]
|
||||
}
|
||||
|
||||
type Student {
|
||||
id:ID!
|
||||
firstName:String
|
||||
lastName:String
|
||||
fullName:String
|
||||
}
|
||||
""";
|
||||
|
||||
private const string TestSchemaQueryStudentById =
|
||||
"""
|
||||
type Query {
|
||||
studentById(id:ID!):Student
|
||||
}
|
||||
|
||||
type Student {
|
||||
id:ID!
|
||||
firstName:String
|
||||
lastName:String
|
||||
fullName:String
|
||||
}
|
||||
""";
|
||||
|
||||
private const string TestSchemaQueryGreeting =
|
||||
"""
|
||||
type Query {
|
||||
greeting:String
|
||||
}
|
||||
""";
|
||||
|
||||
private const string TestSchemaMutationMessage =
|
||||
"""
|
||||
scalar DateTime
|
||||
scalar MyCustomScalar
|
||||
@@ -153,19 +188,6 @@ namespace WireMock.Net.ConsoleApplication
|
||||
createAnotherMessage(x: MyCustomScalar, dt: DateTime): Message
|
||||
updateMessage(id: ID!, input: MessageInput): Message
|
||||
}
|
||||
|
||||
type Query {
|
||||
greeting:String
|
||||
students:[Student]
|
||||
studentById(id:ID!):Student
|
||||
}
|
||||
|
||||
type Student {
|
||||
id:ID!
|
||||
firstName:String
|
||||
lastName:String
|
||||
fullName:String
|
||||
}
|
||||
""";
|
||||
|
||||
private static void RunSse()
|
||||
@@ -433,10 +455,73 @@ namespace WireMock.Net.ConsoleApplication
|
||||
.Given(Request.Create()
|
||||
.WithPath("/graphql")
|
||||
.UsingPost()
|
||||
.WithBodyAsGraphQL(TestSchema, customScalars)
|
||||
.WithGraphQLSchema(TestSchemaQueryStudents)
|
||||
)
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("GraphQL is ok")
|
||||
.WithHeader("Content-Type", "application/json")
|
||||
.WithBody(
|
||||
"""
|
||||
{
|
||||
"data": {
|
||||
"students": [
|
||||
{
|
||||
"id": "1",
|
||||
"firstName": "Alice",
|
||||
"lastName": "Johnson",
|
||||
"fullName": "Alice Johnson"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"firstName": "Bob",
|
||||
"lastName": "Smith",
|
||||
"fullName": "Bob Smith"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
);
|
||||
|
||||
server
|
||||
.Given(Request.Create()
|
||||
.WithPath("/graphql")
|
||||
.UsingPost()
|
||||
.WithGraphQLSchema(TestSchemaQueryStudentById)
|
||||
.WithBody(new JsonPartialWildcardMatcher("{ \"variables\": { \"sid\": \"1\" } }"))
|
||||
)
|
||||
.WithTitle("Student found")
|
||||
.RespondWith(Response.Create()
|
||||
.WithHeader("Content-Type", "application/json")
|
||||
.WithBody(
|
||||
"""
|
||||
{
|
||||
"data": {
|
||||
"studentById": {
|
||||
"id": "123",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"fullName": "John Doe"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
);
|
||||
|
||||
server
|
||||
.Given(Request.Create()
|
||||
.WithPath("/graphql")
|
||||
.UsingPost()
|
||||
.WithGraphQLSchema(TestSchemaQueryStudentById)
|
||||
)
|
||||
.WithTitle("Student not found")
|
||||
.RespondWith(Response.Create()
|
||||
.WithHeader("Content-Type", "application/json")
|
||||
.WithBody(
|
||||
"""
|
||||
{
|
||||
"data": null
|
||||
}
|
||||
""")
|
||||
);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -56,6 +56,11 @@ public class MappingModel
|
||||
/// In case the value is null state will not be changed.
|
||||
/// </summary>
|
||||
public string? SetStateTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of times this match should be matched before the state will be changed to the specified one.
|
||||
/// </summary>
|
||||
public int? TimesInSameState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request model.
|
||||
@@ -86,7 +91,7 @@ public class MappingModel
|
||||
/// Fire and forget for webhooks.
|
||||
/// </summary>
|
||||
public bool? UseWebhooksFireAndForget { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data Object which can be used when WithTransformer is used.
|
||||
/// e.g. lookup a path in this object using
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Models.GraphQL;
|
||||
|
||||
public interface ISchemaData;
|
||||
@@ -4,11 +4,11 @@ using Stef.Validation;
|
||||
using WireMock.Server;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides assertion methods to verify the number of calls made to a WireMock server.
|
||||
/// This class is used in the context of FluentAssertions.
|
||||
/// This class is used in the context of AwesomeAssertions.
|
||||
/// </summary>
|
||||
public class WireMockANumberOfCallsAssertions
|
||||
{
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, string> AtAbsolutePath(string absolutePath, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
_ = AtAbsolutePath(new ExactMatcher(true, absolutePath), because, becauseArgs);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, string>(this, absolutePath);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, IStringMatcher> AtAbsolutePath(IStringMatcher absolutePathMatcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(request => absolutePathMatcher.IsPerfectMatch(request.AbsolutePath));
|
||||
|
||||
var absolutePath = absolutePathMatcher.GetPatterns().FirstOrDefault().GetPattern();
|
||||
|
||||
_chain
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => RequestMessages)
|
||||
.ForCondition(requests => CallsCount == 0 || requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute path {0}{reason}, but no calls were made.",
|
||||
absolutePath
|
||||
)
|
||||
.Then
|
||||
.ForCondition(condition)
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute path {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => absolutePath,
|
||||
requests => requests.Select(request => request.AbsolutePath)
|
||||
);
|
||||
|
||||
FilterRequestMessages(filter);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, IStringMatcher>(this, absolutePathMatcher);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, string> AtPath(string path, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
_ = AtPath(new ExactMatcher(true, path), because, becauseArgs);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, string>(this, path);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, IStringMatcher> AtPath(IStringMatcher pathMatcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(request => pathMatcher.IsPerfectMatch(request.Path));
|
||||
|
||||
var path = pathMatcher.GetPatterns().FirstOrDefault().GetPattern();
|
||||
|
||||
_chain
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => RequestMessages)
|
||||
.ForCondition(requests => CallsCount == 0 || requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the path {0}{reason}, but no calls were made.",
|
||||
path
|
||||
)
|
||||
.Then
|
||||
.ForCondition(condition)
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the path {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => path,
|
||||
requests => requests.Select(request => request.Path)
|
||||
);
|
||||
|
||||
FilterRequestMessages(filter);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, IStringMatcher>(this, pathMatcher);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
public partial class WireMockAssertions
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using System;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
using WireMock.Constants;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using WireMock.Matchers;
|
||||
using WireMock.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using System;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using WireMock.Matchers;
|
||||
using WireMock.Server;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using AwesomeAssertions.Primitives;
|
||||
using WireMock.Server;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
namespace WireMock.AwesomeAssertions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains a number of methods to assert that the <see cref="IWireMockServer"/> is in the expected state.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using WireMock.Server;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions
|
||||
namespace WireMock.AwesomeAssertions
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for custom assertions in unit tests.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Delegates;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a handler for processing WireMock.Net HTTP requests and returning a response asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="requestMessage">The incoming request message.</param>
|
||||
/// <returns>A task that resolves to a <see cref="ResponseMessage"/>.</returns>
|
||||
public delegate Task<ResponseMessage> WireMockHttpRequestHandler(IRequestMessage requestMessage);
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Delegates;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a middleware component for WireMock.Net HTTP request handling.
|
||||
/// </summary>
|
||||
/// <param name="next">The next request handler in the middleware pipeline.</param>
|
||||
/// <returns>A <see cref="WireMockHttpRequestHandler"/> that processes the request.</returns>
|
||||
public delegate WireMockHttpRequestHandler WireMockMiddleware(WireMockHttpRequestHandler next);
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
public static IDictionary<TKey, TValue> AddIf<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> source,
|
||||
bool condition,
|
||||
TKey key,
|
||||
TValue value,
|
||||
IEqualityComparer<TKey>? keyComparer = null)
|
||||
where TKey : notnull =>
|
||||
condition
|
||||
? source.ToImmutableDictionary(keyComparer).Add(key, value)
|
||||
: source;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class HttpResponseExtensions
|
||||
{
|
||||
public static async Task<ResponseMessage> ToResponseMessageAsync(
|
||||
this HttpResponse response)
|
||||
{
|
||||
var headers = response.Headers.ToDictionary(
|
||||
header => header.Key, header => new WireMockList<string?>(header.Value.ToArray()));
|
||||
return new()
|
||||
{
|
||||
Headers = headers!,
|
||||
BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
BodyAsString = await response.ReadBodyAsStringAsync(),
|
||||
},
|
||||
StatusCode = response.StatusCode,
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<string> ReadBodyAsStringAsync(this HttpResponse response)
|
||||
{
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
using var reader = new StreamReader(response.Body);
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JsonConverter.Abstractions;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class RequestMessageExtensions
|
||||
{
|
||||
public static T? GetBodyAsJson<T>(
|
||||
this IRequestMessage requestMessage,
|
||||
IJsonConverter jsonConverter,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
requestMessage.Body is not null
|
||||
? jsonConverter.Deserialize<T>(requestMessage.Body, jsonOptions)
|
||||
: default;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class StringExtensions
|
||||
{
|
||||
public static string ToMatchFullStringRegex(this string regex) =>
|
||||
$"^{regex}$";
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class TaskExtensions
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Usage",
|
||||
"VSTHRD003:Avoid awaiting foreign Tasks",
|
||||
Justification = "Await is required here to transform base task to generic one.")]
|
||||
public static async Task<object?> ToGenericTaskAsync(this Task task)
|
||||
{
|
||||
await task;
|
||||
var taskType = task.GetType();
|
||||
if (!IsAssignableToGenericTaskType(taskType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return task
|
||||
.GetType()
|
||||
.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)!
|
||||
.GetValue(task);
|
||||
}
|
||||
|
||||
private static bool IsAssignableToGenericTaskType(Type type)
|
||||
{
|
||||
if (type.IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(Task<>) &&
|
||||
type.GetGenericArguments()[0] != Type.GetType("System.Threading.Tasks.VoidTaskResult"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return type.BaseType is not null && IsAssignableToGenericTaskType(type.BaseType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Net.Extensions.Routing.Delegates;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
internal static class WireMockHttpRequestHandlerExtensions
|
||||
{
|
||||
public static WireMockHttpRequestHandler UseMiddleware(
|
||||
this WireMockHttpRequestHandler handler, WireMockMiddleware middleware) =>
|
||||
middleware(handler);
|
||||
|
||||
public static WireMockHttpRequestHandler UseMiddlewareCollection(
|
||||
this WireMockHttpRequestHandler handler,
|
||||
IReadOnlyCollection<WireMockMiddleware> middlewareCollection) =>
|
||||
middlewareCollection.Aggregate(handler, UseMiddleware);
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Net.Extensions.Routing.Models;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for mapping HTTP routes to handlers in <see cref="WireMockRouter"/>.
|
||||
/// </summary>
|
||||
public static class WireMockRouterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps a GET request to a synchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapGet(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, object?> requestHandler) =>
|
||||
source.Map(HttpMethod.Get.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a GET request to an asynchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapGet(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, Task<object?>> requestHandler) =>
|
||||
source.Map(HttpMethod.Get.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a POST request to a synchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPost(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, object?> requestHandler) =>
|
||||
source.Map(HttpMethod.Post.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a POST request to an asynchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPost(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, Task<object?>> requestHandler) =>
|
||||
source.Map(HttpMethod.Post.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a POST request to a synchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPost<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, object?> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Post.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a POST request to an asynchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPost<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, Task<object?>> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Post.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a PUT request to a synchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPut(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, object?> requestHandler) =>
|
||||
source.Map(HttpMethod.Put.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a PUT request to an asynchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPut(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, Task<object?>> requestHandler) =>
|
||||
source.Map(HttpMethod.Put.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a PUT request to a synchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPut<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, object?> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Put.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a PUT request to an asynchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapPut<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, Task<object?>> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Put.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DELETE request to a synchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapDelete(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, object?> requestHandler) =>
|
||||
source.Map(HttpMethod.Delete.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DELETE request to an asynchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapDelete(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo, Task<object?>> requestHandler) =>
|
||||
source.Map(HttpMethod.Delete.Method, pattern, requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DELETE request to a synchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapDelete<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, object?> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Delete.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DELETE request to an asynchronous request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="source">The router to extend.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public static WireMockRouter MapDelete<TRequest>(
|
||||
this WireMockRouter source,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, Task<object?>> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null) =>
|
||||
source.Map(HttpMethod.Delete.Method, pattern, requestHandler, jsonConverter, jsonOptions);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Net.Extensions.Routing.Delegates;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Server;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for mapping HTTP requests to handlers in <see cref="WireMockServer"/>.
|
||||
/// </summary>
|
||||
public static class WireMockServerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps a request to a WireMock.Net server using the specified method, path matcher, and request handler.
|
||||
/// </summary>
|
||||
/// <param name="source">The WireMock.Net server to extend.</param>
|
||||
/// <param name="method">The HTTP method to match.</param>
|
||||
/// <param name="pathMatcher">The matcher for the request path.</param>
|
||||
/// <param name="httpRequestHandler">The handler to process the request.</param>
|
||||
/// <returns>The current <see cref="WireMockServer"/> instance.</returns>
|
||||
public static WireMockServer Map(
|
||||
this WireMockServer source,
|
||||
string method,
|
||||
IStringMatcher pathMatcher,
|
||||
WireMockHttpRequestHandler httpRequestHandler)
|
||||
{
|
||||
source
|
||||
.Given(Request.Create().WithPath(pathMatcher).UsingMethod(method))
|
||||
.RespondWith(Response.Create().WithCallback(req => httpRequestHandler(req)));
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents request information for WireMock.Net routing, including the request message and route arguments.
|
||||
/// </summary>
|
||||
public class WireMockRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WireMockRequestInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="request">The incoming request message.</param>
|
||||
public WireMockRequestInfo(IRequestMessage request)
|
||||
{
|
||||
Request = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the incoming request message.
|
||||
/// </summary>
|
||||
public IRequestMessage Request { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the route arguments extracted from the request path.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> RouteArgs { get; init; } =
|
||||
new Dictionary<string, object>();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents request information with a strongly-typed deserialized body for WireMock.Net routing.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The type of the deserialized request body.</typeparam>
|
||||
public sealed class WireMockRequestInfo<TBody> : WireMockRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WireMockRequestInfo{TBody}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="request">The incoming request message.</param>
|
||||
public WireMockRequestInfo(IRequestMessage request)
|
||||
: base(request)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the deserialized request body.
|
||||
/// </summary>
|
||||
public TBody? Body { get; init; }
|
||||
}
|
||||
138
src/WireMock.Net.Extensions.Routing/README.md
Normal file
138
src/WireMock.Net.Extensions.Routing/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# WireMock.Net.Extensions.Routing
|
||||
|
||||
**WireMock.Net.Extensions.Routing** extends [WireMock.Net](https://github.com/wiremock/wiremock) with modern, minimal-API-style routing for .NET. It provides extension methods for expressive, maintainable, and testable HTTP routing, inspired by [ASP.NET Core Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-9.0).
|
||||
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
While [WireMock.Net](https://github.com/WireMock-Net/WireMock.Net) is a powerful tool for HTTP mocking in .NET, its native API for defining routes and request handlers can be verbose and require significant boilerplate. Setting up even simple endpoints often involves multiple chained method calls, manual parsing of request data, and repetitive configuration, which can make tests harder to read and maintain.
|
||||
|
||||
**WireMock.Net.Extensions.Routing** addresses these pain points by introducing a concise, fluent, and minimal-API-inspired approach to routing. This makes your test code:
|
||||
|
||||
- **More readable:** Route definitions are clear and expressive, closely resembling production minimal APIs.
|
||||
- **Easier to maintain:** Less boilerplate means fewer places for errors and easier refactoring.
|
||||
- **Faster to write:** Define routes and handlers in a single line, with strong typing and async support.
|
||||
|
||||
### Example: Native WireMock.Net vs. WireMock.Net.Extensions.Routing
|
||||
|
||||
#### Native WireMock.Net
|
||||
|
||||
```csharp
|
||||
server.Given(
|
||||
Request.Create().WithPath("/hello").UsingGet()
|
||||
)
|
||||
.RespondWith(
|
||||
Response.Create().WithBody("Hello, world!")
|
||||
);
|
||||
|
||||
server.Given(
|
||||
Request.Create().WithPath("/user/*").UsingGet()
|
||||
)
|
||||
.RespondWith(
|
||||
Response.Create().WithCallback(request =>
|
||||
{
|
||||
var id = request.PathSegments[1];
|
||||
// ...fetch user by id...
|
||||
return new ResponseMessage { Body = $"User: {id}" };
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
#### With WireMock.Net.Extensions.Routing
|
||||
|
||||
```csharp
|
||||
router.MapGet("/hello", _ => "Hello, world!");
|
||||
|
||||
router.MapGet("/user/{id:int}", requestInfo =>
|
||||
{
|
||||
var id = requestInfo.RouteArgs["id"];
|
||||
// ...fetch user by id...
|
||||
return $"User: {id}";
|
||||
});
|
||||
```
|
||||
|
||||
With **WireMock.Net.Extensions.Routing**, you get:
|
||||
|
||||
- Minimal, one-line route definitions
|
||||
- Typed route parameters (e.g., `{id:int}`)
|
||||
- Direct access to parsed route arguments and request bodies
|
||||
- Async handler support
|
||||
|
||||
This leads to more maintainable, scalable, and production-like test code.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Minimal API-style route definitions for WireMock.Net
|
||||
- Strongly-typed request handling
|
||||
- Routing parameters with constraints (`int` and `string` are currently supported)
|
||||
- Asynchronous handlers
|
||||
- Fluent, composable routing extensions
|
||||
- Easy integration with existing WireMock.Net servers
|
||||
- .NET 8+ support
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Install from NuGet:
|
||||
|
||||
```shell
|
||||
dotnet add package WireMock.Net.Extensions.Routing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```csharp
|
||||
using System.Net.Http.Json;
|
||||
using WireMock.Net.Extensions.Routing;
|
||||
using WireMock.Net.Extensions.Routing.Extensions;
|
||||
using WireMock.Server;
|
||||
|
||||
var server = WireMockServer.Start();
|
||||
var router = new WireMockRouter(server);
|
||||
|
||||
router.MapGet("/hello", _ => "Hello, world!");
|
||||
|
||||
using var client = server.CreateClient();
|
||||
var result = await client.GetFromJsonAsync<string>("/hello");
|
||||
// Hello, world!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Routing with route parameters
|
||||
|
||||
```csharp
|
||||
router.MapGet("/user/{id:int}", async requestInfo => {
|
||||
var userId = requestInfo.RouteArgs["id"];
|
||||
// var user = await ...
|
||||
return user;
|
||||
});
|
||||
```
|
||||
|
||||
### Strongly-Typed Request Info
|
||||
|
||||
```csharp
|
||||
router.MapPost<Item>("/api/items", requestInfo => {
|
||||
var item = requestInfo.Body!;
|
||||
// process item
|
||||
return Results.Json(new { success = true });
|
||||
});
|
||||
```
|
||||
|
||||
### Supported Methods
|
||||
|
||||
- `MapGet`, `MapPost`, `MapPut`, `MapDelete`
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- [API Reference](./src/WireMock.Net.Extensions.Routing/)
|
||||
- [WireMock.Net Documentation](https://github.com/WireMock-Net/WireMock.Net)
|
||||
107
src/WireMock.Net.Extensions.Routing/Utils/RoutePattern.cs
Normal file
107
src/WireMock.Net.Extensions.Routing/Utils/RoutePattern.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using WireMock.Net.Extensions.Routing.Extensions;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Utils;
|
||||
|
||||
internal static class RoutePattern
|
||||
{
|
||||
private static readonly Regex ArgRegex =
|
||||
new(@"{(?'name'\w+)(?::(?'type'\w+))?}", RegexOptions.Compiled);
|
||||
|
||||
public static IDictionary<string, object> GetArgs(string pattern, string route) =>
|
||||
TryGetArgs(pattern, route, out var args)
|
||||
? args
|
||||
: throw new InvalidOperationException(
|
||||
$"Url {route} does not match route pattern {pattern}");
|
||||
|
||||
public static bool TryGetArgs(
|
||||
string pattern, string route, [NotNullWhen(true)] out IDictionary<string, object>? args)
|
||||
{
|
||||
var regex = new Regex(ToRegex(pattern), RegexOptions.IgnoreCase);
|
||||
var match = regex.Match(route);
|
||||
if (!match.Success)
|
||||
{
|
||||
args = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var routeArgTypeMap = GetArgTypeMap(pattern);
|
||||
args = match.Groups
|
||||
.Cast<Group>()
|
||||
.Where(g => g.Index > 0)
|
||||
.ToDictionary(g => g.Name, g => routeArgTypeMap[g.Name].Parse(g.Value));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string ToRegex(string pattern)
|
||||
{
|
||||
return ArgRegex
|
||||
.Replace(pattern, m => $"(?'{m.Groups["name"].Value}'{GetArgMatchingRegex(m)})")
|
||||
.ToMatchFullStringRegex();
|
||||
|
||||
static string GetArgMatchingRegex(Match match) =>
|
||||
ArgType.GetByName(match.Groups["type"].Value).GetRegex();
|
||||
}
|
||||
|
||||
private static IDictionary<string, ArgType> GetArgTypeMap(string pattern) =>
|
||||
ArgRegex
|
||||
.Matches(pattern)
|
||||
.ToDictionary(
|
||||
m => m.Groups["name"].Value, m => ArgType.GetByName(m.Groups["type"].Value));
|
||||
|
||||
private abstract record ArgType
|
||||
{
|
||||
private ArgType(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public static ArgType String { get; } = new StringArgType();
|
||||
|
||||
public static ArgType Int { get; } = new IntArgType();
|
||||
|
||||
private static IReadOnlyCollection<ArgType> All { get; } =
|
||||
typeof(ArgType)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(p => p.PropertyType.IsAssignableTo(typeof(ArgType)))
|
||||
.Select(p => p.GetValue(null))
|
||||
.Cast<ArgType>()
|
||||
.ToList();
|
||||
|
||||
private static IReadOnlyDictionary<string, ArgType> MapByName { get; } =
|
||||
All.ToDictionary(x => x.Name);
|
||||
|
||||
public static ArgType GetByName(string name) =>
|
||||
GetByNameOrDefault(name)
|
||||
?? throw new InvalidOperationException($"Route argument type {name} is not found");
|
||||
|
||||
public static ArgType? GetByNameOrDefault(string name) =>
|
||||
!string.IsNullOrEmpty(name)
|
||||
? MapByName.GetValueOrDefault(name)
|
||||
: String;
|
||||
|
||||
public abstract object Parse(string input);
|
||||
|
||||
public abstract string GetRegex();
|
||||
|
||||
private sealed record StringArgType() : ArgType("string")
|
||||
{
|
||||
public override object Parse(string input) => input;
|
||||
|
||||
public override string GetRegex() => @".*";
|
||||
}
|
||||
|
||||
private sealed record IntArgType() : ArgType("int")
|
||||
{
|
||||
public override object Parse(string input) => int.Parse(input);
|
||||
|
||||
public override string GetRegex() => @"-?\d+";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
|
||||
<Authors>Gennadii Saltyshchak</Authors>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>tdd;mock;http;wiremock;test;server;unittest;routing;minimalapi</PackageTags>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--<TreatWarningsAsErrors>true</TreatWarningsAsErrors>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
153
src/WireMock.Net.Extensions.Routing/WireMockRouter.cs
Normal file
153
src/WireMock.Net.Extensions.Routing/WireMockRouter.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Net.Extensions.Routing.Delegates;
|
||||
using WireMock.Net.Extensions.Routing.Extensions;
|
||||
using WireMock.Net.Extensions.Routing.Models;
|
||||
using WireMock.Net.Extensions.Routing.Utils;
|
||||
using WireMock.Server;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Provides routing and request mapping functionality for WireMock.Net,
|
||||
/// mimicking ASP.NET Core Minimal APIs routing style.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="WireMockRouter"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="server">The WireMock.Net server instance.</param>
|
||||
public sealed class WireMockRouter(WireMockServer server)
|
||||
{
|
||||
private readonly WireMockServer _server = server;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the collection of middleware for the router.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<WireMockMiddleware> MiddlewareCollection { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the default <see cref="IJsonConverter"/> [optional].
|
||||
/// </summary>
|
||||
public IJsonConverter? DefaultJsonConverter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the default <see cref="JsonConverterOptions"/> [optional].
|
||||
/// </summary>
|
||||
public JsonConverterOptions? DefaultJsonOptions { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps a route to a synchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public WireMockRouter Map(
|
||||
string method, string pattern, Func<WireMockRequestInfo, object?> requestHandler)
|
||||
{
|
||||
return Map(method, pattern, CreateResponse);
|
||||
|
||||
object? CreateResponse(IRequestMessage request) =>
|
||||
requestHandler(CreateRequestInfo(request, pattern));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a route to an asynchronous request handler.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The asynchronous request handler function.</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public WireMockRouter Map(
|
||||
string method, string pattern, Func<WireMockRequestInfo, Task<object?>> requestHandler)
|
||||
{
|
||||
return Map(method, pattern, CreateResponseAsync);
|
||||
|
||||
Task<object?> CreateResponseAsync(IRequestMessage request) =>
|
||||
requestHandler(CreateRequestInfo(request, pattern));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a route to a request handler with a typed body.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">The type of the request body.</typeparam>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="pattern">The route pattern.</param>
|
||||
/// <param name="requestHandler">The request handler function.</param>
|
||||
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
|
||||
/// <param name="jsonOptions">The <see cref="JsonConverterOptions"/> [optional].</param>
|
||||
/// <returns>The current <see cref="WireMockRouter"/> instance.</returns>
|
||||
public WireMockRouter Map<TRequest>(
|
||||
string method,
|
||||
string pattern,
|
||||
Func<WireMockRequestInfo<TRequest>, object?> requestHandler,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null)
|
||||
{
|
||||
return Map(method, pattern, CreateBody);
|
||||
|
||||
object? CreateBody(IRequestMessage request) =>
|
||||
requestHandler(CreateRequestInfo<TRequest>(request, pattern, jsonConverter, jsonOptions));
|
||||
}
|
||||
|
||||
private static WireMockRequestInfo CreateRequestInfo(IRequestMessage request, string pattern) =>
|
||||
new(request)
|
||||
{
|
||||
RouteArgs = RoutePattern.GetArgs(pattern, request.Path),
|
||||
};
|
||||
|
||||
private static WireMockHttpRequestHandler CreateHttpRequestHandler(
|
||||
Func<IRequestMessage, object?> requestHandler) =>
|
||||
request => CreateResponseMessageAsync(requestHandler(request));
|
||||
|
||||
private static async Task<ResponseMessage> CreateResponseMessageAsync(object? response)
|
||||
{
|
||||
var awaitedResponse = response is Task task
|
||||
? await task.ToGenericTaskAsync()
|
||||
: response;
|
||||
var result = awaitedResponse as IResult ?? Results.Ok(awaitedResponse);
|
||||
var httpContext = CreateHttpContext();
|
||||
await result.ExecuteAsync(httpContext);
|
||||
return await httpContext.Response.ToResponseMessageAsync();
|
||||
}
|
||||
|
||||
private static HttpContext CreateHttpContext() =>
|
||||
new DefaultHttpContext
|
||||
{
|
||||
RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider(),
|
||||
Response = { Body = new MemoryStream() },
|
||||
};
|
||||
|
||||
private WireMockRequestInfo<TRequest> CreateRequestInfo<TRequest>(
|
||||
IRequestMessage request,
|
||||
string pattern,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? jsonOptions = null)
|
||||
{
|
||||
var requestInfo = CreateRequestInfo(request, pattern);
|
||||
var establishedJsonConverter =
|
||||
jsonConverter ?? DefaultJsonConverter ?? new NewtonsoftJsonConverter();
|
||||
var establishedJsonOptions = jsonOptions ?? DefaultJsonOptions;
|
||||
return new WireMockRequestInfo<TRequest>(requestInfo.Request)
|
||||
{
|
||||
RouteArgs = requestInfo.RouteArgs,
|
||||
Body = requestInfo.Request.GetBodyAsJson<TRequest>(
|
||||
establishedJsonConverter, establishedJsonOptions),
|
||||
};
|
||||
}
|
||||
|
||||
private WireMockRouter Map(
|
||||
string method, string pattern, Func<IRequestMessage, object?> requestHandler)
|
||||
{
|
||||
var matcher = new RegexMatcher(RoutePattern.ToRegex(pattern), ignoreCase: true);
|
||||
var httpRequestHandler =
|
||||
CreateHttpRequestHandler(requestHandler).UseMiddlewareCollection(MiddlewareCollection);
|
||||
_server.Map(method, matcher, httpRequestHandler);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Net.Extensions.Routing.Delegates;
|
||||
using WireMock.Server;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a builder for configuring and creating a <see cref="WireMockRouter"/> with middleware and JSON settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="WireMockServerRouterBuilder"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="server">The WireMock.Net server instance.</param>
|
||||
public sealed class WireMockServerRouterBuilder(WireMockServer server)
|
||||
{
|
||||
private readonly WireMockServer _server = server;
|
||||
|
||||
private readonly ConcurrentQueue<WireMockMiddleware> _middlewareCollection = new();
|
||||
|
||||
private IJsonConverter? _defaultJsonConverter;
|
||||
|
||||
private JsonConverterOptions? _defaultJsonOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a <see cref="WireMockRouter"/> with the configured middleware and JSON settings.
|
||||
/// </summary>
|
||||
/// <returns>The configured <see cref="WireMockRouter"/>.</returns>
|
||||
public WireMockRouter Build() =>
|
||||
new(_server)
|
||||
{
|
||||
MiddlewareCollection = _middlewareCollection,
|
||||
DefaultJsonConverter = _defaultJsonConverter,
|
||||
DefaultJsonOptions = _defaultJsonOptions,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Adds a middleware to the router builder.
|
||||
/// </summary>
|
||||
/// <param name="middleware">The middleware to add.</param>
|
||||
/// <returns>The current <see cref="WireMockServerRouterBuilder"/> instance.</returns>
|
||||
public WireMockServerRouterBuilder Use(WireMockMiddleware middleware)
|
||||
{
|
||||
_middlewareCollection.Enqueue(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default <see cref="IJsonConverter"/>.
|
||||
/// </summary>
|
||||
/// <param name="defaultJsonConverter">the default <see cref="IJsonConverter"/></param>
|
||||
/// <returns>The current <see cref="WireMockServerRouterBuilder"/> instance.</returns>
|
||||
public WireMockServerRouterBuilder WithDefaultJsonConverter(
|
||||
IJsonConverter? defaultJsonConverter)
|
||||
{
|
||||
_defaultJsonConverter = defaultJsonConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default <see cref="JsonConverterOptions"/> [optional].
|
||||
/// </summary>
|
||||
/// <param name="defaultJsonOptions">the default <see cref="JsonConverterOptions"/> [optional]</param>
|
||||
/// <returns>The current <see cref="WireMockServerRouterBuilder"/> instance.</returns>
|
||||
public WireMockServerRouterBuilder WithDefaultJsonOptions(
|
||||
JsonConverterOptions? defaultJsonOptions)
|
||||
{
|
||||
_defaultJsonOptions = defaultJsonOptions;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
public partial class WireMockAssertions
|
||||
{
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, string> AtAbsolutePath(string absolutePath, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
_ = AtAbsolutePath(new ExactMatcher(true, absolutePath), because, becauseArgs);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, string>(this, absolutePath);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, IStringMatcher> AtAbsolutePath(IStringMatcher absolutePathMatcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(request => absolutePathMatcher.IsPerfectMatch(request.AbsolutePath));
|
||||
|
||||
var absolutePath = absolutePathMatcher.GetPatterns().FirstOrDefault().GetPattern();
|
||||
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => RequestMessages)
|
||||
.ForCondition(requests => CallsCount == 0 || requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute path {0}{reason}, but no calls were made.",
|
||||
absolutePath
|
||||
)
|
||||
.Then
|
||||
.ForCondition(condition)
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute path {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => absolutePath,
|
||||
requests => requests.Select(request => request.AbsolutePath)
|
||||
);
|
||||
|
||||
FilterRequestMessages(filter);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, IStringMatcher>(this, absolutePathMatcher);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, string> AtPath(string path, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
_ = AtPath(new ExactMatcher(true, path), because, becauseArgs);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, string>(this, path);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndWhichConstraint<WireMockAssertions, IStringMatcher> AtPath(IStringMatcher pathMatcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(request => pathMatcher.IsPerfectMatch(request.Path));
|
||||
|
||||
var path = pathMatcher.GetPatterns().FirstOrDefault().GetPattern();
|
||||
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => RequestMessages)
|
||||
.ForCondition(requests => CallsCount == 0 || requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the path {0}{reason}, but no calls were made.",
|
||||
path
|
||||
)
|
||||
.Then
|
||||
.ForCondition(condition)
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the path {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => path,
|
||||
requests => requests.Select(request => request.Path)
|
||||
);
|
||||
|
||||
FilterRequestMessages(filter);
|
||||
|
||||
return new AndWhichConstraint<WireMockAssertions, IStringMatcher>(this, pathMatcher);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
#if GRAPHQL
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -14,17 +13,17 @@ using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Exceptions;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers.Models;
|
||||
using WireMock.GraphQL.Models;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
using WireMock.Models.GraphQL;
|
||||
using WireMock.Utils;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// GrapQLMatcher Schema Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
public class GraphQLMatcher : IStringMatcher
|
||||
public class GraphQLMatcher : IGraphQLMatcher
|
||||
{
|
||||
private sealed class GraphQLRequest
|
||||
{
|
||||
@@ -54,7 +53,7 @@ public class GraphQLMatcher : IStringMatcher
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
public GraphQLMatcher(
|
||||
AnyOf<string, StringPattern, ISchema> schema,
|
||||
AnyOf<string, StringPattern, ISchemaData> schema,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
MatchOperator matchOperator = MatchOperator.Or
|
||||
) : this(schema, null, matchBehaviour, matchOperator)
|
||||
@@ -69,7 +68,7 @@ public class GraphQLMatcher : IStringMatcher
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
public GraphQLMatcher(
|
||||
AnyOf<string, StringPattern, ISchema> schema,
|
||||
AnyOf<string, StringPattern, ISchemaData> schema,
|
||||
IDictionary<string, Type>? customScalars,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
MatchOperator matchOperator = MatchOperator.Or
|
||||
@@ -94,7 +93,7 @@ public class GraphQLMatcher : IStringMatcher
|
||||
break;
|
||||
|
||||
case AnyOfType.Third:
|
||||
_schema = schema.Third;
|
||||
_schema = ((SchemaDataWrapper)schema.Third).Schema;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -201,7 +200,7 @@ public class GraphQLMatcher : IStringMatcher
|
||||
throw new WireMockException($"The GraphQL Scalar type '{scalarTypeDefinitionName}' is not defined in the CustomScalars dictionary.");
|
||||
}
|
||||
|
||||
// Create a this custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class)
|
||||
// Create a custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class)
|
||||
var customScalarGraphType = ReflectionUtils.CreateGenericType(customScalarGraphTypeName, typeof(WireMockCustomScalarGraphType<>), clrType);
|
||||
schema.RegisterType(customScalarGraphType);
|
||||
}
|
||||
@@ -209,5 +208,4 @@ public class GraphQLMatcher : IStringMatcher
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
18
src/WireMock.Net.GraphQL/Models/SchemaDataWrapper.cs
Normal file
18
src/WireMock.Net.GraphQL/Models/SchemaDataWrapper.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using GraphQL.Types;
|
||||
using WireMock.Models.GraphQL;
|
||||
|
||||
namespace WireMock.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a wrapper for schema data, providing access to the associated schema.
|
||||
/// </summary>
|
||||
/// <param name="schema"></param>
|
||||
public class SchemaDataWrapper(ISchema schema) : ISchemaData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the schema associated with the current instance.
|
||||
/// </summary>
|
||||
public ISchema Schema { get; } = schema;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
#if GRAPHQL
|
||||
using System;
|
||||
using GraphQL.Types;
|
||||
|
||||
namespace WireMock.Matchers.Models;
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.GraphQL.Models;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract class WireMockCustomScalarGraphType<T> : ScalarGraphType
|
||||
@@ -28,5 +28,4 @@ public abstract class WireMockCustomScalarGraphType<T> : ScalarGraphType
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
8
src/WireMock.Net.GraphQL/Properties/AssemblyInfo.cs
Normal file
8
src/WireMock.Net.GraphQL/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// [assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
// Needed for Moq in the UnitTest project
|
||||
// [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GraphQL.Types;
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Models;
|
||||
using WireMock.Models.GraphQL;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
/// <summary>
|
||||
/// IRequestBuilderExtensions extensions for GraphQL.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IRequestBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL body as a string.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchema"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, new SchemaDataWrapper(schema)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchema"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, new SchemaDataWrapper(schema), customScalars));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchemaData"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchemaData schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchemaData"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchemaData schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace WireMock.Util;
|
||||
namespace WireMock.Utils;
|
||||
|
||||
internal static class ReflectionUtils
|
||||
{
|
||||
47
src/WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj
Normal file
47
src/WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj
Normal file
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>GraphQL support for WireMock.Net</Description>
|
||||
<AssemblyTitle>WireMock.Net.Matchers.GraphQL</AssemblyTitle>
|
||||
<Authors>Stef Heyenrath</Authors>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>wiremock;matchers;matcher;graphql</PackageTags>
|
||||
<RootNamespace>WireMock</RootNamespace>
|
||||
<ProjectGuid>{B6269AAC-170A-4346-8B9A-444DED3D9A45}</ProjectGuid>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<!--<DelaySign>true</DelaySign>-->
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="Nullable" Version="1.3.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -18,7 +18,7 @@ namespace WireMock.Matchers;
|
||||
/// CSharpCode / CS-Script Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="ICSharpCodeMatcher"/>
|
||||
internal class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
public class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
{
|
||||
private const string TemplateForIsMatchWithString = "public class CodeHelper {{ public bool IsMatch(string it) {{ {0} }} }}";
|
||||
|
||||
@@ -63,17 +63,24 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
Value = patterns;
|
||||
}
|
||||
|
||||
public MatchResult IsMatch(string? input)
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input) => IsMatchInternal(input);
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input) => IsMatchInternal(input);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return IsMatchInternal(input);
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
return IsMatchInternal(input);
|
||||
}
|
||||
|
||||
public MatchResult IsMatchInternal(object? input)
|
||||
private MatchResult IsMatchInternal(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
@@ -93,17 +100,6 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private bool IsMatch(dynamic input, string pattern)
|
||||
{
|
||||
var isMatchWithString = input is string;
|
||||
|
||||
@@ -68,7 +68,7 @@ public interface IMapping
|
||||
/// <summary>
|
||||
/// The number of times this match should be matched before the state will be changed to the next state.
|
||||
/// </summary>
|
||||
int? StateTimes { get; }
|
||||
int? TimesInSameState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The RequestMatcher.
|
||||
|
||||
@@ -43,7 +43,7 @@ public class Mapping : IMapping
|
||||
public string? NextState { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? StateTimes { get; }
|
||||
public int? TimesInSameState { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestMatcher RequestMatcher { get; }
|
||||
@@ -137,7 +137,7 @@ public class Mapping : IMapping
|
||||
Scenario = scenario;
|
||||
ExecutionConditionState = executionConditionState;
|
||||
NextState = nextState;
|
||||
StateTimes = stateTimes;
|
||||
TimesInSameState = stateTimes;
|
||||
Webhooks = webhooks;
|
||||
UseWebhooksFireAndForget = useWebhooksFireAndForget;
|
||||
TimeSettings = timeSettings;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using AnyOfTypes;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Models.GraphQL;
|
||||
|
||||
namespace WireMock.Models;
|
||||
|
||||
@@ -22,17 +23,16 @@ public class GraphQLSchemaDetails
|
||||
/// </summary>
|
||||
public StringPattern? SchemaAsStringPattern { get; set; }
|
||||
|
||||
#if GRAPHQL
|
||||
/// <summary>
|
||||
/// The GraphQL schema as a <seealso cref="GraphQL.Types.ISchema"/>.
|
||||
/// The GraphQL schema as a <seealso cref="ISchemaData"/>.
|
||||
/// </summary>
|
||||
public GraphQL.Types.ISchema? SchemaAsISchema { get; set; }
|
||||
public ISchemaData? SchemaAsISchemaData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The GraphQL Schema.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public AnyOf<string, StringPattern, GraphQL.Types.ISchema>? Schema
|
||||
public AnyOf<string, StringPattern, ISchemaData>? Schema
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -46,15 +46,14 @@ public class GraphQLSchemaDetails
|
||||
return SchemaAsStringPattern;
|
||||
}
|
||||
|
||||
if (SchemaAsISchema != null)
|
||||
if (SchemaAsISchemaData != null)
|
||||
{
|
||||
return new AnyOf<string, StringPattern, GraphQL.Types.ISchema>(SchemaAsISchema);
|
||||
return new AnyOf<string, StringPattern, ISchemaData>(SchemaAsISchemaData);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The custom Scalars to define for this schema.
|
||||
|
||||
@@ -299,7 +299,7 @@ namespace WireMock.Owin
|
||||
scenario.Counter++;
|
||||
|
||||
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
|
||||
if (scenario.Counter == (mapping.StateTimes ?? 1))
|
||||
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
|
||||
{
|
||||
scenario.NextState = mapping.NextState;
|
||||
scenario.Counter = 0;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WireMock.Matchers;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
/// <summary>
|
||||
/// The GraphQLRequestBuilder interface.
|
||||
/// </summary>
|
||||
public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// WithGraphQLSchema: The GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithGraphQLSchema: The GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
#if GRAPHQL
|
||||
/// <summary>
|
||||
/// WithGraphQLSchema: The GraphQL schema as a ISchema.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithGraphQLSchema: The GraphQL schema as a ISchema.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a ISchema.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQL: The GraphQL schema as a ISchema.
|
||||
/// </summary>
|
||||
/// <param name="schema">The GraphQL schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
#endif
|
||||
}
|
||||
@@ -98,10 +98,4 @@ public partial class Request
|
||||
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return WithGraphQLSchema(body, matchBehaviour);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
public partial class Request
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return WithBodyAsGraphQL(schema, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return WithBodyAsGraphQL(schema, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -73,6 +73,17 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
||||
return _requestMatchers.OfType<T>().FirstOrDefault(func);
|
||||
}
|
||||
|
||||
public IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
|
||||
{
|
||||
foreach (var existing in _requestMatchers.OfType<T>().ToArray())
|
||||
{
|
||||
_requestMatchers.Remove(existing);
|
||||
}
|
||||
|
||||
_requestMatchers.Add(requestMatcher);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
|
||||
{
|
||||
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
|
||||
@@ -85,15 +96,4 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
||||
protoBufMatcher = bodyMatcher?.Matchers?.OfType<IProtoBufMatcher>().FirstOrDefault();
|
||||
return protoBufMatcher != null;
|
||||
}
|
||||
|
||||
private IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
|
||||
{
|
||||
foreach (var existing in _requestMatchers.OfType<T>().ToArray())
|
||||
{
|
||||
_requestMatchers.Remove(existing);
|
||||
}
|
||||
|
||||
_requestMatchers.Add(requestMatcher);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ using WireMock.Constants;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Models;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Types;
|
||||
@@ -111,15 +110,13 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
sb.AppendLine($" .WithHttpVersion({requestMessageHttpVersionMatcher.HttpVersion})");
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
if (requestMessageGraphQLMatcher?.Matchers != null)
|
||||
{
|
||||
if (requestMessageGraphQLMatcher.Matchers.OfType<GraphQLMatcher>().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
|
||||
if (requestMessageGraphQLMatcher.Matchers.OfType<IGraphQLMatcher>().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
|
||||
{
|
||||
sb.AppendLine($" .WithGraphQLSchema({GetString(graphQLMatcher)})");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (requestMessageMultiPartMatcher?.Matchers != null)
|
||||
{
|
||||
@@ -164,6 +161,16 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
// Guid
|
||||
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
|
||||
|
||||
// Scenario + State
|
||||
if (!string.IsNullOrEmpty(mapping.Scenario))
|
||||
{
|
||||
sb.AppendLine($" .InScenario({ToCSharpStringLiteral(mapping.Scenario)})");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(mapping.NextState))
|
||||
{
|
||||
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
|
||||
}
|
||||
|
||||
if (mapping.Probability != null)
|
||||
{
|
||||
sb.AppendLine($" .WithProbability({mapping.Probability.Value.ToString(CultureInfoUtils.CultureInfoEnUS)})");
|
||||
@@ -270,6 +277,7 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
Scenario = mapping.Scenario,
|
||||
WhenStateIs = mapping.ExecutionConditionState,
|
||||
SetStateTo = mapping.NextState,
|
||||
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
|
||||
Data = mapping.Data,
|
||||
Probability = mapping.Probability,
|
||||
Request = new RequestModel
|
||||
|
||||
@@ -10,6 +10,7 @@ using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Models;
|
||||
using WireMock.Models.GraphQL;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -70,10 +71,11 @@ internal class MatcherMapper
|
||||
|
||||
case nameof(ExactObjectMatcher):
|
||||
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]);
|
||||
#if GRAPHQL
|
||||
case nameof(GraphQLMatcher):
|
||||
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcherModel.CustomScalars, matchBehaviour, matchOperator);
|
||||
#endif
|
||||
|
||||
case "GraphQLMatcher":
|
||||
var patternAsString = stringPatterns[0].GetPattern();
|
||||
var schema = new AnyOf<string, StringPattern, ISchemaData>(patternAsString);
|
||||
return TypeLoader.LoadNewInstance<IGraphQLMatcher>(schema, matcherModel.CustomScalars, matchBehaviour, matchOperator);
|
||||
|
||||
case "MimePartMatcher":
|
||||
return CreateMimePartMatcher(matchBehaviour, matcherModel);
|
||||
@@ -165,11 +167,10 @@ internal class MatcherMapper
|
||||
case XPathMatcher xpathMatcher:
|
||||
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
|
||||
break;
|
||||
#if GRAPHQL
|
||||
case GraphQLMatcher graphQLMatcher:
|
||||
|
||||
case IGraphQLMatcher graphQLMatcher:
|
||||
model.CustomScalars = graphQLMatcher.CustomScalars;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (matcher)
|
||||
@@ -277,10 +278,10 @@ internal class MatcherMapper
|
||||
|
||||
private IMimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel matcher)
|
||||
{
|
||||
var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher;
|
||||
var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher;
|
||||
var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher;
|
||||
var contentMatcher = Map(matcher?.ContentMatcher);
|
||||
var contentTypeMatcher = Map(matcher.ContentTypeMatcher) as IStringMatcher;
|
||||
var contentDispositionMatcher = Map(matcher.ContentDispositionMatcher) as IStringMatcher;
|
||||
var contentTransferEncodingMatcher = Map(matcher.ContentTransferEncodingMatcher) as IStringMatcher;
|
||||
var contentMatcher = Map(matcher.ContentMatcher);
|
||||
|
||||
return TypeLoader.LoadNewInstance<IMimePartMatcher>(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public partial class WireMockServer
|
||||
|
||||
if (!string.IsNullOrEmpty(mappingModel.SetStateTo))
|
||||
{
|
||||
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!);
|
||||
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!, mappingModel.TimesInSameState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
|
||||
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.2.1" />
|
||||
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ namespace WireMock.Extensions;
|
||||
public static class AnyOfExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the pattern.
|
||||
/// Gets the pattern as string value.
|
||||
/// </summary>
|
||||
/// <param name="value">AnyOf type</param>
|
||||
/// <returns>string value</returns>
|
||||
/// <returns>The string value</returns>
|
||||
public static string GetPattern(this AnyOf<string, StringPattern> value)
|
||||
{
|
||||
return value.IsFirst ? value.First : value.Second.Pattern;
|
||||
|
||||
@@ -34,14 +34,10 @@ internal static class BodyDataMatchScoreCalculator
|
||||
}
|
||||
}
|
||||
|
||||
if (matcher is ExactObjectMatcher exactObjectMatcher)
|
||||
if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher)
|
||||
{
|
||||
// If the body is a byte array, try to match.
|
||||
var detectedBodyType = requestMessage.DetectedBodyType;
|
||||
if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded)
|
||||
{
|
||||
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
|
||||
}
|
||||
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
|
||||
}
|
||||
|
||||
// Check if the matcher is a IObjectMatcher
|
||||
@@ -74,4 +70,4 @@ internal static class BodyDataMatchScoreCalculator
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,4 @@ namespace WireMock.Matchers;
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IObjectMatcher"/>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher
|
||||
{
|
||||
}
|
||||
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher;
|
||||
18
src/WireMock.Net.Shared/Matchers/IGraphQLMatcher.cs
Normal file
18
src/WireMock.Net.Shared/Matchers/IGraphQLMatcher.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// GraphQLMatcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
public interface IGraphQLMatcher : IStringMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional dictionary defining the custom Scalar and the type.
|
||||
/// </summary>
|
||||
public IDictionary<string, Type>? CustomScalars { get; }
|
||||
}
|
||||
@@ -3,8 +3,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
using WireMock.Models.GraphQL;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
@@ -34,18 +38,16 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||
{
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="schema">The schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
|
||||
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars = null) :
|
||||
this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema>(schema), customScalars))
|
||||
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, ISchemaData schema, IDictionary<string, Type>? customScalars = null) :
|
||||
this(CreateMatcherArray(matchBehaviour, new AnyOf<string, StringPattern, ISchemaData>(schema), customScalars))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
@@ -89,22 +91,16 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||
|
||||
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
|
||||
{
|
||||
return Matchers == null ? new[] { new MatchResult() } : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
|
||||
return Matchers == null ? [new MatchResult()] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
private static IMatcher[] CreateMatcherArray(
|
||||
MatchBehaviour matchBehaviour,
|
||||
AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema> schema,
|
||||
AnyOf<string, StringPattern, ISchemaData> schema,
|
||||
IDictionary<string, Type>? customScalars
|
||||
)
|
||||
{
|
||||
return new[] { new GraphQLMatcher(schema, customScalars, matchBehaviour) }.Cast<IMatcher>().ToArray();
|
||||
var graphQLMatcher = TypeLoader.LoadNewInstance<IGraphQLMatcher>(schema, customScalars, matchBehaviour, MatchOperator.Or);
|
||||
return [graphQLMatcher];
|
||||
}
|
||||
#else
|
||||
private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema, IDictionary<string, Type>? customScalars)
|
||||
{
|
||||
throw new System.NotSupportedException("The GrapQLMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Minimal, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.MimePart, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
@@ -52,7 +52,7 @@ public interface IBodyRequestBuilder : IProtoBufRequestBuilder
|
||||
IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsJson: A <see cref="JsonMatcher"/> will be used to match this object.
|
||||
/// WithBodyAsJson: A JsonMatcher will be used to match this object.
|
||||
/// </summary>
|
||||
/// <param name="body">The body.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour [default is AcceptOnMatch].</param>
|
||||
@@ -93,12 +93,4 @@ public interface IBodyRequestBuilder : IProtoBufRequestBuilder
|
||||
/// <param name="func">The form-urlencoded values.</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsGraphQLSchema: Body as GraphQL schema as a string.
|
||||
/// </summary>
|
||||
/// <param name="body">The GraphQL schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace WireMock.RequestBuilders;
|
||||
/// <summary>
|
||||
/// The ProtoBufRequestBuilder interface.
|
||||
/// </summary>
|
||||
public interface IProtoBufRequestBuilder : IGraphQLRequestBuilder
|
||||
public interface IProtoBufRequestBuilder : IMultiPartRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,4 +9,5 @@ namespace WireMock.RequestBuilders;
|
||||
/// </summary>
|
||||
public interface IRequestBuilder : IClientIPRequestBuilder
|
||||
{
|
||||
public IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher;
|
||||
}
|
||||
@@ -134,6 +134,8 @@ internal static class CSharpFormatter
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToCSharpIntLiteral(int? value) => value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", value) : Null;
|
||||
|
||||
public static string ToCSharpBooleanLiteral(bool value) => value ? "true" : "false";
|
||||
|
||||
public static string ToCSharpStringLiteral(string? value)
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -34,5 +34,6 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
|
||||
<ProjectReference Include="../WireMock.Net.MimePart/WireMock.Net.MimePart.csproj" />
|
||||
<ProjectReference Include="../WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AwesomeAssertions;
|
||||
using WireMock.Net.Extensions.Routing.Extensions;
|
||||
using WireMock.Server;
|
||||
|
||||
namespace WireMock.Net.Extensions.Routing.Tests.Tests;
|
||||
|
||||
public sealed class WireMockRouterTests
|
||||
{
|
||||
private const string DefaultUrlPattern = "/test";
|
||||
|
||||
private readonly WireMockServer _server = WireMockServer.Start();
|
||||
private readonly WireMockRouter _sut;
|
||||
|
||||
public WireMockRouterTests()
|
||||
{
|
||||
_sut = new WireMockRouter(_server);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Map_ShouldReturnResultFromHandler_ForGetMethod()
|
||||
{
|
||||
const int handlerResult = 5;
|
||||
_sut.Map(HttpMethod.Get.ToString(), DefaultUrlPattern, _ => handlerResult);
|
||||
using var client = _server.CreateClient();
|
||||
|
||||
var result = await client.GetFromJsonAsync<int>(DefaultUrlPattern);
|
||||
|
||||
result.Should().Be(handlerResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Map_ShouldReturnResultFromAsyncHandler_ForGetMethod()
|
||||
{
|
||||
const int handlerResult = 5;
|
||||
_sut.Map(HttpMethod.Get.ToString(), DefaultUrlPattern, _ => Task.FromResult(handlerResult));
|
||||
using var client = _server.CreateClient();
|
||||
|
||||
var result = await client.GetFromJsonAsync<int>(DefaultUrlPattern);
|
||||
|
||||
result.Should().Be(handlerResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Map_ShouldReturnResultFromAsyncHandlerWithAwait_ForGetMethod()
|
||||
{
|
||||
const int handlerResult = 5;
|
||||
_sut.Map(
|
||||
HttpMethod.Get.ToString(),
|
||||
DefaultUrlPattern,
|
||||
async _ => await Task.FromResult(handlerResult));
|
||||
using var client = _server.CreateClient();
|
||||
|
||||
var result = await client.GetFromJsonAsync<int>(DefaultUrlPattern);
|
||||
|
||||
result.Should().Be(handlerResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Map_ShouldReturnResultFromAsyncHandlerWithDelayAndAwait_ForGetMethod()
|
||||
{
|
||||
const int handlerResult = 5;
|
||||
|
||||
async Task<object?> HandleRequestAsync()
|
||||
{
|
||||
await Task.Delay(1);
|
||||
return handlerResult;
|
||||
}
|
||||
|
||||
_sut.Map(HttpMethod.Get.ToString(), DefaultUrlPattern, _ => HandleRequestAsync());
|
||||
using var client = _server.CreateClient();
|
||||
|
||||
var result = await client.GetFromJsonAsync<int>(DefaultUrlPattern);
|
||||
|
||||
result.Should().Be(handlerResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MapGet_ShouldReturnResultFromAsyncHandlerWithDelayAwait()
|
||||
{
|
||||
const int handlerResult = 5;
|
||||
|
||||
async Task<object?> HandleRequestAsync()
|
||||
{
|
||||
await Task.Delay(1);
|
||||
return handlerResult;
|
||||
}
|
||||
|
||||
_sut.MapGet(DefaultUrlPattern, _ => HandleRequestAsync());
|
||||
using var client = _server.CreateClient();
|
||||
|
||||
var result = await client.GetFromJsonAsync<int>(DefaultUrlPattern);
|
||||
|
||||
result.Should().Be(handlerResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Authors>Gennadii Saltyshchak</Authors>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DebugType>full</DebugType>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<SonarQubeExclude>true</SonarQubeExclude>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../../src/WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\WireMock.Net.Extensions.Routing\WireMock.Net.Extensions.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -108,7 +108,7 @@ public class WireMockAssertionsTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedACall_AtAbsoluteUrlWilcardMAtcher_WhenACallWasMadeToAbsoluteUrl_Should_BeOK()
|
||||
public async Task HaveReceivedACall_AtAbsoluteUrlWildcardMatcher_WhenACallWasMadeToAbsoluteUrl_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("anyurl").ConfigureAwait(false);
|
||||
|
||||
@@ -143,6 +143,106 @@ public class WireMockAssertionsTests : IDisposable
|
||||
.WithMessage($"Expected _server to have been called at address matching the absolute url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedNoCalls_AtAbsolutePath_WhenACallWasNotMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("xxx").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceivedNoCalls()
|
||||
.AtAbsolutePath("anypath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceived0Calls_AtAbsolutePath_WhenACallWasNotMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("xxx").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceived(0).Calls()
|
||||
.AtAbsolutePath("anypath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceived1Calls_AtAbsolutePath_WhenACallWasMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("anypath").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceived(1).Calls()
|
||||
.AtAbsolutePath("/anypath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceived1Calls_AtAbsolutePathUsingPost_WhenAPostCallWasMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.PostAsync("anypath", new StringContent("")).ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceived(1).Calls()
|
||||
.AtAbsolutePath("/anypath")
|
||||
.And
|
||||
.UsingPost();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceived2Calls_AtAbsolutePath_WhenACallWasMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("anypath").ConfigureAwait(false);
|
||||
|
||||
await _httpClient.GetAsync("anypath").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceived(2).Calls()
|
||||
.AtAbsolutePath("/anypath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedACall_AtAbsolutePath_WhenACallWasMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("anypath").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceivedACall()
|
||||
.AtAbsolutePath(new WildcardMatcher("/any*"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedACall_AtAbsolutePathWildcardMatcher_WhenACallWasMadeToAbsolutePath_Should_BeOK()
|
||||
{
|
||||
await _httpClient.GetAsync("anypath").ConfigureAwait(false);
|
||||
|
||||
_server.Should()
|
||||
.HaveReceivedACall()
|
||||
.AtAbsolutePath("/anypath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HaveReceivedACall_AtAbsolutePath_Should_ThrowWhenNoCallsWereMade()
|
||||
{
|
||||
Action act = () => _server.Should()
|
||||
.HaveReceivedACall()
|
||||
.AtAbsolutePath("anypath");
|
||||
|
||||
act.Should()
|
||||
.Throw<Exception>()
|
||||
.WithMessage("Expected _server to have been called at address matching the absolute path \"anypath\", but no calls were made.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedACall_AtAbsolutePath_Should_ThrowWhenNoCallsMatchingTheAbsolutePathWereMade()
|
||||
{
|
||||
await _httpClient.GetAsync("").ConfigureAwait(false);
|
||||
|
||||
Action act = () => _server.Should()
|
||||
.HaveReceivedACall()
|
||||
.AtAbsolutePath("/anypath");
|
||||
|
||||
act.Should()
|
||||
.Throw<Exception>()
|
||||
.WithMessage($"Expected _server to have been called at address matching the absolute path \"/anypath\", but didn't find it among the calls to {{\"/\"}}.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HaveReceivedACall_WithHeader_WhenACallWasMadeWithExpectedHeader_Should_BeOK()
|
||||
{
|
||||
|
||||
@@ -165,5 +165,30 @@
|
||||
]
|
||||
},
|
||||
Response: {}
|
||||
},
|
||||
{
|
||||
Guid: 98fae52e-76df-47d9-876f-2ee32e931005,
|
||||
UpdatedAt: 2023-01-14 15:16:17,
|
||||
Scenario: To do list,
|
||||
SetStateTo: TodoList State Started,
|
||||
TimesInSameState: 2,
|
||||
Request: {
|
||||
Path: {
|
||||
Matchers: [
|
||||
{
|
||||
Name: WildcardMatcher,
|
||||
Pattern: /todo/items,
|
||||
IgnoreCase: false
|
||||
}
|
||||
]
|
||||
},
|
||||
Methods: [
|
||||
GET
|
||||
]
|
||||
},
|
||||
Response: {
|
||||
BodyDestination: SameAsSource,
|
||||
Body: Buy milk
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -66,3 +66,15 @@ builder
|
||||
.RespondWith(Response.Create()
|
||||
);
|
||||
|
||||
builder
|
||||
.Given(Request.Create()
|
||||
.UsingMethod("GET")
|
||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/todo/items", false, WireMock.Matchers.MatchOperator.Or))
|
||||
)
|
||||
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931005")
|
||||
.InScenario("To do list")
|
||||
.WhenStateIs("TodoList State Started", 2)
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("Buy milk")
|
||||
);
|
||||
|
||||
|
||||
@@ -66,3 +66,15 @@ server
|
||||
.RespondWith(Response.Create()
|
||||
);
|
||||
|
||||
server
|
||||
.Given(Request.Create()
|
||||
.UsingMethod("GET")
|
||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/todo/items", false, WireMock.Matchers.MatchOperator.Or))
|
||||
)
|
||||
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931005")
|
||||
.InScenario("To do list")
|
||||
.WhenStateIs("TodoList State Started", 2)
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("Buy milk")
|
||||
);
|
||||
|
||||
|
||||
@@ -161,5 +161,30 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
Guid: 98fae52e-76df-47d9-876f-2ee32e931005,
|
||||
UpdatedAt: 2023-01-14T15:16:17,
|
||||
Scenario: To do list,
|
||||
SetStateTo: TodoList State Started,
|
||||
TimesInSameState: 2,
|
||||
Request: {
|
||||
Path: {
|
||||
Matchers: [
|
||||
{
|
||||
Name: WildcardMatcher,
|
||||
Pattern: /todo/items,
|
||||
IgnoreCase: false
|
||||
}
|
||||
]
|
||||
},
|
||||
Methods: [
|
||||
GET
|
||||
]
|
||||
},
|
||||
Response: {
|
||||
BodyDestination: SameAsSource,
|
||||
Body: Buy milk
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -34,6 +34,7 @@ public class MappingBuilderTests
|
||||
private static readonly DateTime UtcNow = new(2023, 1, 14, 15, 16, 17);
|
||||
|
||||
private readonly Mock<IFileSystemHandler> _fileSystemHandlerMock;
|
||||
private readonly int _numMappings;
|
||||
|
||||
private readonly MappingBuilder _sut;
|
||||
|
||||
@@ -104,11 +105,20 @@ public class MappingBuilderTests
|
||||
).RespondWith(Response.Create());
|
||||
|
||||
_sut.Given(Request.Create()
|
||||
.WithPath("/regex")
|
||||
.WithParam("foo", new RegexMatcher(".*"))
|
||||
.UsingGet()
|
||||
)
|
||||
.RespondWith(Response.Create());
|
||||
.WithPath("/regex")
|
||||
.WithParam("foo", new RegexMatcher(".*"))
|
||||
.UsingGet()
|
||||
).RespondWith(Response.Create());
|
||||
|
||||
_sut.Given(Request.Create()
|
||||
.WithPath("/todo/items")
|
||||
.UsingGet())
|
||||
.InScenario("To do list")
|
||||
.WillSetStateTo("TodoList State Started", 2)
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("Buy milk"));
|
||||
|
||||
_numMappings = _sut.GetMappings().Length;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -197,9 +207,9 @@ public class MappingBuilderTests
|
||||
_sut.SaveMappingsToFolder(null);
|
||||
|
||||
// Verify
|
||||
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Exactly(5));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Exactly(5));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(5));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Exactly(_numMappings));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Exactly(_numMappings));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(_numMappings));
|
||||
_fileSystemHandlerMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
@@ -215,8 +225,8 @@ public class MappingBuilderTests
|
||||
|
||||
// Verify
|
||||
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Never);
|
||||
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Exactly(5));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(5));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Exactly(_numMappings));
|
||||
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(_numMappings));
|
||||
_fileSystemHandlerMock.VerifyNoOtherCalls();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#if GRAPHQL
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using WireMock.Matchers.Models;
|
||||
using WireMock.GraphQL.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace WireMock.Net.Tests.Matchers.Models;
|
||||
|
||||
@@ -404,6 +404,79 @@ public class RequestMessageBodyMatcherTests
|
||||
objectMatcherMock.Verify(m => m.IsMatch(It.IsAny<byte[]>()), Times.Once);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[] { 1 })]
|
||||
[InlineData(new byte[] { 48 })]
|
||||
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeBytes_BodyAsBytes_ExactObjectMapper(byte[] bytes)
|
||||
{
|
||||
// Assign
|
||||
var body = new BodyData
|
||||
{
|
||||
BodyAsBytes = bytes,
|
||||
DetectedBodyType = BodyType.Bytes
|
||||
};
|
||||
var exactObjectMapper = new ExactObjectMatcher(bytes);
|
||||
|
||||
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
|
||||
|
||||
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
|
||||
|
||||
// Act
|
||||
var result = new RequestMatchResult();
|
||||
double score = matcher.GetMatchingScore(requestMessage, result);
|
||||
|
||||
// Assert
|
||||
Check.That(score).IsEqualTo(1.0d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeString_BodyAsBytes_ExactObjectMapper()
|
||||
{
|
||||
// Assign
|
||||
var bytes = Encoding.UTF8.GetBytes("hello");
|
||||
var body = new BodyData
|
||||
{
|
||||
BodyAsBytes = bytes,
|
||||
DetectedBodyType = BodyType.String
|
||||
};
|
||||
var exactObjectMapper = new ExactObjectMatcher(bytes);
|
||||
|
||||
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
|
||||
|
||||
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
|
||||
|
||||
// Act
|
||||
var result = new RequestMatchResult();
|
||||
double score = matcher.GetMatchingScore(requestMessage, result);
|
||||
|
||||
// Assert
|
||||
Check.That(score).IsEqualTo(1.0d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeJson_BodyAsBytes_ExactObjectMapper()
|
||||
{
|
||||
// Assign
|
||||
var bytes = Encoding.UTF8.GetBytes("""{"value":42}""");
|
||||
var body = new BodyData
|
||||
{
|
||||
BodyAsBytes = bytes,
|
||||
DetectedBodyType = BodyType.Json
|
||||
};
|
||||
var exactObjectMapper = new ExactObjectMatcher(bytes);
|
||||
|
||||
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
|
||||
|
||||
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
|
||||
|
||||
// Act
|
||||
var result = new RequestMatchResult();
|
||||
double score = matcher.GetMatchingScore(requestMessage, result);
|
||||
|
||||
// Assert
|
||||
Check.That(score).IsEqualTo(1.0d);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MatchingScoreData))]
|
||||
public async Task RequestMessageBodyMatcher_GetMatchingScore_Funcs_Matching(object body, RequestMessageBodyMatcher matcher, bool shouldMatch)
|
||||
@@ -459,13 +532,13 @@ public class RequestMessageBodyMatcherTests
|
||||
{json, new RequestMessageBodyMatcher((object? o) => ((dynamic) o!).a == "b"), true},
|
||||
{json, new RequestMessageBodyMatcher((string? s) => s == json), true},
|
||||
{json, new RequestMessageBodyMatcher((byte[]? b) => b?.SequenceEqual(Encoding.UTF8.GetBytes(json)) == true), true},
|
||||
|
||||
|
||||
// JSON no match ---
|
||||
{json, new RequestMessageBodyMatcher((object? o) => false), false},
|
||||
{json, new RequestMessageBodyMatcher((string? s) => false), false},
|
||||
{json, new RequestMessageBodyMatcher((byte[]? b) => false), false},
|
||||
{json, new RequestMessageBodyMatcher(), false },
|
||||
|
||||
|
||||
// string match +++
|
||||
{str, new RequestMessageBodyMatcher((object? o) => o == null), true},
|
||||
{str, new RequestMessageBodyMatcher((string? s) => s == str), true},
|
||||
@@ -476,7 +549,7 @@ public class RequestMessageBodyMatcherTests
|
||||
{str, new RequestMessageBodyMatcher((string? s) => false), false},
|
||||
{str, new RequestMessageBodyMatcher((byte[]? b) => false), false},
|
||||
{str, new RequestMessageBodyMatcher(), false },
|
||||
|
||||
|
||||
// binary match +++
|
||||
{bytes, new RequestMessageBodyMatcher((object? o) => o == null), true},
|
||||
{bytes, new RequestMessageBodyMatcher((string? s) => s == null), true},
|
||||
|
||||
@@ -549,7 +549,7 @@ message HelloReply {
|
||||
lastName:String
|
||||
fullName:String
|
||||
}";
|
||||
var request = Request.Create().WithBodyAsGraphQLSchema(schema);
|
||||
var request = Request.Create().WithGraphQLSchema(schema);
|
||||
var response = Response.Create();
|
||||
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, null);
|
||||
|
||||
|
||||
@@ -1076,7 +1076,7 @@ message HelloReply {
|
||||
};
|
||||
|
||||
// Act
|
||||
var matcher = (GraphQLMatcher)_sut.Map(model)!;
|
||||
var matcher = (IGraphQLMatcher)_sut.Map(model)!;
|
||||
|
||||
// Assert
|
||||
matcher.GetPatterns().Should().HaveElementAt(0, testSchema);
|
||||
|
||||
Reference in New Issue
Block a user