File: Dashboard\GrpcTerminalStreamTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.DashboardService.Proto.V1;
using Aspire.Hosting.Dashboard;
using Aspire.Hosting.Tests.Utils.Grpc;
using Google.Protobuf;
 
namespace Aspire.Hosting.Tests.Dashboard;
 
public class GrpcTerminalStreamTests
{
    [Fact]
    public async Task WriteEndedAsync_WritesLifecycleFrameSeparatelyFromHmpBytes()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        await stream.WriteAsync("output"u8.ToArray());
        await stream.WriteEndedAsync(CancellationToken.None);
 
        var output = await responseStream.ReadNextAsync();
        Assert.Equal("output", output.Data.ToStringUtf8());
        Assert.False(output.Ended);
        var ended = await responseStream.ReadNextAsync();
        Assert.True(ended.Ended);
        Assert.True(ended.Data.IsEmpty);
    }
 
    [Fact]
    public async Task ReadAsync_SplitsSingleFrameAcrossReads()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        requestStream.AddMessage(new TerminalClientFrame { Data = ByteString.CopyFrom("hello"u8.ToArray()) });
 
        var buffer = new byte[2];
 
        Assert.Equal(2, await stream.ReadAsync(buffer));
        Assert.Equal("he"u8.ToArray(), buffer);
 
        Assert.Equal(2, await stream.ReadAsync(buffer));
        Assert.Equal("ll"u8.ToArray(), buffer);
 
        Assert.Equal(1, await stream.ReadAsync(buffer));
        Assert.Equal("o"u8.ToArray(), buffer[..1]);
    }
 
    [Fact]
    public async Task ReadAsync_SkipsEmptyFramesWithoutSignallingEndOfStream()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        requestStream.AddMessage(new TerminalClientFrame());
        requestStream.AddMessage(new TerminalClientFrame { Data = ByteString.CopyFrom("x"u8.ToArray()) });
 
        var buffer = new byte[8];
 
        Assert.Equal(1, await stream.ReadAsync(buffer));
        Assert.Equal((byte)'x', buffer[0]);
    }
 
    [Fact]
    public async Task ReadAsync_ReturnsZeroWhenRequestStreamCompletes()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        requestStream.Complete();
 
        Assert.Equal(0, await stream.ReadAsync(new byte[8]));
        // A second read must stay at end of stream rather than pulling on the completed reader again.
        Assert.Equal(0, await stream.ReadAsync(new byte[8]));
    }
 
    [Fact]
    public async Task WriteAsync_CopiesBufferSoCallerCanReuseIt()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        var buffer = "ok"u8.ToArray();
        await stream.WriteAsync(buffer);
        buffer[0] = (byte)'X';
 
        var frame = await responseStream.ReadNextAsync();
        Assert.Equal("ok", frame.Data.ToStringUtf8());
    }
 
    [Fact]
    public async Task WriteAsync_EmptyBufferDoesNotProduceFrame()
    {
        var context = TestServerCallContext.Create();
        var requestStream = new TestAsyncStreamReader<TerminalClientFrame>(context);
        var responseStream = new TestServerStreamWriter<TerminalServerFrame>(context);
        await using var stream = new GrpcTerminalStream(requestStream, responseStream);
 
        await stream.WriteAsync(ReadOnlyMemory<byte>.Empty);
        await stream.WriteAsync("data"u8.ToArray());
 
        var frame = await responseStream.ReadNextAsync();
        Assert.Equal("data", frame.Data.ToStringUtf8());
    }
}