Compare commits

..

15 Commits

Author SHA1 Message Date
Stef Heyenrath
0d510cdde8 1.8.18 2025-08-04 18:03:44 +02:00
Stef Heyenrath
52a396beef 1.8.18 2025-08-04 18:03:23 +02:00
Sam Fields
6ccfe68686 Fixes an issue with matching JSON bodies as bytes (#1339)
* Fixes an issue with matching JSON bodies as bytes

* Adding tests for exact object matching

* Simplify the check for byte data
2025-08-02 20:11:13 +02:00
Stef Heyenrath
e400e92452 1.8.17 2025-07-23 09:40:55 +02:00
Stef Heyenrath
7a187dfb78 Add more examples for WithGraphQLSchema (#1335) 2025-07-23 09:34:31 +02:00
Stef Heyenrath
e6ff8776fb Make CSharpCodeMatcher public (#1337) 2025-07-23 09:29:53 +02:00
Stef Heyenrath
c32e904f4d 1.8.16 2025-07-19 08:16:34 +02:00
Stef Heyenrath
e80d436dd6 Use corerct Handlebars.Net.Helpers.Xslt (2.5.2) (#1332) 2025-07-18 10:57:58 +02:00
Stef Heyenrath
fcc95ff06f 1.8.15 2025-07-18 08:45:42 +02:00
Stef Heyenrath
020cc15420 Correctly map the Pact Interaction Description property (#1331)
* Correctly map the Pact Interaction Description property

* Update src/WireMock.Net.Minimal/Serialization/PactMapper.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* post

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-18 08:41:04 +02:00
Stef Heyenrath
aeb15725e4 1.8.14 2025-07-13 08:53:53 +02:00
Stef Heyenrath
a06ee6b158 Fix HandlebarsContext.ParseAndEvaluate (#1329) 2025-07-12 11:05:02 +02:00
Stef Heyenrath
b0076b4e81 Implement IMimeMessageData (#1326)
* Implement IMimeMessageData

* 1

* Update src/WireMock.Net.MimePart/Util/MimeKitUtils.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* v1

* v2

* e

* ?

* fix

* if (Array.TrueForAll(_funcs, func => func(value).IsPerfect()))

* Update src/WireMock.Net.Shared/Util/IMimeKitUtils.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.MimePart/Models/MimeEntityDataWrapper.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }

* Update src/WireMock.Net.MimePart/Util/MimeKitUtils.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.MimePart/Models/MimePartDataWrapper.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.MimePart/Models/MimeMessageDataWrapper.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.Shared/Util/IMimeKitUtils.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-12 09:54:18 +02:00
Stef Heyenrath
6c61f87ef3 Add method CreateHttpClientFactory (#1325)
* Add method CreateHttpClientFactory

* rev
2025-07-08 10:50:30 +02:00
Stef Heyenrath
35cd06b47b Update README.md 2025-06-24 09:31:22 +02:00
42 changed files with 1271 additions and 123 deletions

View File

@@ -1,3 +1,25 @@
# 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]
# 1.8.15 (18 July 2025)
- [#1331](https://github.com/wiremock/WireMock.Net/pull/1331) - Correctly map the Pact Interaction Description property [bug] contributed by [StefH](https://github.com/StefH)
- [#1330](https://github.com/wiremock/WireMock.Net/issues/1330) - Generated Pact-compatible consumer contract does not contain description [bug]
# 1.8.14 (13 July 2025)
- [#1325](https://github.com/wiremock/WireMock.Net/pull/1325) - Add method CreateHttpClientFactory [feature] contributed by [StefH](https://github.com/StefH)
- [#1326](https://github.com/wiremock/WireMock.Net/pull/1326) - Implement IMimeMessageData [feature] contributed by [StefH](https://github.com/StefH)
- [#1329](https://github.com/wiremock/WireMock.Net/pull/1329) - Fix HandlebarsContext.ParseAndEvaluate [bug] contributed by [StefH](https://github.com/StefH)
- [#1327](https://github.com/wiremock/WireMock.Net/issues/1327) - Response Body Does Not Evaluate Multiple Handlebars Templates [bug]
# 1.8.13 (23 June 2025)
- [#1322](https://github.com/wiremock/WireMock.Net/pull/1322) - Add Scenario set State method [feature] contributed by [StefH](https://github.com/StefH)
- [#1321](https://github.com/wiremock/WireMock.Net/issues/1321) - Feature: Setting individual scenario state via Admin API [feature]

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.8.13</VersionPrefix>
<VersionPrefix>1.8.18</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.8.13
SET version=1.8.18
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -1,5 +1,5 @@
# 1.8.13 (23 June 2025)
- #1322 Add Scenario set State method [feature]
- #1321 Feature: Setting individual scenario state via Admin API [feature]
# 1.8.18 (04 August 2025)
- #1339 Fixes an issue with matching JSON bodies as bytes [bug]
- #1338 Specifying .WithBody(byte[]) fails to match for JSON bodies [bug]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -132,3 +132,8 @@ For more details see also [Docker](https://github.com/wiremock/WireMock.Net-dock
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/wiremock/WireMock.Net/wiki/Using-HTTPS-(SSL))
---
## Powered by
[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource)

View File

@@ -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

View File

@@ -123,7 +123,7 @@ public interface IRequestMessage
/// The original body as MimeMessage.
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
object? BodyAsMimeMessage { get; }
Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }
#endif
/// <summary>

View File

@@ -0,0 +1,60 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
namespace WireMock.Models.Mime;
/// <summary>
/// An interface exposing the public, readable properties of a ContentDisposition.
/// </summary>
public interface IContentDispositionData
{
/// <summary>
/// Get the disposition.
/// </summary>
/// <value>The disposition.</value>
string Disposition { get; }
/// <summary>
/// Get a value indicating whether the <see cref="IMimeEntityData"/> is an attachment.
/// </summary>
/// <value><see langword="true" /> if the <see cref="IMimeEntityData"/> is an attachment; otherwise, <see langword="false" />.</value>
bool IsAttachment { get; }
/// <summary>
/// Get the list of parameters on the ContentDisposition.
/// </summary>
/// <value>The parameters.</value>
public IList<string> Parameters { get; }
/// <summary>
/// Get the name of the file.
/// </summary>
/// <value>The name of the file.</value>
string FileName { get; }
/// <summary>
/// Get the creation-date parameter.
/// </summary>
/// <value>The creation date.</value>
DateTimeOffset? CreationDate { get; }
/// <summary>
/// Get the modification-date parameter.
/// </summary>
/// <value>The modification date.</value>
DateTimeOffset? ModificationDate { get; }
/// <summary>
/// Get the read-date parameter.
/// </summary>
/// <value>The read date.</value>
DateTimeOffset? ReadDate { get; }
/// <summary>
/// Get the size parameter.
/// </summary>
/// <value>The size.</value>
long? Size { get; }
}

View File

@@ -0,0 +1,67 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Text;
namespace WireMock.Models.Mime;
/// <summary>
/// An interface exposing the public, readable properties of a ContentType
/// with complex types simplified to a generic object.
/// </summary>
public interface IContentTypeData
{
/// <summary>
/// Get the type of the media.
/// </summary>
/// <value>The type of the media.</value>
string MediaType { get; }
/// <summary>
/// Get the media subtype.
/// </summary>
/// <value>The media subtype.</value>
string MediaSubtype { get; }
/// <summary>
/// Get the list of parameters on the ContentType.
/// </summary>
/// <value>The parameters.</value>
IList<string> Parameters { get; }
/// <summary>
/// Get the boundary parameter.
/// </summary>
/// <value>The boundary.</value>
string Boundary { get; }
/// <summary>
/// Get the charset parameter.
/// </summary>
/// <value>The charset.</value>
string Charset { get; }
/// <summary>
/// Get the charset parameter as an Encoding.
/// </summary>
/// <value>The charset encoding.</value>
Encoding CharsetEncoding { get; }
/// <summary>
/// Get the format parameter.
/// </summary>
/// <value>The format.</value>
string Format { get; }
/// <summary>
/// Get the simple mime-type.
/// </summary>
/// <value>The mime-type.</value>
string MimeType { get; }
/// <summary>
/// Get the name parameter.
/// </summary>
/// <value>The name.</value>
string Name { get; }
}

View File

@@ -0,0 +1,54 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
namespace WireMock.Models.Mime;
/// <summary>
/// A simplified interface exposing the public, readable properties of MimeEntity.
/// </summary>
public interface IMimeEntityData
{
/// <summary>
/// Get the list of headers.
/// </summary>
/// <value>The list of headers.</value>
IList<string> Headers { get; }
/// <summary>
/// Get the content disposition.
/// </summary>
/// <value>The content disposition.</value>
IContentDispositionData? ContentDisposition { get; }
/// <summary>
/// Get the type of the content.
/// </summary>
/// <value>The type of the content.</value>
IContentTypeData? ContentType { get; }
/// <summary>
/// Get the base content URI.
/// </summary>
/// <value>The base content URI or <see langword="null"/>.</value>
Uri ContentBase { get; }
/// <summary>
/// Get the content location.
/// </summary>
/// <value>The content location or <see langword="null"/>.</value>
Uri ContentLocation { get; }
/// <summary>
/// Get the Content-Id.
/// </summary>
/// <value>The content identifier.</value>
string ContentId { get; }
/// <summary>
/// Get a value indicating whether this <see cref="IMimeEntityData"/> is an attachment.
/// </summary>
/// <value><see langword="true" /> if this <see cref="IMimeEntityData"/> is an attachment; otherwise, <see langword="false" />.</value>
bool IsAttachment { get; }
}

View File

@@ -0,0 +1,186 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
namespace WireMock.Models.Mime;
/// <summary>
/// A simplified interface exposing the public, readable properties of a MIME message.
/// </summary>
public interface IMimeMessageData
{
/// <summary>
/// Get the list of headers.
/// </summary>
/// <value>The list of headers.</value>
IList<string> Headers { get; }
/// <summary>
/// Get the value of the Importance header.
/// </summary>
/// <value>The importance, as an integer.</value>
int Importance { get; }
/// <summary>
/// Get the value of the Priority header.
/// </summary>
/// <value>The priority, as an integer.</value>
int Priority { get; }
/// <summary>
/// Get the value of the X-Priority header.
/// </summary>
/// <value>The X-priority, as an integer.</value>
int XPriority { get; }
/// <summary>
/// Get the address in the Sender header.
/// </summary>
/// <value>The address in the Sender header.</value>
string Sender { get; }
/// <summary>
/// Get the address in the Resent-Sender header.
/// </summary>
/// <value>The address in the Resent-Sender header.</value>
string ResentSender { get; }
/// <summary>
/// Get the list of addresses in the From header.
/// </summary>
/// <value>The list of addresses in the From header.</value>
IList<string> From { get; }
/// <summary>
/// Get the list of addresses in the Resent-From header.
/// </summary>
/// <value>The list of addresses in the Resent-From header.</value>
IList<string> ResentFrom { get; }
/// <summary>
/// Get the list of addresses in the Reply-To header.
/// </summary>
/// <value>The list of addresses in the Reply-To header.</value>
IList<string> ReplyTo { get; }
/// <summary>
/// Get the list of addresses in the Resent-Reply-To header.
/// </summary>
/// <value>The list of addresses in the Resent-Reply-To header.</value>
IList<string> ResentReplyTo { get; }
/// <summary>
/// Get the list of addresses in the To header.
/// </summary>
/// <value>The list of addresses in the To header.</value>
IList<string> To { get; }
/// <summary>
/// Get the list of addresses in the Resent-To header.
/// </summary>
/// <value>The list of addresses in the Resent-To header.</value>
IList<string> ResentTo { get; }
/// <summary>
/// Get the list of addresses in the Cc header.
/// </summary>
/// <value>The list of addresses in the Cc header.</value>
IList<string> Cc { get; }
/// <summary>
/// Get the list of addresses in the Resent-Cc header.
/// </summary>
/// <value>The list of addresses in the Resent-Cc header.</value>
IList<string> ResentCc { get; }
/// <summary>
/// Get the list of addresses in the Bcc header.
/// </summary>
/// <value>The list of addresses in the Bcc header.</value>
IList<string> Bcc { get; }
/// <summary>
/// Get the list of addresses in the Resent-Bcc header.
/// </summary>
/// <value>The list of addresses in the Resent-Bcc header.</value>
IList<string> ResentBcc { get; }
/// <summary>
/// Get the subject of the message.
/// </summary>
/// <value>The subject of the message.</value>
string Subject { get; }
/// <summary>
/// Get the date of the message.
/// </summary>
/// <value>The date of the message.</value>
DateTimeOffset Date { get; }
/// <summary>
/// Get the Resent-Date of the message.
/// </summary>
/// <value>The Resent-Date of the message.</value>
DateTimeOffset ResentDate { get; }
/// <summary>
/// Get the list of references to other messages.
/// </summary>
/// <value>The references.</value>
IList<string> References { get; }
/// <summary>
/// Get the Message-Id that this message is replying to.
/// </summary>
/// <value>The message id that this message is in reply to.</value>
string InReplyTo { get; }
/// <summary>
/// Get the message identifier.
/// </summary>
/// <value>The message identifier.</value>
string MessageId { get; }
/// <summary>
/// Get the Resent-Message-Id header.
/// </summary>
/// <value>The Resent-Message-Id.</value>
string ResentMessageId { get; }
/// <summary>
/// Get the MIME-Version.
/// </summary>
/// <value>The MIME version.</value>
Version MimeVersion { get; }
/// <summary>
/// Get the body of the message.
/// </summary>
/// <value>The body of the message.</value>
IMimeEntityData Body { get; }
/// <summary>
/// Get the text body of the message if it exists.
/// </summary>
/// <value>The text body if it exists; otherwise, <see langword="null"/>.</value>
string TextBody { get; }
/// <summary>
/// Get the html body of the message if it exists.
/// </summary>
/// <value>The html body if it exists; otherwise, <see langword="null"/>.</value>
string HtmlBody { get; }
/// <summary>
/// Get the body parts of the message.
/// </summary>
/// <value>The body parts.</value>
IList<IMimePartData> BodyParts { get; }
/// <summary>
/// Get the attachments.
/// </summary>
/// <value>The attachments.</value>
IList<IMimeEntityData> Attachments { get; }
}

View File

@@ -0,0 +1,57 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.IO;
namespace WireMock.Models.Mime;
/// <summary>
/// A simplified interface exposing the public, readable properties of MimePart.
/// </summary>
public interface IMimePartData : IMimeEntityData
{
/// <summary>
/// Get the description of the content if available.
/// </summary>
/// <value>The description of the content.</value>
string ContentDescription { get; }
/// <summary>
/// Get the duration of the content if available.
/// </summary>
/// <value>The duration of the content.</value>
int? ContentDuration { get; }
/// <summary>
/// Get the md5sum of the content.
/// </summary>
/// <value>The md5sum of the content.</value>
string ContentMd5 { get; }
/// <summary>
/// Get the content transfer encoding.
/// </summary>
/// <value>The content transfer encoding as a string.</value>
string ContentTransferEncoding { get; }
/// <summary>
/// Get the name of the file.
/// </summary>
/// <value>The name of the file.</value>
string FileName { get; }
/// <summary>
/// Get the MIME content.
/// </summary>
/// <value>The MIME content.</value>
IDictionary<string, object?> Content { get; }
/// <summary>
/// Open the decoded content stream.
/// </summary>
/// <remarks>
/// Provides a means of reading the decoded content without having to first write it to another stream.
/// </remarks>
/// <returns>The decoded content stream.</returns>
Stream Open();
}

View File

@@ -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;

View File

@@ -1,8 +1,8 @@
// Copyright © WireMock.Net
using System;
using MimeKit;
using WireMock.Matchers.Helpers;
using WireMock.Models.Mime;
using WireMock.Util;
namespace WireMock.Matchers;
@@ -12,7 +12,7 @@ namespace WireMock.Matchers;
/// </summary>
public class MimePartMatcher : IMimePartMatcher
{
private readonly Func<MimePart, MatchResult>[] _funcs;
private readonly Func<IMimePartData, MatchResult>[] _funcs;
/// <inheritdoc />
public string Name => nameof(MimePartMatcher);
@@ -52,21 +52,21 @@ public class MimePartMatcher : IMimePartMatcher
_funcs =
[
mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect,
mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition.ToString().Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect,
mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToString().ToLowerInvariant()) ?? MatchScores.Perfect,
mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect,
mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()) ?? MatchScores.Perfect,
MatchOnContent
];
}
/// <inheritdoc />
public MatchResult IsMatch(object value)
public MatchResult IsMatch(IMimePartData value)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
try
{
if (value is MimePart mimePart && Array.TrueForAll(_funcs, func => func(mimePart).IsPerfect()))
if (Array.TrueForAll(_funcs, func => func(value).IsPerfect()))
{
score = MatchScores.Perfect;
}
@@ -85,7 +85,7 @@ public class MimePartMatcher : IMimePartMatcher
return "NotImplemented";
}
private MatchResult MatchOnContent(MimePart mimePart)
private MatchResult MatchOnContent(IMimePartData mimePart)
{
if (ContentMatcher == null)
{
@@ -94,10 +94,10 @@ public class MimePartMatcher : IMimePartMatcher
var bodyParserSettings = new BodyParserSettings
{
Stream = mimePart.Content.Open(),
Stream = mimePart.Open(),
ContentType = GetContentTypeAsString(mimePart.ContentType),
DeserializeJson = true,
ContentEncoding = null, // mimePart.ContentType.CharsetEncoding.ToString(),
ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(),
DecompressGZipAndDeflate = true
};
@@ -105,8 +105,8 @@ public class MimePartMatcher : IMimePartMatcher
return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher);
}
private static string? GetContentTypeAsString(ContentType? contentType)
private static string? GetContentTypeAsString(IContentTypeData? contentType)
{
return contentType?.ToString().Replace("Content-Type: ", string.Empty);
return contentType?.ToString()?.Replace("Content-Type: ", string.Empty);
}
}

View File

@@ -0,0 +1,63 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using MimeKit;
using Stef.Validation;
using WireMock.Models.Mime;
namespace WireMock.Models;
/// <summary>
/// A wrapper class that implements the IContentDispositionData interface
/// by wrapping a ContentDisposition object.
/// </summary>
/// <remarks>
/// This class provides a simplified, read-only view of a ContentDisposition.
/// </remarks>
public class ContentDispositionDataWrapper : IContentDispositionData
{
private readonly ContentDisposition _contentDisposition;
/// <summary>
/// Initializes a new instance of the <see cref="ContentDispositionDataWrapper"/> class.
/// </summary>
/// <param name="contentDisposition">The ContentDisposition to wrap.</param>
public ContentDispositionDataWrapper(ContentDisposition contentDisposition)
{
_contentDisposition = Guard.NotNull(contentDisposition);
Parameters = _contentDisposition.Parameters.Select(p => p.ToString()).ToList();
}
/// <inheritdoc/>
public string Disposition => _contentDisposition.Disposition;
/// <inheritdoc/>
public bool IsAttachment => _contentDisposition.IsAttachment;
/// <inheritdoc/>
public IList<string> Parameters { get; private set; }
/// <inheritdoc/>
public string FileName => _contentDisposition.FileName;
/// <inheritdoc/>
public DateTimeOffset? CreationDate => _contentDisposition.CreationDate;
/// <inheritdoc/>
public DateTimeOffset? ModificationDate => _contentDisposition.ModificationDate;
/// <inheritdoc/>
public DateTimeOffset? ReadDate => _contentDisposition.ReadDate;
/// <inheritdoc/>
public long? Size => _contentDisposition.Size;
/// <inheritdoc/>
public override string ToString()
{
return _contentDisposition.ToString();
}
}

View File

@@ -0,0 +1,65 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MimeKit;
using Stef.Validation;
using WireMock.Models.Mime;
namespace WireMock.Models;
/// <summary>
/// A wrapper class that implements the <see cref="IContentTypeData"/> interface by wrapping a <see cref="ContentType"/> object.
/// </summary>
/// <remarks>
/// This class provides a simplified, read-only view of a <see cref="ContentType"/>.
/// </remarks>
public class ContentTypeDataWrapper : IContentTypeData
{
private readonly ContentType _contentType;
/// <summary>
/// Initializes a new instance of the <see cref="ContentTypeDataWrapper"/> class.
/// </summary>
/// <param name="contentType">The ContentType to wrap.</param>
public ContentTypeDataWrapper(ContentType contentType)
{
_contentType = Guard.NotNull(contentType);
Parameters = _contentType.Parameters.Select(p => p.ToString()).ToList();
}
/// <inheritdoc/>
public string MediaType => _contentType.MediaType;
/// <inheritdoc/>
public string MediaSubtype => _contentType.MediaSubtype;
/// <inheritdoc/>
public IList<string> Parameters { get; private set; }
/// <inheritdoc/>
public string Boundary => _contentType.Boundary;
/// <inheritdoc/>
public string Charset => _contentType.Charset;
/// <inheritdoc/>
public Encoding CharsetEncoding => _contentType.CharsetEncoding;
/// <inheritdoc/>
public string Format => _contentType.Format;
/// <inheritdoc/>
public string MimeType => _contentType.MimeType;
/// <inheritdoc/>
public string Name => _contentType.Name;
/// <inheritdoc/>
public override string ToString()
{
return _contentType.ToString();
}
}

View File

@@ -0,0 +1,61 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using MimeKit;
using Stef.Validation;
using WireMock.Models.Mime;
namespace WireMock.Models;
/// <summary>
/// A wrapper class that implements the <see cref="IMimeEntityData" /> interface by wrapping an <see cref="IMimeEntity" /> interface.
/// </summary>
/// <remarks>
/// This class provides a simplified, read-only view of an <see cref="IMimeEntity"/>.
/// </remarks>
public class MimeEntityDataWrapper : IMimeEntityData
{
private readonly IMimeEntity _entity;
/// <summary>
/// Initializes a new instance of the <see cref="MimeEntityDataWrapper"/> class.
/// </summary>
/// <param name="entity">The MIME entity to wrap.</param>
public MimeEntityDataWrapper(IMimeEntity entity)
{
_entity = Guard.NotNull(entity);
ContentDisposition = _entity.ContentDisposition != null ? new ContentDispositionDataWrapper(_entity.ContentDisposition) : null;
ContentType = _entity.ContentType != null ? new ContentTypeDataWrapper(_entity.ContentType) : null;
Headers = _entity.Headers.Select(h => h.ToString()).ToList();
}
/// <inheritdoc/>
public IList<string> Headers { get; private set; }
/// <inheritdoc/>
public IContentDispositionData? ContentDisposition { get; private set; }
/// <inheritdoc/>
public IContentTypeData? ContentType { get; private set; }
/// <inheritdoc/>
public Uri ContentBase => _entity.ContentBase;
/// <inheritdoc/>
public Uri ContentLocation => _entity.ContentLocation;
/// <inheritdoc/>
public string ContentId => _entity.ContentId;
/// <inheritdoc/>
public bool IsAttachment => _entity.IsAttachment;
/// <inheritdoc/>
public override string ToString()
{
return _entity.ToString()!;
}
}

View File

@@ -0,0 +1,140 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using MimeKit;
using Stef.Validation;
using WireMock.Models.Mime;
namespace WireMock.Models;
/// <summary>
/// A wrapper class that implements the <see cref="IMimeMessageData" /> interface by wrapping an <see cref="IMimeMessage" /> interface.
/// </summary>
/// <remarks>
/// This class provides a simplified, read-only view of an <see cref="IMimeMessage"/>.
/// </remarks>
internal class MimeMessageDataWrapper : IMimeMessageData
{
private readonly IMimeMessage _message;
/// <summary>
/// Initializes a new instance of the <see cref="MimeMessageDataWrapper"/> class.
/// </summary>
/// <param name="message">The MIME message to wrap.</param>
public MimeMessageDataWrapper(IMimeMessage message)
{
_message = Guard.NotNull(message);
Bcc = _message.Bcc.Select(h => h.ToString()).ToList();
Cc = _message.Cc.Select(h => h.ToString()).ToList();
From = _message.From.Select(h => h.ToString()).ToList();
Headers = _message.Headers.Select(h => h.ToString()).ToList();
References = _message.References.ToList();
ReplyTo = _message.ReplyTo.Select(h => h.ToString()).ToList();
ResentBcc = _message.ResentBcc.Select(h => h.ToString()).ToList();
ResentCc = _message.ResentCc.Select(h => h.ToString()).ToList();
ResentFrom = _message.ResentFrom.Select(h => h.ToString()).ToList();
ResentReplyTo = _message.ResentReplyTo.Select(h => h.ToString()).ToList();
ResentTo = _message.ResentTo.Select(h => h.ToString()).ToList();
To = _message.To.Select(h => h.ToString()).ToList();
Body = new MimeEntityDataWrapper(_message.Body);
BodyParts = _message.BodyParts.OfType<MimePart>().Select(mp => new MimePartDataWrapper(mp)).ToList<IMimePartData>();
Attachments = _message.Attachments.Select(me => new MimeEntityDataWrapper(me)).ToList<IMimeEntityData>();
}
/// <inheritdoc/>
public IList<string> Headers { get; private set; }
/// <inheritdoc/>
public int Importance => (int)_message.Importance;
/// <inheritdoc/>
public int Priority => (int)_message.Priority;
/// <inheritdoc/>
public int XPriority => (int)_message.XPriority;
/// <inheritdoc/>
public string Sender => _message.Sender.Address;
/// <inheritdoc/>
public string ResentSender => _message.ResentSender.ToString();
/// <inheritdoc/>
public IList<string> From { get; private set; }
/// <inheritdoc/>
public IList<string> ResentFrom { get; private set; }
/// <inheritdoc/>
public IList<string> ReplyTo { get; private set; }
/// <inheritdoc/>
public IList<string> ResentReplyTo { get; private set; }
/// <inheritdoc/>
public IList<string> To { get; private set; }
/// <inheritdoc/>
public IList<string> ResentTo { get; private set; }
/// <inheritdoc/>
public IList<string> Cc { get; private set; }
/// <inheritdoc/>
public IList<string> ResentCc { get; private set; }
/// <inheritdoc/>
public IList<string> Bcc { get; private set; }
/// <inheritdoc/>
public IList<string> ResentBcc { get; private set; }
/// <inheritdoc/>
public string Subject => _message.Subject;
/// <inheritdoc/>
public DateTimeOffset Date => _message.Date;
/// <inheritdoc/>
public DateTimeOffset ResentDate => _message.ResentDate;
/// <inheritdoc/>
public IList<string> References { get; private set; }
/// <inheritdoc/>
public string InReplyTo => _message.InReplyTo;
/// <inheritdoc/>
public string MessageId => _message.MessageId;
/// <inheritdoc/>
public string ResentMessageId => _message.ResentMessageId;
/// <inheritdoc/>
public Version MimeVersion => _message.MimeVersion;
/// <inheritdoc/>
public IMimeEntityData Body { get; private set; }
/// <inheritdoc/>
public string TextBody => _message.TextBody;
/// <inheritdoc/>
public string HtmlBody => _message.HtmlBody;
/// <inheritdoc/>
public IList<IMimePartData> BodyParts { get; private set; }
/// <inheritdoc/>
public IList<IMimeEntityData> Attachments { get; private set; }
/// <inheritdoc/>
public override string ToString()
{
return _message.ToString();
}
}

View File

@@ -0,0 +1,64 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.IO;
using MimeKit;
using Stef.Validation;
using WireMock.Models.Mime;
namespace WireMock.Models;
/// <summary>
/// A wrapper class that implements the <see cref="IMimePartData" /> interface by wrapping an <see cref="IMimePart"/> interface.
/// </summary>
/// <remarks>
/// This class provides a simplified, read-only view of an <see cref="IMimePart"/>.
/// </remarks>
public class MimePartDataWrapper : MimeEntityDataWrapper, IMimePartData
{
private readonly IMimePart _part;
/// <summary>
/// Initializes a new instance of the <see cref="MimePartDataWrapper"/> class.
/// </summary>
/// <param name="part">The MIME part to wrap.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="part"/> is <see langword="null"/>.
/// </exception>
public MimePartDataWrapper(IMimePart part) : base(part)
{
_part = Guard.NotNull(part);
}
/// <inheritdoc/>
public string ContentDescription => _part.ContentDescription;
/// <inheritdoc/>
public int? ContentDuration => _part.ContentDuration;
/// <inheritdoc/>
public string ContentMd5 => _part.ContentMd5;
/// <inheritdoc/>
public string ContentTransferEncoding => _part.ContentTransferEncoding.ToString();
/// <inheritdoc/>
public string FileName => _part.FileName;
/// <inheritdoc/>
public IDictionary<string, object?> Content => new Dictionary<string, object?>()
{
{ nameof(MimePart.Content.Encoding), _part.Content.Encoding },
{ nameof(MimePart.Content.NewLineFormat), _part.Content.NewLineFormat },
{ nameof(MimePart.Content.Stream), _part.Content.Stream }
};
/// <inheritdoc/>
public Stream Open() => _part.Content.Open();
/// <inheritdoc/>
public override string ToString()
{
return _part.ToString()!;
}
}

View File

@@ -9,6 +9,8 @@ using System.Text;
using MimeKit;
using Stef.Validation;
using WireMock.Http;
using WireMock.Models;
using WireMock.Models.Mime;
using WireMock.Types;
namespace WireMock.Util;
@@ -16,13 +18,13 @@ namespace WireMock.Util;
internal class MimeKitUtils : IMimeKitUtils
{
/// <inheritdoc />
public object LoadFromStream(Stream stream)
public IMimeMessageData LoadFromStream(Stream stream)
{
return MimeMessage.Load(Guard.NotNull(stream));
return new MimeMessageDataWrapper(MimeMessage.Load(Guard.NotNull(stream)));
}
/// <inheritdoc />
public bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out object? mimeMessage)
public bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out IMimeMessageData? mimeMessageData)
{
Guard.NotNull(requestMessage);
@@ -44,26 +46,14 @@ internal class MimeKitUtils : IMimeKitUtils
var fixedBytes = FixBytes(bytes, contentTypeHeader[0]);
mimeMessage = LoadFromStream(new MemoryStream(fixedBytes));
mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes));
return true;
}
mimeMessage = null;
mimeMessageData = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<object> GetBodyParts(object mimeMessage)
{
if (mimeMessage is not MimeMessage mm)
{
throw new ArgumentException($"The mimeMessage must be of type {nameof(MimeMessage)}", nameof(mimeMessage));
}
return mm.BodyParts
.OfType<MimePart>()
.ToArray();
}
private static bool StartsWithMultiPart(WireMockList<string> contentTypeHeader)
{

View File

@@ -0,0 +1,18 @@
// Copyright © WireMock.Net
#if NET5_0_OR_GREATER
using System;
using System.Net.Http;
using WireMock.Server;
namespace WireMock.Http;
internal class WireMockHttpClientFactory(WireMockServer server, params DelegatingHandler[] handlers) : IHttpClientFactory
{
private readonly Lazy<HttpClient> _lazyHttpClient = new(() => server.CreateClient());
public HttpClient CreateClient(string name)
{
return handlers.Length > 0 ? server.CreateClient(handlers) : _lazyHttpClient.Value;
}
}
#endif

View File

@@ -72,7 +72,8 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray())
{
score = MatchScores.Mismatch;
foreach (var mimeBodyPart in MimeKitUtils.GetBodyParts(message))
foreach (var mimeBodyPart in message.BodyParts)
{
var matchResult = mimePartMatcher.IsMatch(mimeBodyPart);
if (matchResult.IsPerfect())

View File

@@ -85,7 +85,7 @@ public class RequestMessage : IRequestMessage
#if MIMEKIT
/// <inheritdoc />
[Newtonsoft.Json.JsonIgnore] // Issue 1001
public object? BodyAsMimeMessage { get; }
public Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }
#endif
/// <inheritdoc />

View File

@@ -42,8 +42,7 @@ internal static class PactMapper
var interaction = new Interaction
{
Description = mapping.Description,
ProviderState = mapping.Title,
Description = !string.IsNullOrWhiteSpace(mapping.Description) ? mapping.Description : mapping.Title ?? string.Empty,
Request = MapRequest(mapping.Request, path),
Response = MapResponse(mapping.Response)
};

View File

@@ -120,6 +120,32 @@ public partial class WireMockServer : IWireMockServer
#endregion
#region HttpClient
#if NET5_0_OR_GREATER
private readonly Lazy<IHttpClientFactory> _lazyHttpClientFactory;
/// <summary>
/// Create a <see cref="IHttpClientFactory"/> which can be used to generate a HttpClient to call this instance.
/// <param name="handlers">
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public IHttpClientFactory CreateHttpClientFactory(params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create IHttpClientFactory because the service is not started.");
}
return handlers.Length > 0 ? new WireMockHttpClientFactory(this, handlers) : _lazyHttpClientFactory.Value;
}
#endif
/// <summary>
/// Create a <see cref="HttpClient"/> which can be used to call this instance.
/// <param name="handlers">
@@ -427,6 +453,10 @@ public partial class WireMockServer : IWireMockServer
}
InitSettings(settings);
#if NET5_0_OR_GREATER
_lazyHttpClientFactory = new Lazy<IHttpClientFactory>(() => new WireMockHttpClientFactory(this));
#endif
}
/// <inheritdoc cref="IWireMockServer.Stop" />

View File

@@ -1,5 +1,6 @@
// Copyright © WireMock.Net
using System.Text.RegularExpressions;
using HandlebarsDotNet;
using HandlebarsDotNet.Helpers.Extensions;
using Stef.Validation;
@@ -9,6 +10,8 @@ namespace WireMock.Transformers.Handlebars;
internal class HandlebarsContext : IHandlebarsContext
{
private static readonly Regex _tryEvaluateRegex = new(@"\{\{.*?\}\}", RegexOptions.Compiled);
public IHandlebars Handlebars { get; }
public IFileSystemHandler FileSystemHandler { get; }
@@ -27,9 +30,8 @@ internal class HandlebarsContext : IHandlebarsContext
public object? ParseAndEvaluate(string text, object model)
{
if (text.StartsWith("{{") && text.EndsWith("}}") &&
Handlebars.TryEvaluate(text, model, out var result) &&
result is not UndefinedBindingResult)
// Only try to evaluate if the text matches the pattern `{{ xxx }}` exactly once.
if (_tryEvaluateRegex.Matches(text).Count == 1 && Handlebars.TryEvaluate(text, model, out var result) && result is not UndefinedBindingResult)
{
return result;
}

View File

@@ -167,7 +167,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' ">
<PackageReference Include="Handlebars.Net.Helpers.Xslt" Version="2.4.6" />
<PackageReference Include="Handlebars.Net.Helpers.Xslt" Version="2.5.2" />
</ItemGroup>
<!--<ItemGroup>

View File

@@ -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;
}
}
}

View File

@@ -7,6 +7,4 @@ namespace WireMock.Matchers;
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher
{
}
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher;

View File

@@ -1,5 +1,7 @@
// Copyright © WireMock.Net
using WireMock.Models.Mime;
namespace WireMock.Matchers;
/// <summary>
@@ -33,5 +35,5 @@ public interface IMimePartMatcher : IMatcher
/// </summary>
/// <param name="value">The MimePart.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public MatchResult IsMatch(object value);
public MatchResult IsMatch(IMimePartData value);
}

View File

@@ -1,8 +1,8 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using WireMock.Models.Mime;
namespace WireMock.Util;
@@ -12,24 +12,16 @@ namespace WireMock.Util;
public interface IMimeKitUtils
{
/// <summary>
/// Loads the MimeKit.MimeMessage from the stream.
/// Loads the <see cref="IMimeMessageData"/> from the stream.
/// </summary>
/// <param name="stream">The stream</param>
/// <returns>MimeKit.MimeMessage</returns>
object LoadFromStream(Stream stream);
IMimeMessageData LoadFromStream(Stream stream);
/// <summary>
/// Tries to get the MimeKit.MimeMessage from the request message.
/// Tries to get the <see cref="IMimeMessageData"/> from the request message.
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <param name="mimeMessage">The MimeKit.MimeMessage</param>
/// <param name="mimeMessageData">A class MimeMessageDataWrapper which wraps a MimeKit.MimeMessage.</param>
/// <returns><c>true</c> when parsed correctly, else <c>false</c></returns>
bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out object? mimeMessage);
/// <summary>
/// Gets the body parts from the MimeKit.MimeMessage.
/// </summary>
/// <param name="mimeMessage">The MimeKit.MimeMessage.</param>
/// <returns>A list of MimeParts.</returns>
IReadOnlyList<object> GetBodyParts(object mimeMessage);
bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out IMimeMessageData? mimeMessageData);
}

View File

@@ -24,7 +24,6 @@ using WireMock.Client.Extensions;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Net.Tests.VerifyExtensions;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -185,7 +184,7 @@ public partial class WireMockAdminApiTests
server.Stop();
}
[Fact]
public async Task IWireMockAdminApi_FindRequestsAsync()

View File

@@ -48,7 +48,7 @@ public class MimePartMatcherTests
{
// Arrange
var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart));
var part = MimeKitUtils.GetBodyParts(message)[0];
var part = message.BodyParts[0];
// Act
var contentTypeMatcher = new ContentTypeMatcher("text/plain");
@@ -67,7 +67,7 @@ public class MimePartMatcherTests
{
// Arrange
var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart));
var part = MimeKitUtils.GetBodyParts(message)[1];
var part = message.BodyParts[1];
// Act
var contentTypeMatcher = new ContentTypeMatcher("text/json");
@@ -85,7 +85,7 @@ public class MimePartMatcherTests
{
// Arrange
var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart));
var part = MimeKitUtils.GetBodyParts(message)[2];
var part = message.BodyParts[2];
// Act
var contentTypeMatcher = new ContentTypeMatcher("image/png");

View File

@@ -0,0 +1,32 @@
{
"consumer": {
"name": "Something API Consumer Get"
},
"interactions": [
{
"description": "A GET request to retrieve the something",
"request": {
"headers": {
"Accept": "application/json"
},
"method": "GET",
"path": "/tester",
"query": "q1=test&q2=ok"
},
"response": {
"body": {
"id": "tester",
"firstName": "Totally",
"lastName": "Awesome"
},
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"status": 200
}
}
],
"provider": {
"name": "Something API"
}
}

View File

@@ -0,0 +1,20 @@
{
"consumer": {
"name": "Default Consumer"
},
"interactions": [
{
"description": "A POST request to change something",
"request": {
"method": "POST",
"path": "/tester"
},
"response": {
"status": 200
}
}
],
"provider": {
"name": "Default Provider"
}
}

View File

@@ -1,11 +1,14 @@
#if !(NET452 || NET461 || NETCOREAPP3_1)
// Copyright © WireMock.Net
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using VerifyXunit;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -14,10 +17,11 @@ using Xunit;
namespace WireMock.Net.Tests.Pact;
[UsesVerify]
public class PactTests
{
[Fact]
public void SavePact_Get_Request_And_Response_WithBodyAsJson()
public async Task SavePact_Get_Request_And_Response_WithBodyAsJson()
{
var server = WireMockServer.Start();
server
@@ -46,12 +50,34 @@ public class PactTests
var folder = Path.Combine("../../../", "Pact", "files");
var file = "pact-get.json";
var path = Path.Combine(folder, file);
// Act
server.SavePact(folder, file);
// Assert
File.ReadAllBytes(Path.Combine(folder, file)).Length.Should().BeGreaterThan(1);
await Verifier.VerifyFile(path);
}
[Fact]
public async Task SavePact_Post_Request_WithDescription()
{
var server = WireMockServer.Start();
server
.Given(Request.Create().UsingPost().WithPath("/tester"))
.WithTitle("POST something")
.WithDescription("A POST request to change something")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
var folder = Path.Combine("../../../", "Pact", "files");
var file = "pact-post.json";
var path = Path.Combine(folder, file);
// Act
server.SavePact(folder, file);
// Assert
await Verifier.VerifyFile(path);
}
[Fact]
@@ -219,4 +245,5 @@ public class PactTests
// Assert
File.ReadAllBytes(Path.Combine(folder, file)).Length.Should().BeGreaterThan(1);
}
}
}
#endif

View File

@@ -4,7 +4,7 @@
},
"interactions": [
{
"providerState": "A GET request to retrieve the something",
"description": "A GET request to retrieve the something",
"request": {
"headers": {
"Accept": "application/json"

View File

@@ -4,7 +4,7 @@
},
"interactions": [
{
"providerState": "A GET request to retrieve the something",
"description": "A GET request to retrieve the something",
"request": {
"headers": {
"Accept": "application/json"
@@ -26,7 +26,7 @@
}
},
{
"providerState": "A Post request to add the something",
"description": "A Post request to add the something",
"request": {
"headers": {
"Accept": "application/json"

View File

@@ -0,0 +1,20 @@
{
"consumer": {
"name": "Default Consumer"
},
"interactions": [
{
"description": "A POST request to change something",
"request": {
"method": "POST",
"path": "/tester"
},
"response": {
"status": 200
}
}
],
"provider": {
"name": "Default Provider"
}
}

View File

@@ -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},

View File

@@ -120,21 +120,21 @@ public class ResponseWithTransformerTests
var request = new RequestMessage(urlDetails, "POST", ClientIp);
var responseBuilder = Response.Create()
.WithBody("{{request.PathSegments.[0]}} {{request.AbsolutePathSegments.[0]}}")
.WithBody("{{request.PathSegments.[0]}} {{request.PathSegments.[1]}} {{request.AbsolutePathSegments.[0]}}")
.WithTransformer();
// Act
var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false);
// Assert
Check.That(response.Message.BodyData!.BodyAsString).Equals("a wiremock");
Check.That(response.Message.BodyData!.BodyAsString).Equals("a b wiremock");
}
[Theory]
[InlineData("{{request.PathSegments.[0]}}", "a")]
[InlineData("prefix_{{request.PathSegments.[0]}}", "prefix_a")]
[InlineData("{{request.PathSegments.[0]}}_postfix", "a_postfix")]
[InlineData("prefix_{{request.PathSegments.[0]}}_postfix", "prefix_a_postfix")]
[InlineData("{{request.PathSegments.[0]}} {{request.PathSegments.[1]}}", "a b")]
[InlineData("prefix_{{request.PathSegments.[0]}} {{request.PathSegments.[1]}}", "prefix_a b")]
[InlineData("{{request.PathSegments.[0]}} {{request.PathSegments.[1]}}_postfix", "a b_postfix")]
[InlineData("prefix_{{request.PathSegments.[0]}} {{request.PathSegments.[1]}}_postfix", "prefix_a b_postfix")]
public async Task Response_ProvideResponse_Handlebars_BodyAsJson_PathSegments(string field, string expected)
{
// Assign

View File

@@ -560,6 +560,30 @@ public class WireMockServerAdminTests
Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Equals($"{{\"Status\":\"Mappings deleted. Affected GUIDs: [{guid1}, {guid2}]\"}}");
}
#if NET5_0_OR_GREATER
[Fact]
public async Task WireMockServer_CreateHttpClientFactory_And_CallEndpoint()
{
// Arrange
var server = WireMockServer.Start();
var factory = server.CreateHttpClientFactory();
var client = factory.CreateClient("any name");
// Act
await client.GetAsync($"{server.Url}/foo").ConfigureAwait(false);
// Assert
Check.That(server.LogEntries).HasSize(1);
var requestLogged = server.LogEntries.First();
Check.That(requestLogged.RequestMessage.Method).IsEqualTo("GET");
Check.That(requestLogged.RequestMessage.BodyData).IsNull();
// Cleanup
server.Stop();
server.Dispose();
}
#endif
[Fact]
public async Task WireMockServer_CreateClient_And_CallEndpoint()
{