mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-23 08:48:28 +02:00
Fixed failing admin requests when content type includes a charset (based on idea from Paul Roub) (#353)
* . * #350 * fix * .
This commit is contained in:
73
src/WireMock.Net/Matchers/ContentTypeMatcher.cs
Normal file
73
src/WireMock.Net/Matchers/ContentTypeMatcher.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace WireMock.Matchers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ContentTypeMatcher which accepts also all charsets
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="RegexMatcher" />
|
||||||
|
public class ContentTypeMatcher : WildcardMatcher
|
||||||
|
{
|
||||||
|
private readonly string[] _patterns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pattern">The pattern.</param>
|
||||||
|
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||||
|
public ContentTypeMatcher([NotNull] string pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||||
|
/// <param name="pattern">The pattern.</param>
|
||||||
|
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||||
|
public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] string pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="patterns">The patterns.</param>
|
||||||
|
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||||
|
public ContentTypeMatcher([NotNull] string[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||||
|
/// <param name="patterns">The patterns.</param>
|
||||||
|
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||||
|
public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] string[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase)
|
||||||
|
{
|
||||||
|
_patterns = patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="RegexMatcher.IsMatch"/>
|
||||||
|
public override double IsMatch(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out MediaTypeHeaderValue contentType))
|
||||||
|
{
|
||||||
|
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.IsMatch(contentType.MediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
|
||||||
|
public override string[] GetPatterns()
|
||||||
|
{
|
||||||
|
return _patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMatcher.Name"/>
|
||||||
|
public override string Name => "ContentTypeMatcher";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,7 +71,7 @@ namespace WireMock.Matchers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
|
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
|
||||||
public double IsMatch(string input)
|
public virtual double IsMatch(string input)
|
||||||
{
|
{
|
||||||
double match = MatchScores.Mismatch;
|
double match = MatchScores.Mismatch;
|
||||||
if (input != null)
|
if (input != null)
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ namespace WireMock.Serialization
|
|||||||
case "WildcardMatcher":
|
case "WildcardMatcher":
|
||||||
return new WildcardMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
|
return new WildcardMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
|
||||||
|
|
||||||
|
case "ContentTypeMatcher":
|
||||||
|
return new ContentTypeMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
|
||||||
|
|
||||||
case "SimMetricsMatcher":
|
case "SimMetricsMatcher":
|
||||||
SimMetricType type = SimMetricType.Levenstein;
|
SimMetricType type = SimMetricType.Levenstein;
|
||||||
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
|
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ namespace WireMock.Server
|
|||||||
private const string AdminSettings = "/__admin/settings";
|
private const string AdminSettings = "/__admin/settings";
|
||||||
private const string AdminScenarios = "/__admin/scenarios";
|
private const string AdminScenarios = "/__admin/scenarios";
|
||||||
|
|
||||||
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true);
|
||||||
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
||||||
|
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
||||||
|
|
||||||
private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
|
private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
@@ -60,11 +61,11 @@ namespace WireMock.Server
|
|||||||
{
|
{
|
||||||
// __admin/settings
|
// __admin/settings
|
||||||
Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet));
|
Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet));
|
||||||
Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, ContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate));
|
Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate));
|
||||||
|
|
||||||
// __admin/mappings
|
// __admin/mappings
|
||||||
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
|
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
|
||||||
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, ContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
|
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
|
||||||
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
|
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
|
||||||
|
|
||||||
// __admin/mappings/reset
|
// __admin/mappings/reset
|
||||||
@@ -72,7 +73,7 @@ namespace WireMock.Server
|
|||||||
|
|
||||||
// __admin/mappings/{guid}
|
// __admin/mappings/{guid}
|
||||||
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet));
|
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet));
|
||||||
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, ContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
|
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
|
||||||
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
||||||
|
|
||||||
// __admin/mappings/save
|
// __admin/mappings/save
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using NFluent;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NFluent;
|
||||||
using WireMock.RequestBuilders;
|
using WireMock.RequestBuilders;
|
||||||
using WireMock.ResponseBuilders;
|
using WireMock.ResponseBuilders;
|
||||||
using WireMock.Server;
|
using WireMock.Server;
|
||||||
@@ -187,7 +188,7 @@ namespace WireMock.Net.Tests
|
|||||||
var server = FluentMockServer.Start();
|
var server = FluentMockServer.Start();
|
||||||
|
|
||||||
server
|
server
|
||||||
.Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null))
|
.Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null))
|
||||||
.AtPriority(0)
|
.AtPriority(0)
|
||||||
.RespondWith(Response.Create().WithStatusCode(400));
|
.RespondWith(Response.Create().WithStatusCode(400));
|
||||||
server
|
server
|
||||||
@@ -203,7 +204,7 @@ namespace WireMock.Net.Tests
|
|||||||
// Assert
|
// Assert
|
||||||
Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
|
Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("POST")]
|
[InlineData("POST")]
|
||||||
[InlineData("PUT")]
|
[InlineData("PUT")]
|
||||||
@@ -233,5 +234,38 @@ namespace WireMock.Net.Tests
|
|||||||
// Assert
|
// Assert
|
||||||
Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
|
Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("application/json; charset=ascii")]
|
||||||
|
[InlineData("application/json; charset=utf-8")]
|
||||||
|
[InlineData("application/json; charset=UTF-8")]
|
||||||
|
public async Task WireMockServer_Should_AcceptPostMappingsWithContentTypeJsonAndAnyCharset(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string message = @"{
|
||||||
|
""request"": {
|
||||||
|
""method"": ""GET"",
|
||||||
|
""url"": ""/some/thing""
|
||||||
|
},
|
||||||
|
""response"": {
|
||||||
|
""status"": 200,
|
||||||
|
""body"": ""Hello world!"",
|
||||||
|
""headers"": {
|
||||||
|
""Content-Type"": ""text/plain""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
var stringContent = new StringContent(message);
|
||||||
|
stringContent.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
|
||||||
|
var server = FluentMockServer.StartWithAdminInterface();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await new HttpClient().PostAsync($"{server.Urls[0]}/__admin/mappings", stringContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Check.That(response.StatusCode).Equals(HttpStatusCode.Created);
|
||||||
|
Check.That(await response.Content.ReadAsStringAsync()).Contains("Mapping added");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
58
test/WireMock.Net.Tests/Matchers/ContentTypeMatcherTests.cs
Normal file
58
test/WireMock.Net.Tests/Matchers/ContentTypeMatcherTests.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using NFluent;
|
||||||
|
using WireMock.Matchers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.Matchers
|
||||||
|
{
|
||||||
|
public class ContentTypeMatcherTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("application/json; charset=ascii")]
|
||||||
|
[InlineData("application/json; charset=utf-8")]
|
||||||
|
[InlineData("application/json; charset=UTF-8")]
|
||||||
|
public void ContentTypeMatcher_IsMatchWithIgnoreCaseFalse_Positive(string contentType)
|
||||||
|
{
|
||||||
|
var matcher = new ContentTypeMatcher("application/json");
|
||||||
|
Check.That(matcher.IsMatch(contentType)).IsEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("application/JSON")]
|
||||||
|
[InlineData("application/json; CharSet=ascii")]
|
||||||
|
[InlineData("application/json; charset=utf-8")]
|
||||||
|
[InlineData("application/json; charset=UTF-8")]
|
||||||
|
public void ContentTypeMatcher_IsMatchWithIgnoreCaseTrue_Positive(string contentType)
|
||||||
|
{
|
||||||
|
var matcher = new ContentTypeMatcher("application/json", true);
|
||||||
|
Check.That(matcher.IsMatch(contentType)).IsEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContentTypeMatcher_GetName()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var matcher = new ContentTypeMatcher("x");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string name = matcher.Name;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Check.That(name).Equals("ContentTypeMatcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContentTypeMatcher_GetPatterns()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var matcher = new ContentTypeMatcher("x");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string[] patterns = matcher.GetPatterns();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Check.That(patterns).ContainsExactly("x");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user