diff --git a/src/WireMock.Net.Abstractions/Models/IBodyDataExtension.cs b/src/WireMock.Net.Abstractions/Models/IBodyDataExtension.cs new file mode 100644 index 00000000..7f90d056 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Models/IBodyDataExtension.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs index 23619bef..a43a6528 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Stef.Validation; using WireMock.Constants; using WireMock.Types; +using WireMock.Util; namespace WireMock.Http; @@ -33,12 +34,14 @@ internal static class HttpRequestMessageHelper 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 }; diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index 694ab2a1..417b1047 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -19,6 +19,7 @@ using WireMock.Util; #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; @@ -136,30 +137,37 @@ namespace WireMock.Owin.Mappers return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage; } - private async Task GetNormalBodyAsync(IResponseMessage responseMessage) - { - switch (responseMessage.BodyData?.DetectedBodyType) + private async Task 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; diff --git a/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs b/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs index b356e40f..edd22b2b 100644 --- a/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs +++ b/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs @@ -49,6 +49,26 @@ public class HttpRequestMessageHelperTests 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() { @@ -64,7 +84,7 @@ public class HttpRequestMessageHelperTests 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] diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithProxyIntegrationTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithProxyIntegrationTests.cs new file mode 100644 index 00000000..eebac805 --- /dev/null +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithProxyIntegrationTests.cs @@ -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().Features.Get()!.Addresses + .Select(x => new Uri(x).Port) + .First(); + + public async ValueTask Run() { + var started = new TaskCompletionSource(); + var host = app.Services.GetRequiredService(); + host.ApplicationStarted.Register(() => started.SetResult()); + _ = Task.Run(() => app.RunAsync()); + await started.Task; + disposable = new(() => host.StopApplication()); + return this; + } + + public void Dispose() { + disposable.Dispose(); + } + } +} + +#endif \ No newline at end of file