File: JsonRpcCallbackInvokerTests.cs
Web Access
Project: src\tests\Aspire.Hosting.RemoteHost.Tests\Aspire.Hosting.RemoteHost.Tests.csproj (Aspire.Hosting.RemoteHost.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.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Hosting.RemoteHost.Language;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using Xunit;
 
namespace Aspire.Hosting.RemoteHost.Tests;
 
public class JsonRpcCallbackInvokerTests
{
    [Fact]
    public async Task ConfiguredCallbackDeadlineUsesTheInjectedClock()
    {
        var clock = new FakeTimeProvider();
        var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var response = new TaskCompletionSource<JsonNode?>(TaskCreationOptions.RunContinuationsAsynchronously);
        using var connection = new IntegrationHostTestConnection(
            JsonSerializer.SerializeToElement(Array.Empty<object>()),
            (_, _, _) =>
            {
                started.TrySetResult();
                return response.Task;
            });
        var configuration = new IntegrationHostConfiguration(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["ASPIRE_INTEGRATION_HOSTS_ENABLED"] = "true",
            ["IntegrationHost:CallbackTimeout"] = "00:03:00"
        }).Build());
        await using var invoker = new JsonRpcCallbackInvoker(NullLogger<JsonRpcCallbackInvoker>.Instance, configuration, clock);
        invoker.SetConnection(connection.ServerRpc);
        var invocation = invoker.InvokeAsync<JsonNode?>("stall", null, TestContext.Current.CancellationToken);
        try
        {
            await started.Task.WaitAsync(TestContext.Current.CancellationToken);
            clock.Advance(TimeSpan.FromSeconds(179));
            Assert.False(invocation.IsCompleted);
            clock.Advance(TimeSpan.FromSeconds(1));
            var error = await Assert.ThrowsAsync<TimeoutException>(() => invocation);
            Assert.Equal("Callback 'stall' timed out after 180s; its owner connection was retired.", error.Message);
            Assert.True(invoker.LifetimeToken.IsCancellationRequested);
            await connection.ServerRpc.Completion.WaitAsync(TestContext.Current.CancellationToken);
            Assert.False(invoker.IsConnected);
            await Assert.ThrowsAnyAsync<OperationCanceledException>(() => invoker.InvokeAsync<JsonNode>(
                "responsive", null, TestContext.Current.CancellationToken, TimeSpan.FromSeconds(10)));
        }
        finally
        {
            response.TrySetResult(null);
        }
    }
 
    [Fact]
    public async Task OrdinaryCallbackTimeoutLeavesConnectionUsable()
    {
        var response = new TaskCompletionSource<JsonNode?>(TaskCreationOptions.RunContinuationsAsynchronously);
        using var connection = new IntegrationHostTestConnection(
            JsonSerializer.SerializeToElement(Array.Empty<object>()),
            async (callbackId, _, cancellationToken) =>
            {
                if (callbackId == "stall")
                {
                    return await response.Task.WaitAsync(cancellationToken);
                }
 
                return JsonValue.Create("responsive");
            });
        await using var invoker = new JsonRpcCallbackInvoker(
            NullLogger<JsonRpcCallbackInvoker>.Instance, IntegrationHostConfiguration.Default, new FakeTimeProvider());
        invoker.SetConnection(connection.ServerRpc);
 
        await Assert.ThrowsAsync<TimeoutException>(() => invoker.InvokeAsync<JsonNode>(
            "stall", null, TestContext.Current.CancellationToken, TimeSpan.Zero));
 
        Assert.True(invoker.IsConnected);
        var result = await invoker.InvokeAsync<JsonNode>(
            "responsive", null, TestContext.Current.CancellationToken, TimeSpan.FromSeconds(10));
        Assert.Equal("responsive", result.GetValue<string>());
    }
}