diff --git a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs
new file mode 100644
index 000000000..3f0f1cfcb
--- /dev/null
+++ b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs
@@ -0,0 +1,73 @@
+using System.Net.Http.Headers;
+using JetBrains.Annotations;
+
+namespace WireMock.Matchers
+{
+ ///
+ /// ContentTypeMatcher which accepts also all charsets
+ ///
+ ///
+ public class ContentTypeMatcher : WildcardMatcher
+ {
+ private readonly string[] _patterns;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pattern.
+ /// IgnoreCase (default false)
+ public ContentTypeMatcher([NotNull] string pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// The pattern.
+ /// IgnoreCase (default false)
+ public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] string pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The patterns.
+ /// IgnoreCase (default false)
+ public ContentTypeMatcher([NotNull] string[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// The patterns.
+ /// IgnoreCase (default false)
+ public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] string[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase)
+ {
+ _patterns = patterns;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public override string[] GetPatterns()
+ {
+ return _patterns;
+ }
+
+ ///
+ public override string Name => "ContentTypeMatcher";
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/RegexMatcher.cs b/src/WireMock.Net/Matchers/RegexMatcher.cs
index 4947cdc4b..9101a112c 100644
--- a/src/WireMock.Net/Matchers/RegexMatcher.cs
+++ b/src/WireMock.Net/Matchers/RegexMatcher.cs
@@ -71,7 +71,7 @@ public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] strin
}
///
- public double IsMatch(string input)
+ public virtual double IsMatch(string input)
{
double match = MatchScores.Mismatch;
if (input != null)
diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs
index 4b63aff08..2cab19b61 100644
--- a/src/WireMock.Net/Serialization/MatcherMapper.cs
+++ b/src/WireMock.Net/Serialization/MatcherMapper.cs
@@ -76,6 +76,9 @@ public IMatcher Map([CanBeNull] MatcherModel matcher)
case "WildcardMatcher":
return new WildcardMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
+ case "ContentTypeMatcher":
+ return new ContentTypeMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
+
case "SimMetricsMatcher":
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
index 9cc2a399f..8ee0a65b3 100644
--- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs
+++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
@@ -40,8 +40,9 @@ public partial class FluentMockServer
private const string AdminSettings = "/__admin/settings";
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 _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 _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true);
+ 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
{
@@ -60,11 +61,11 @@ private void InitAdmin()
{
// __admin/settings
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
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));
// __admin/mappings/reset
@@ -72,7 +73,7 @@ private void InitAdmin()
// __admin/mappings/{guid}
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));
// __admin/mappings/save
diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs
index 22ddc1d79..8d7ecf2e4 100644
--- a/test/WireMock.Net.Tests/FluentMockServerTests.cs
+++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs
@@ -1,9 +1,10 @@
-using NFluent;
-using System;
+using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading.Tasks;
+using NFluent;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
@@ -187,7 +188,7 @@ public async Task FluentMockServer_Should_exclude_body_for_methods_where_body_is
var server = FluentMockServer.Start();
server
- .Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null))
+ .Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null))
.AtPriority(0)
.RespondWith(Response.Create().WithStatusCode(400));
server
@@ -203,7 +204,7 @@ public async Task FluentMockServer_Should_exclude_body_for_methods_where_body_is
// Assert
Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
}
-
+
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
@@ -233,5 +234,38 @@ public async Task FluentMockServer_Should_not_exclude_body_for_supported_methods
// Assert
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");
+ }
}
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Matchers/ContentTypeMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/ContentTypeMatcherTests.cs
new file mode 100644
index 000000000..890a05e79
--- /dev/null
+++ b/test/WireMock.Net.Tests/Matchers/ContentTypeMatcherTests.cs
@@ -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");
+ }
+ }
+}
\ No newline at end of file