File: ResponseBodyTests.cs
Web Access
Project: src\src\Hosting\TestHost\test\Microsoft.AspNetCore.TestHost.Tests.csproj (Microsoft.AspNetCore.TestHost.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Hosting;
 
namespace Microsoft.AspNetCore.TestHost.Tests;
 
public class ResponseBodyTests
{
    [Fact]
    public async Task BodyWriter_GetMemoryAdvance_AutoCompleted()
    {
        var length = -1;
        using var host = await CreateHost(httpContext =>
        {
            var writer = httpContext.Response.BodyWriter;
            length = writer.GetMemory().Length;
            writer.Advance(length);
            return Task.CompletedTask;
        });
 
        var response = await host.GetTestServer().CreateClient().GetAsync("/");
        var bytes = await response.Content.ReadAsByteArrayAsync();
        Assert.Equal(length, bytes.Length);
    }
 
    [Fact]
    public async Task BodyWriter_StartAsyncGetMemoryAdvance_AutoCompleted()
    {
        var length = -1;
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.StartAsync();
            var writer = httpContext.Response.BodyWriter;
            length = writer.GetMemory().Length;
            writer.Advance(length);
        });
 
        var response = await host.GetTestServer().CreateClient().GetAsync("/");
        var bytes = await response.Content.ReadAsByteArrayAsync();
        Assert.Equal(length, bytes.Length);
    }
 
    [Fact]
    public async Task BodyStream_SyncDisabled_WriteThrows()
    {
        var contentBytes = new byte[] { 32 };
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.StartAsync();
            httpContext.Response.Body.Write(contentBytes, 0, contentBytes.Length);
            await httpContext.Response.CompleteAsync();
        });
 
        var client = host.GetTestServer().CreateClient();
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => client.GetAsync("/"));
        Assert.Contains("Synchronous operations are disallowed.", ex.Message);
    }
 
    [Fact]
    public async Task BodyStream_SyncEnabled_WriteSucceeds()
    {
        var contentBytes = new byte[] { 32 };
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.StartAsync();
            httpContext.Response.Body.Write(contentBytes, 0, contentBytes.Length);
            await httpContext.Response.CompleteAsync();
        });
 
        host.GetTestServer().AllowSynchronousIO = true;
 
        var client = host.GetTestServer().CreateClient();
        var response = await client.GetAsync("/");
        var responseBytes = await response.Content.ReadAsByteArrayAsync();
        Assert.Equal(contentBytes, responseBytes);
    }
 
    [Fact]
    public async Task BodyStream_SyncDisabled_FlushThrows()
    {
        var contentBytes = new byte[] { 32 };
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.StartAsync();
            await httpContext.Response.Body.WriteAsync(contentBytes, 0, contentBytes.Length);
            httpContext.Response.Body.Flush();
            await httpContext.Response.CompleteAsync();
        });
 
        var client = host.GetTestServer().CreateClient();
        var requestException = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync("/"));
        var ex = (InvalidOperationException)requestException?.InnerException?.InnerException;
        Assert.NotNull(ex);
        Assert.Contains("Synchronous operations are disallowed.", ex.Message);
    }
 
    [Fact]
    public async Task BodyStream_SyncEnabled_FlushSucceeds()
    {
        var contentBytes = new byte[] { 32 };
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.StartAsync();
            await httpContext.Response.Body.WriteAsync(contentBytes, 0, contentBytes.Length);
            httpContext.Response.Body.Flush();
            await httpContext.Response.CompleteAsync();
        });
 
        host.GetTestServer().AllowSynchronousIO = true;
 
        var client = host.GetTestServer().CreateClient();
        var response = await client.GetAsync("/");
        var responseBytes = await response.Content.ReadAsByteArrayAsync();
        Assert.Equal(contentBytes, responseBytes);
    }
 
    [Fact]
    public async Task BodyStream_ZeroByteRead_Success()
    {
        var emptyReadStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var contentBytes = new byte[] { 32 };
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.Body.WriteAsync(contentBytes);
            await emptyReadStarted.Task;
            await httpContext.Response.Body.WriteAsync(contentBytes);
        });
 
        var client = host.GetTestServer().CreateClient();
        var response = await client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead);
        var stream = await response.Content.ReadAsStreamAsync();
        var bytes = new byte[5];
        var read = await stream.ReadAsync(bytes);
        Assert.Equal(1, read);
        Assert.Equal(contentBytes[0], bytes[0]);
 
        // This will chain to the Memory overload, but that does less restrictive validation on the input.
        // https://github.com/dotnet/aspnetcore/issues/41692#issuecomment-1248714684
        var zeroByteRead = stream.ReadAsync(Array.Empty<byte>(), 0, 0);
        Assert.False(zeroByteRead.IsCompleted);
        emptyReadStarted.SetResult();
        read = await zeroByteRead.DefaultTimeout();
        Assert.Equal(0, read);
 
        read = await stream.ReadAsync(bytes);
        Assert.Equal(1, read);
        Assert.Equal(contentBytes[0], bytes[0]);
    }
 
    private Task<IHost> CreateHost(RequestDelegate appDelegate)
    {
        return new HostBuilder()
            .ConfigureWebHost(webBuilder =>
            {
                webBuilder
                    .UseTestServer()
                    .Configure(app =>
                    {
                        app.Run(appDelegate);
                    });
            })
            .StartAsync();
    }
}