Compare commits

...

16 Commits

Author SHA1 Message Date
Stef Heyenrath
71393204cc 1.5.26 2023-05-25 21:36:01 +02:00
Cezary Piątek
e5cc6f570c Code generator improvements (#940)
* Fix quotation marks escaping in multiline string

* Add support for JsonPartialMatcher and JsonPartialWildcardMatcher in mapping code generator
2023-05-25 20:59:13 +02:00
Stef Heyenrath
7c3a0c815d Add GetParameter method to IRequestMessage (#942) 2023-05-25 15:14:02 +02:00
Stef Heyenrath
e61f08fe48 WireMockMiddleware should use HandleRequestsSynchronously correctly (#939) 2023-05-19 21:43:14 +02:00
Stef Heyenrath
11f4c47851 Add more unitests for CSharpFormatter utils (#938)
* Add unit-tests for CSharpFormatter

* .

* t
2023-05-19 16:14:26 +02:00
Stef Heyenrath
3956cd703b 1.5.25 (ReleaseNotes) 2023-05-13 11:17:03 +02:00
Stef Heyenrath
27682d0ce4 1.5.25 + Fixes in CSharpFormatter 2023-05-13 11:15:40 +02:00
Cezary Piątek
8444c8c506 Code generator improvements (#934)
* Handle new line escaping in C# mapping code generator

* Prevent date conversion when value persisted as string

* Handle object properties named as csharp keywords

* Refactor: Extract logic responsible for generating anonymous object definition to a separate class
2023-05-13 09:33:25 +02:00
Stef Heyenrath
6ef116a295 1.5.24 2023-05-07 14:48:20 +02:00
Stef Heyenrath
59195eaed8 Refactor some code (MappingConverter) 2023-05-07 14:42:00 +02:00
Cezary Piątek
7d9e450814 C# code generator improvements (#933)
* Escape quotes in generated C# strings

* Handle response with JSON body in C# code generator
2023-05-07 14:37:48 +02:00
Stef Heyenrath
7019a5a78c Add Package Readme (#932)
* Add Package Readme

* C# .NET API

* <PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
2023-05-07 09:24:48 +02:00
Stef Heyenrath
d29f3e81f3 Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' (#931)
* Add property 'IsStartedWithAdminInterface' to 'IWireMockServer'

* update tests

* .
2023-05-06 13:12:00 +02:00
Stef Heyenrath
ccd8026884 Update C# mapping code generator for WithStatusCode (#930) 2023-05-06 10:24:53 +02:00
Cezary Piątek
1214ba5108 Enrich generated code with status code (#927) 2023-05-06 09:40:41 +02:00
Cezary Piątek
427715a38a Fix csharp mapping code generator (#926) 2023-05-02 16:48:32 +02:00
24 changed files with 592 additions and 47 deletions

View File

@@ -1,3 +1,20 @@
# 1.5.26 (25 May 2023)
- [#938](https://github.com/WireMock-Net/WireMock.Net/pull/938) - Add more unitests for CSharpFormatter utils [test] contributed by [StefH](https://github.com/StefH)
- [#939](https://github.com/WireMock-Net/WireMock.Net/pull/939) - WireMockMiddleware should use HandleRequestsSynchronously correctly [bug] contributed by [StefH](https://github.com/StefH)
- [#940](https://github.com/WireMock-Net/WireMock.Net/pull/940) - Code generator improvements contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#942](https://github.com/WireMock-Net/WireMock.Net/pull/942) - Add GetParameter method to IRequestMessage [feature] contributed by [StefH](https://github.com/StefH)
- [#941](https://github.com/WireMock-Net/WireMock.Net/issues/941) - RequestMessage.GetParameter method missing from IRequestMessage interface [feature]
# 1.5.25 (13 May 2023)
- [#934](https://github.com/WireMock-Net/WireMock.Net/pull/934) - Code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.24 (07 May 2023)
- [#926](https://github.com/WireMock-Net/WireMock.Net/pull/926) - Fix C# mapping code generator for header names [bug] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#927](https://github.com/WireMock-Net/WireMock.Net/pull/927) - Enrich generated code with status code [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#930](https://github.com/WireMock-Net/WireMock.Net/pull/930) - Update C# mapping code generator for WithStatusCode [feature] contributed by [StefH](https://github.com/StefH)
- [#931](https://github.com/WireMock-Net/WireMock.Net/pull/931) - Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' [feature] contributed by [StefH](https://github.com/StefH)
- [#933](https://github.com/WireMock-Net/WireMock.Net/pull/933) - C# code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.23 (23 April 2023)
- [#922](https://github.com/WireMock-Net/WireMock.Net/pull/922) - Add WithProbability [feature] contributed by [StefH](https://github.com/StefH)
- [#924](https://github.com/WireMock-Net/WireMock.Net/pull/924) - Allow removal of prefix when proxying to another server (#630) [feature] contributed by [nudejustin](https://github.com/nudejustin)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.23</VersionPrefix>
<VersionPrefix>1.5.26</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -29,7 +30,7 @@
<ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" />
<!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>-->
<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>
</ItemGroup>
<Choose>

View File

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

55
PackageReadme.md Normal file
View File

@@ -0,0 +1,55 @@
## WireMock.Net
Lightweight Http Mocking Server for .NET, inspired by [WireMock(http://WireMock.org) from the Java landscape.
### :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
### :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
### :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
### :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
### :star: Using
WireMock.Net can be used in several ways:
#### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
#### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
#### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
#### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
#### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
#### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/WireMock-Net/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/WireMock-Net/WireMock.Net/wiki/Using-HTTPS-(SSL))
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).

View File

@@ -1,6 +1,8 @@
# 1.5.23 (23 April 2023)
- #922 Add WithProbability [feature]
- #924 Allow removal of prefix when proxying to another server (#630) [feature]
- #925 Add IgnoreCase option to ProxyUrlReplaceSettings [feature]
# 1.5.26 (25 May 2023)
- #938 Add more unitests for CSharpFormatter utils [test]
- #939 WireMockMiddleware should use HandleRequestsSynchronously correctly [bug]
- #940 Code generator improvements
- #942 Add GetParameter method to IRequestMessage [feature]
- #941 RequestMessage.GetParameter method missing from IRequestMessage interface [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,24 +1,24 @@
# WireMock.Net
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock.org](http://WireMock.org).
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock](http://WireMock.org).
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
## Key Features
## :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent DotNet API, JSON files and JSON over HTTP
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
## Blogs
## :memo: Blogs
- [mStack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
## Project Info
## :computer: Project Info
| | |
| --- | --- |
| ***Project*** | &nbsp; |
@@ -31,7 +31,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/WireMock-Net/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/WireMock-Net/WireMock.Net)|
### NuGet packages
### :package: NuGet packages
| | Official | Preview [:information_source:](https://github.com/WireMock-Net/WireMock.Net/wiki/MyGet-preview-versions) |
| - | - | - |
@@ -45,23 +45,23 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
## Development
## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.
## Stubbing
## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
## Request Matching
## :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
## Response Templating
## :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
## Admin API Reference
## :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
## Using
## :star: Using
WireMock.Net can be used in several ways:
### UnitTesting

View File

@@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config
PackageReadme.md = PackageReadme.md
PackageReleaseNotes.template = PackageReleaseNotes.template
PackageReleaseNotes.txt = PackageReleaseNotes.txt
README.md = README.md

View File

@@ -143,6 +143,14 @@ public interface IRequestMessage
/// </summary>
string Origin { get; }
/// <summary>
/// Get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <returns>The query parameter value as WireMockList or null when not found.</returns>
WireMockList<string>? GetParameter(string key, bool ignoreCase = false);
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Gets the connection's client certificate

View File

@@ -17,6 +17,11 @@ public interface IWireMockServer : IDisposable
/// </summary>
bool IsStarted { get; }
/// <summary>
/// Gets a value indicating whether this server is started with the admin interface enabled.
/// </summary>
bool IsStartedWithAdminInterface { get; }
/// <summary>
/// Gets the request logs.
/// </summary>

View File

@@ -60,7 +60,7 @@ namespace WireMock.Owin
public Task Invoke(IContext ctx)
#endif
{
if (_options.HandleRequestsSynchronously.GetValueOrDefault(true))
if (_options.HandleRequestsSynchronously.GetValueOrDefault(false))
{
lock (_lock)
{

View File

@@ -180,12 +180,7 @@ public class RequestMessage : IRequestMessage
#endif
}
/// <summary>
/// Get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <returns>The query parameter.</returns>
/// <inheritdoc />
public WireMockList<string>? GetParameter(string key, bool ignoreCase = false)
{
if (Query == null)

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Constants;
@@ -17,6 +18,7 @@ using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using static WireMock.Util.CSharpFormatter;
namespace WireMock.Serialization;
internal class MappingConverter
@@ -105,11 +107,28 @@ internal class MappingConverter
if (bodyMatcher is { Matchers: { } })
{
var wildcardMatcher = bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault();
if (wildcardMatcher is { } && wildcardMatcher.GetPatterns().Any())
if (bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
{
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
}
else if (bodyMatcher.Matchers.OfType<JsonPartialMatcher>().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
))");
}
else if (bodyMatcher.Matchers.OfType<JsonPartialWildcardMatcher>().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.Regex)}
))");
}
}
sb.AppendLine(@" )");
@@ -125,21 +144,42 @@ internal class MappingConverter
// Response
sb.AppendLine(" .RespondWith(Response.Create()");
if (response.ResponseMessage.StatusCode is int or string)
{
sb.AppendLine($" .WithStatusCode({JsonConvert.SerializeObject(response.ResponseMessage.StatusCode)})");
}
else if (response.ResponseMessage.StatusCode is HttpStatusCode httpStatusCode)
{
sb.AppendLine($" .WithStatusCode({(int)httpStatusCode})");
}
if (response.ResponseMessage.Headers is { })
{
foreach (var header in response.ResponseMessage.Headers)
{
sb.AppendLine($" .WithHeader(\"{header.Key})\", {ToValueArguments(header.Value.ToArray())})");
sb.AppendLine($" .WithHeader(\"{header.Key}\", {ToValueArguments(header.Value.ToArray())})");
}
}
if (response.ResponseMessage.BodyData is { })
if (response.ResponseMessage.BodyData is { } bodyData)
{
switch (response.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")");
sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyData.BodyAsString)})");
break;
case BodyType.Json:
if (bodyData.BodyAsJson is string bodyStringValue)
{
sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyStringValue)})");
}
else if(bodyData.BodyAsJson is {} jsonBody)
{
var anonymousObjectDefinition = ConvertToAnonymousObjectDefinition(jsonBody);
sb.AppendLine($" .WithBodyAsJson({anonymousObjectDefinition})");
}
break;
}
}
@@ -382,7 +422,7 @@ internal class MappingConverter
private static string GetString(IStringMatcher stringMatcher)
{
return stringMatcher.GetPatterns().Select(p => $"\"{p.GetPattern()}\"").First();
return stringMatcher.GetPatterns().Select(p => ToCSharpStringLiteral(p.GetPattern())).First();
}
private static string[] GetStringArray(IReadOnlyList<IStringMatcher> stringMatchers)
@@ -435,7 +475,7 @@ internal class MappingConverter
private static string ToValueArguments(string[]? values, string defaultValue = "")
{
return values is { } ? string.Join(", ", values.Select(v => $"\"{v}\"")) : $"\"{defaultValue}\"";
return values is { } ? string.Join(", ", values.Select(ToCSharpStringLiteral)) : ToCSharpStringLiteral(defaultValue);
}
private static WebProxyModel? MapWebProxy(WebProxySettings? settings)
@@ -466,4 +506,6 @@ internal class MappingConverter
return newDictionary;
}
}

View File

@@ -43,10 +43,14 @@ public partial class WireMockServer : IWireMockServer
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
/// <inheritdoc cref="IWireMockServer.IsStarted" />
/// <inheritdoc />
[PublicAPI]
public bool IsStarted => _httpServer is { IsStarted: true };
/// <inheritdoc />
[PublicAPI]
public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault();
/// <inheritdoc />
[PublicAPI]
public List<int> Ports { get; }

View File

@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WireMock.Util;
internal static class CSharpFormatter
{
#region Reserved Keywords
private static readonly HashSet<string> CSharpReservedKeywords = new(new[]
{
"abstract",
"as",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"void",
"volatile",
"while"
});
#endregion
private const string Null = "null";
public static object ConvertToAnonymousObjectDefinition(object jsonBody)
{
var serializedBody = JsonConvert.SerializeObject(jsonBody);
using var jsonReader = new JsonTextReader(new StringReader(serializedBody));
jsonReader.DateParseHandling = DateParseHandling.None;
var deserializedBody = JObject.Load(jsonReader);
return ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2);
}
public static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0)
{
return token switch
{
JArray jArray => FormatArray(jArray, ind),
JObject jObject => FormatObject(jObject, ind),
JProperty jProperty => $"{FormatPropertyName(jProperty.Name)} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}",
JValue jValue => jValue.Type switch
{
JTokenType.None => Null,
JTokenType.Integer => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value) : Null,
JTokenType.Float => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value) : Null,
JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()),
JTokenType.Boolean => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value).ToLower() : Null,
JTokenType.Null => Null,
JTokenType.Undefined => Null,
JTokenType.Date when jValue.Value is DateTime dateValue =>
$"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})",
_ => $"UNHANDLED_CASE: {jValue.Type}"
},
_ => $"UNHANDLED_CASE: {token}"
};
}
public static string ToCSharpBooleanLiteral(bool value) => value ? "true" : "false";
public static string ToCSharpStringLiteral(string? value)
{
if (string.IsNullOrEmpty(value))
{
return "\"\"";
}
if (value.Contains("\n"))
{
var escapedValue = value?.Replace("\"", "\"\"") ?? string.Empty;
return $"@\"{escapedValue}\"";
}
else
{
var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty;
return $"\"{escapedValue}\"";
}
}
public static string FormatPropertyName(string propertyName)
{
return CSharpReservedKeywords.Contains(propertyName) ? "@" + propertyName : propertyName;
}
private static string FormatObject(JObject jObject, int ind)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1));
return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
private static string FormatArray(JArray jArray, int ind)
{
var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray;
var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind));
if (hasComplexItems)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
return $"new [] {{ {string.Join(", ", items)} }}";
}
}

View File

@@ -11,7 +11,7 @@
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -12,7 +12,7 @@ builder
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -11,7 +11,7 @@
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -12,7 +12,7 @@ server
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -10,17 +10,17 @@ public class BytesEncodingUtilsTests
[Fact]
public void TryGetEncoding_UTF32()
{
var result = BytesEncodingUtils.TryGetEncoding(new byte[] { 0xff, 0xfe, 0x00, 0x00 }, out Encoding encoding);
var result = BytesEncodingUtils.TryGetEncoding(new byte[] { 0xff, 0xfe, 0x00, 0x00 }, out var encoding);
// Assert
result.Should().BeTrue();
encoding.CodePage.Should().Be(Encoding.UTF32.CodePage);
encoding?.CodePage.Should().Be(Encoding.UTF32.CodePage);
}
[Fact]
public void TryGetEncoding_Invalid()
{
var result = BytesEncodingUtils.TryGetEncoding(new byte[] { 0xff }, out Encoding encoding);
var result = BytesEncodingUtils.TryGetEncoding(new byte[] { 0xff }, out var encoding);
// Assert
result.Should().BeFalse();

View File

@@ -0,0 +1,105 @@
using FluentAssertions;
using Newtonsoft.Json.Linq;
using WireMock.Util;
using Xunit;
namespace WireMock.Net.Tests.Util;
public class CSharpFormatterTests
{
[Fact]
public void ConvertToAnonymousObjectDefinition_ShouldReturn_ValidValue_WhenJsonBodyIsValidJsonString()
{
// Arrange
var jsonBody = new { Key1 = "value1", Key2 = 42, F = 1.2 };
var expectedOutput = "new\r\n {\r\n Key1 = \"value1\",\r\n Key2 = 42,\r\n F = 1.2\r\n }";
// Act
var result = CSharpFormatter.ConvertToAnonymousObjectDefinition(jsonBody);
// Assert
result.Should().Be(expectedOutput);
}
[Fact]
public void ToCSharpStringLiteral_ShouldReturn_ValidValue_WhenStringIsNotNull()
{
// Arrange
var inputString = "test string";
var expectedOutput = "\"test string\"";
// Act
var result = CSharpFormatter.ToCSharpStringLiteral(inputString);
// Assert
result.Should().Be(expectedOutput);
}
[Fact]
public void ToCSharpStringLiteral_ShouldReturn_ValidValue_WhenStringContainsNewLineCharacters()
{
// Arrange
var inputString = "line1\nline2\nline3";
var expectedOutput = "@\"line1\nline2\nline3\"";
// Action
var result = CSharpFormatter.ToCSharpStringLiteral(inputString);
// Assert
result.Should().Be(expectedOutput);
}
[Fact]
public void FormatPropertyName_ShouldReturn_ValidPropertyName_WhenPropertyNameIsNotReserved()
{
// Arrange
var propertyName = "propertyname";
var expectedOutput = "propertyname";
// Action
var result = CSharpFormatter.FormatPropertyName(propertyName);
// Assert
result.Should().Be(expectedOutput);
}
[Fact]
public void FormatPropertyName_ShouldReturn_ValidPropertyName_WhenPropertyNameIsReserved()
{
// Arrange
var propertyName = "class";
var expectedOutput = "@class";
// Action
var result = CSharpFormatter.FormatPropertyName(propertyName);
// Assert
result.Should().Be(expectedOutput);
}
[Fact]
public void ConvertJsonToAnonymousObjectDefinition_ShouldReturn_ValidObject_WhenJsonInputIsValid()
{
// Arrange
var jObject = new JObject
(
new JProperty("Name", "John Smith"),
new JProperty("Age", 25.1f),
new JProperty("Gender", "Male"),
new JProperty("address", new JObject
(
new JProperty("Street", "123 Main St"),
new JProperty("City", "Anytown"),
new JProperty("State", "CA"),
new JProperty("Zip", "90001")
)
));
var expectedOutput = "new\r\n{\r\n Name = \"John Smith\",\r\n Age = 25.1,\r\n Gender = \"Male\",\r\n address = new\r\n {\r\n Street = \"123 Main St\",\r\n City = \"Anytown\",\r\n State = \"CA\",\r\n Zip = \"90001\"\r\n }\r\n}";
// Action
var result = CSharpFormatter.ConvertJsonToAnonymousObjectDefinition(jObject);
// Assert
result.Should().Be(expectedOutput);
}
}

View File

@@ -7,5 +7,6 @@ server
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("1")
);

View File

@@ -7,18 +7,84 @@ server
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("1")
);
server
.Given(Request.Create()
.UsingMethod("GET")
.UsingMethod("POST")
.WithPath("/foo2")
.WithParam("p2", "abc")
.WithHeader("h1", "W/\"234f2q3r\"", true)
)
.WithGuid("1b731398-4a5b-457f-a6e3-d65e541c428f")
.RespondWith(Response.Create()
.WithHeader("hk)", "hv")
.WithStatusCode("201")
.WithHeader("hk", "hv")
.WithHeader("ETag", "W/\"168d8e\"")
.WithBody("2")
);
server
.Given(Request.Create()
.UsingMethod("DELETE")
.WithUrl("https://localhost/test")
)
.WithGuid("f74fd144-df53-404f-8e35-da22a640bd5f")
.RespondWith(Response.Create()
.WithStatusCode(208)
.WithBodyAsJson(new
{
@as = 1,
b = 1.2,
d = true,
e = false,
f = new [] { 1, 2, 3, 4 },
g = new
{
z1 = 1,
z2 = 2,
z3 = new [] { "a", "b", "c" },
z4 = new []
{
new
{
a = 1,
b = 2
},
new
{
a = 2,
b = 3
}
}
},
date_field = "2023-05-08T11:20:19",
string_field_with_date = "2021-03-13T21:04:00Z",
multiline_text = @"This
is
multiline
text
"
})
);
server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/foo3")
.WithBody(new JsonPartialMatcher(
value: "{ a = 1, b = 2 }",
ignoreCase: false,
throwException: false,
regex: false
))
)
.WithGuid("4126dec8-470b-4eff-93bb-c24f83b8b1fd")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(@"Line1
Some ""value"" in Line2")
);

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@@ -17,6 +18,7 @@ using WireMock.Admin.Settings;
using WireMock.Client;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Net.Tests.VerifyExtensions;
using WireMock.RequestBuilders;
@@ -717,6 +719,8 @@ public class WireMockAdminApiTests
// Arrange
var guid1 = Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f05");
var guid2 = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f");
var guid3 = Guid.Parse("f74fd144-df53-404f-8e35-da22a640bd5f");
var guid4 = Guid.Parse("4126DEC8-470B-4EFF-93BB-C24F83B8B1FD");
var server = WireMockServer.StartWithAdminInterface();
server
@@ -738,21 +742,54 @@ public class WireMockAdminApiTests
Request.Create()
.WithPath("/foo2")
.WithParam("p2", "abc")
.UsingGet()
.WithHeader("h1", "W/\"234f2q3r\"")
.UsingPost()
)
.WithGuid(guid2)
.RespondWith(
Response.Create()
.WithStatusCode(201)
.WithStatusCode("201")
.WithHeader("hk", "hv")
.WithHeader("ETag", "W/\"168d8e\"")
.WithBody("2")
);
server
.Given(
Request.Create()
.WithUrl("https://localhost/test")
.UsingDelete()
)
.WithGuid(guid3)
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.AlreadyReported)
.WithBodyAsJson(new { @as = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, date_field = new DateTime(2023,05,08,11,20,19), string_field_with_date="2021-03-13T21:04:00Z", multiline_text= @"This
is
multiline
text
" })
);
server
.Given(
Request.Create()
.WithPath("/foo3")
.WithBody(new JsonPartialMatcher(new { a=1, b=2}))
.UsingPost()
)
.WithGuid(guid4)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithBody("Line1\r\nSome \"value\" in Line2")
);
// Act
var api = RestClient.For<IWireMockAdminApi>(server.Url);
var mappings = await api.GetMappingsAsync().ConfigureAwait(false);
mappings.Should().HaveCount(2);
mappings.Should().HaveCount(4);
var code = await api.GetMappingsCodeAsync().ConfigureAwait(false);

View File

@@ -34,6 +34,32 @@ public partial class WireMockServerTests
_testOutputHelper = testOutputHelper;
}
[Fact]
public void WireMockServer_Start()
{
// Act
var server = WireMockServer.Start();
// Assert
server.IsStarted.Should().BeTrue();
server.IsStartedWithAdminInterface.Should().BeFalse();
server.Stop();
}
[Fact]
public void WireMockServer_StartWithAdminInterface()
{
// Act
var server = WireMockServer.StartWithAdminInterface();
// Assert
server.IsStarted.Should().BeTrue();
server.IsStartedWithAdminInterface.Should().BeTrue();
server.Stop();
}
[Fact]
public async Task WireMockServer_Should_Reset_LogEntries()
{