mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-20 15:31:20 +02:00
Attempt to fix JSON parsing of text/plain content type (#1172)
* UseContentType * Fix unit tests * Add a unit test and an integration test for the fix. * Simplify body type checking with GetBodyType extension. * Split IBodyDataExtension, and use imperative style instead of functional style * Remove excessive null forgiving operators * Adjust braces --------- Co-authored-by: Ruxo Zheng <rz@just.sent.as>
This commit is contained in:
20
src/WireMock.Net.Abstractions/Models/IBodyDataExtension.cs
Normal file
20
src/WireMock.Net.Abstractions/Models/IBodyDataExtension.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using WireMock.Types;
|
||||||
|
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
namespace WireMock.Util;
|
||||||
|
|
||||||
|
public static class IBodyDataExtension
|
||||||
|
{
|
||||||
|
public static BodyType GetBodyType(this IBodyData bodyData)
|
||||||
|
{
|
||||||
|
if (bodyData.DetectedBodyTypeFromContentType is not null and not BodyType.None)
|
||||||
|
{
|
||||||
|
return bodyData.DetectedBodyTypeFromContentType.Value;
|
||||||
|
}
|
||||||
|
if (bodyData.DetectedBodyType is not null and not BodyType.None)
|
||||||
|
{
|
||||||
|
return bodyData.DetectedBodyType.Value;
|
||||||
|
}
|
||||||
|
return BodyType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ using Newtonsoft.Json;
|
|||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Constants;
|
using WireMock.Constants;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
namespace WireMock.Http;
|
namespace WireMock.Http;
|
||||||
|
|
||||||
@@ -33,12 +34,14 @@ internal static class HttpRequestMessageHelper
|
|||||||
MediaTypeHeaderValue.TryParse(value, out contentType);
|
MediaTypeHeaderValue.TryParse(value, out contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRequestMessage.Content = requestMessage.BodyData?.DetectedBodyType switch
|
var bodyData = requestMessage.BodyData;
|
||||||
|
httpRequestMessage.Content = bodyData?.GetBodyType() switch
|
||||||
{
|
{
|
||||||
BodyType.Bytes => ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType),
|
BodyType.Bytes => ByteArrayContentHelper.Create(bodyData!.BodyAsBytes!, contentType),
|
||||||
BodyType.Json => StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType),
|
BodyType.Json => StringContentHelper.Create(JsonConvert.SerializeObject(bodyData!.BodyAsJson), contentType),
|
||||||
BodyType.String => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType),
|
BodyType.String => StringContentHelper.Create(bodyData!.BodyAsString!, contentType),
|
||||||
BodyType.FormUrlEncoded => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType),
|
BodyType.FormUrlEncoded => StringContentHelper.Create(bodyData!.BodyAsString!, contentType),
|
||||||
|
|
||||||
_ => httpRequestMessage.Content
|
_ => httpRequestMessage.Content
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using WireMock.Util;
|
|||||||
|
|
||||||
#if !USE_ASPNETCORE
|
#if !USE_ASPNETCORE
|
||||||
using IResponse = Microsoft.Owin.IOwinResponse;
|
using IResponse = Microsoft.Owin.IOwinResponse;
|
||||||
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
#else
|
#else
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
|
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
|
||||||
@@ -136,30 +137,37 @@ namespace WireMock.Owin.Mappers
|
|||||||
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
|
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
|
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage) {
|
||||||
{
|
var bodyData = responseMessage.BodyData;
|
||||||
switch (responseMessage.BodyData?.DetectedBodyType)
|
switch (bodyData?.GetBodyType())
|
||||||
{
|
{
|
||||||
case BodyType.String:
|
case BodyType.String:
|
||||||
case BodyType.FormUrlEncoded:
|
case BodyType.FormUrlEncoded:
|
||||||
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
|
return (bodyData!.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
|
||||||
|
|
||||||
case BodyType.Json:
|
case BodyType.Json:
|
||||||
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
|
var formatting = bodyData!.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
|
||||||
var jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
|
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
|
||||||
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
||||||
|
|
||||||
#if PROTOBUF
|
#if PROTOBUF
|
||||||
case BodyType.ProtoBuf:
|
case BodyType.ProtoBuf:
|
||||||
var protoDefinition = responseMessage.BodyData.ProtoDefinition?.Invoke().Text;
|
var protoDefinition = bodyData!.ProtoDefinition?.Invoke().Text;
|
||||||
return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinition, responseMessage.BodyData.ProtoBufMessageType, responseMessage.BodyData.BodyAsJson).ConfigureAwait(false);
|
return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinition, bodyData!.ProtoBufMessageType, bodyData!.BodyAsJson).ConfigureAwait(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case BodyType.Bytes:
|
case BodyType.Bytes:
|
||||||
return responseMessage.BodyData.BodyAsBytes;
|
return bodyData!.BodyAsBytes;
|
||||||
|
|
||||||
case BodyType.File:
|
case BodyType.File:
|
||||||
return _options.FileSystemHandler?.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile!);
|
return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData!.BodyAsFile!);
|
||||||
|
|
||||||
|
case BodyType.MultiPart:
|
||||||
|
_options.Logger.Warn("MultiPart body type is not handled!");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BodyType.None:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -49,6 +49,26 @@ public class HttpRequestMessageHelperTests
|
|||||||
Check.That(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ContainsExactly(Encoding.UTF8.GetBytes("hi"));
|
Check.That(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ContainsExactly(Encoding.UTF8.GetBytes("hi"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HttpRequestMessageHelper_Create_TextPlain()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var body = new BodyData
|
||||||
|
{
|
||||||
|
BodyAsString = "0123", // or 83 in decimal
|
||||||
|
BodyAsJson = 83,
|
||||||
|
DetectedBodyType = BodyType.Json,
|
||||||
|
DetectedBodyTypeFromContentType = BodyType.String
|
||||||
|
};
|
||||||
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp, body);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Check.That(await message.Content!.ReadAsStringAsync().ConfigureAwait(false)).Equals("0123");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HttpRequestMessageHelper_Create_Json()
|
public async Task HttpRequestMessageHelper_Create_Json()
|
||||||
{
|
{
|
||||||
@@ -64,7 +84,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Check.That(await message.Content.ReadAsStringAsync().ConfigureAwait(false)).Equals("{\"x\":42}");
|
Check.That(await message.Content!.ReadAsStringAsync().ConfigureAwait(false)).Equals("{\"x\":42}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using WireMock.Net.Xunit;
|
||||||
|
using WireMock.RequestBuilders;
|
||||||
|
using WireMock.ResponseBuilders;
|
||||||
|
using WireMock.Server;
|
||||||
|
using WireMock.Settings;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.ResponseBuilders;
|
||||||
|
|
||||||
|
public sealed class ResponseWithProxyIntegrationTests(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Response_UsingTextPlain()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
using var server = await TestServer.New().Run();
|
||||||
|
var port = server.GetPort();
|
||||||
|
output.WriteLine($"Server running on port {port}");
|
||||||
|
|
||||||
|
var settings = new WireMockServerSettings {
|
||||||
|
Port = 0,
|
||||||
|
Logger = new TestOutputHelperWireMockLogger(output)
|
||||||
|
};
|
||||||
|
using var mockServer = WireMockServer.Start(settings);
|
||||||
|
mockServer.Given(Request.Create().WithPath("/zipcode").UsingPatch())
|
||||||
|
.RespondWith(Response.Create().WithProxy($"http://localhost:{port}"));
|
||||||
|
|
||||||
|
using var client = new HttpClient { BaseAddress = new Uri(mockServer.Urls[0]) };
|
||||||
|
using var content = new ByteArrayContent(Encoding.UTF8.GetBytes("0123"));
|
||||||
|
content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
|
||||||
|
|
||||||
|
// When
|
||||||
|
var response = await client.PatchAsync("/zipcode", content);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
response.Content.Headers.GetValues("Content-Type").Should().BeEquivalentTo("text/plain; charset=utf-8");
|
||||||
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
|
result.Should().Be("0123");
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Disposable(Action dispose) : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose() => dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TestServer(WebApplication app) : IDisposable
|
||||||
|
{
|
||||||
|
Disposable disposable = new(() => { });
|
||||||
|
|
||||||
|
public static TestServer New() {
|
||||||
|
var builder = WebApplication.CreateBuilder();
|
||||||
|
builder.WebHost.ConfigureKestrel(opts => opts.ListenAnyIP(0));
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapPatch("/zipcode", async (HttpRequest req) => {
|
||||||
|
var memory = new MemoryStream();
|
||||||
|
await req.Body.CopyToAsync(memory);
|
||||||
|
var content = Encoding.UTF8.GetString(memory.ToArray());
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
return new(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetPort()
|
||||||
|
=> app.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()!.Addresses
|
||||||
|
.Select(x => new Uri(x).Port)
|
||||||
|
.First();
|
||||||
|
|
||||||
|
public async ValueTask<TestServer> Run() {
|
||||||
|
var started = new TaskCompletionSource();
|
||||||
|
var host = app.Services.GetRequiredService<IHostApplicationLifetime>();
|
||||||
|
host.ApplicationStarted.Register(() => started.SetResult());
|
||||||
|
_ = Task.Run(() => app.RunAsync());
|
||||||
|
await started.Task;
|
||||||
|
disposable = new(() => host.StopApplication());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user