Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix JSON parsing of text/plain content type #1172

Merged
merged 7 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/WireMock.Net.Abstractions/Models/IBodyData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,15 @@ public interface IBodyData
/// </summary>
public string? ProtoBufMessageType { get; set; }
#endregion
}
}

public static class IBodyDataExtension
StefH marked this conversation as resolved.
Show resolved Hide resolved
{
public static BodyType GetBodyType(this IBodyData bodyData)
=> bodyData.DetectedBodyTypeFromContentType?.ToNullable()
?? bodyData.DetectedBodyType?.ToNullable()
?? BodyType.None;

static BodyType? ToNullable(this BodyType v)
=> v == BodyType.None ? null : v;
}
13 changes: 8 additions & 5 deletions src/WireMock.Net/Http/HttpRequestMessageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Stef.Validation;
using WireMock.Constants;
using WireMock.Types;
using WireMock.Util;

namespace WireMock.Http;

Expand All @@ -33,12 +34,14 @@ internal static HttpRequestMessage Create(IRequestMessage requestMessage, string
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.Json => StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType),
BodyType.String => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType),
BodyType.FormUrlEncoded => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType),
BodyType.Bytes => ByteArrayContentHelper.Create(bodyData!.BodyAsBytes!, contentType),
BodyType.Json => StringContentHelper.Create(JsonConvert.SerializeObject(bodyData!.BodyAsJson), contentType),
BodyType.String => StringContentHelper.Create(bodyData!.BodyAsString!, contentType),
BodyType.FormUrlEncoded => StringContentHelper.Create(bodyData!.BodyAsString!, contentType),

_ => httpRequestMessage.Content
};

Expand Down
30 changes: 19 additions & 11 deletions src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#if !USE_ASPNETCORE
using IResponse = Microsoft.Owin.IOwinResponse;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
#else
using Microsoft.AspNetCore.Http;
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
Expand Down Expand Up @@ -136,30 +137,37 @@ private bool IsFault(IResponseMessage responseMessage)
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}

private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
switch (responseMessage.BodyData?.DetectedBodyType)
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage) {
var bodyData = responseMessage.BodyData;
switch (bodyData?.GetBodyType())
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
return (bodyData!.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);

case BodyType.Json:
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
var jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
var formatting = bodyData!.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);

#if PROTOBUF
case BodyType.ProtoBuf:
var protoDefinition = responseMessage.BodyData.ProtoDefinition?.Invoke().Text;
return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinition, responseMessage.BodyData.ProtoBufMessageType, responseMessage.BodyData.BodyAsJson).ConfigureAwait(false);
var protoDefinition = bodyData!.ProtoDefinition?.Invoke().Text;
return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinition, bodyData!.ProtoBufMessageType, bodyData!.BodyAsJson).ConfigureAwait(false);
#endif

case BodyType.Bytes:
return responseMessage.BodyData.BodyAsBytes;
return bodyData!.BodyAsBytes;

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;
Expand Down
22 changes: 21 additions & 1 deletion test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ public async Task HttpRequestMessageHelper_Create_Bytes()
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]
public async Task HttpRequestMessageHelper_Create_Json()
{
Expand All @@ -64,7 +84,7 @@ public async Task HttpRequestMessageHelper_Create_Json()
var message = HttpRequestMessageHelper.Create(request, "http://url");

// Assert
Check.That(await message.Content.ReadAsStringAsync().ConfigureAwait(false)).Equals("{\"x\":42}");
Check.That(await message.Content!.ReadAsStringAsync().ConfigureAwait(false)).Equals("{\"x\":42}");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -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