File: Model\DashboardClientTests.cs
Web Access
Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.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.Dashboard.Configuration;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Utils;
using Google.Protobuf.WellKnownTypes;
using Aspire.ResourceService.Proto.V1;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
using Microsoft.AspNetCore.InternalTesting;
 
namespace Aspire.Dashboard.Tests.Model;
 
public sealed class DashboardClientTests
{
    private static readonly BrowserTimeProvider s_timeProvider = new(NullLoggerFactory.Instance);
 
    private readonly IConfiguration _configuration;
    private readonly IOptions<DashboardOptions> _dashboardOptions;
 
    public DashboardClientTests()
    {
        _configuration = new ConfigurationManager();
 
        var options = new DashboardOptions
        {
            ResourceServiceClient =
            {
                AuthMode = ResourceClientAuthMode.Unsecured,
                Url = "http://localhost:12345"
            }
        };
        options.ResourceServiceClient.TryParseOptions(out _);
 
        _dashboardOptions = Options.Create(options);
    }
 
    [Fact]
    public async Task SubscribeResources_OnCancel_ChannelRemoved()
    {
        await using var instance = CreateResourceServiceClient();
        instance.SetInitialDataReceived();
 
        IDashboardClient client = instance;
 
        var cts = new CancellationTokenSource();
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
 
        var (_, subscription) = await client.SubscribeResourcesAsync(CancellationToken.None).DefaultTimeout();
 
        Assert.Equal(1, instance.OutgoingResourceSubscriberCount);
 
        var readTask = Task.Run(async () =>
        {
            await foreach (var item in subscription.WithCancellation(cts.Token))
            {
            }
        });
 
        cts.Cancel();
 
        await TaskHelpers.WaitIgnoreCancelAsync(readTask).DefaultTimeout();
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
    }
 
    [Fact]
    public async Task SubscribeResources_OnDispose_ChannelRemoved()
    {
        await using var instance = CreateResourceServiceClient();
        instance.SetInitialDataReceived();
 
        IDashboardClient client = instance;
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
 
        var (_, subscription) = await client.SubscribeResourcesAsync(CancellationToken.None).DefaultTimeout();
 
        Assert.Equal(1, instance.OutgoingResourceSubscriberCount);
 
        var readTask = Task.Run(async () =>
        {
            await foreach (var item in subscription)
            {
            }
        });
 
        await instance.DisposeAsync().DefaultTimeout();
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
 
        await TaskHelpers.WaitIgnoreCancelAsync(readTask).DefaultTimeout();
    }
 
    [Fact]
    public async Task SubscribeResources_ThrowsIfDisposed()
    {
        await using IDashboardClient client = CreateResourceServiceClient();
 
        await client.DisposeAsync().DefaultTimeout();
 
        await Assert.ThrowsAsync<ObjectDisposedException>(() => client.SubscribeResourcesAsync(CancellationToken.None)).DefaultTimeout();
    }
 
    [Fact]
    public async Task SubscribeResources_IncreasesSubscriberCount()
    {
        await using var instance = CreateResourceServiceClient();
        instance.SetInitialDataReceived();
 
        IDashboardClient client = instance;
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
 
        _ = await client.SubscribeResourcesAsync(CancellationToken.None).DefaultTimeout();
 
        Assert.Equal(1, instance.OutgoingResourceSubscriberCount);
 
        await instance.DisposeAsync().DefaultTimeout();
 
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
    }
 
    [Fact]
    public async Task SubscribeResources_HasInitialData_InitialDataReturned()
    {
        await using var instance = CreateResourceServiceClient();
 
        IDashboardClient client = instance;
 
        var cts = new CancellationTokenSource();
 
        var subscribeTask = client.SubscribeResourcesAsync(CancellationToken.None);
 
        Assert.False(subscribeTask.IsCompleted);
        Assert.Equal(0, instance.OutgoingResourceSubscriberCount);
 
        instance.SetInitialDataReceived([new Resource
        {
            Name = "test",
            CreatedAt = Timestamp.FromDateTime(DateTime.UtcNow),
        }]);
 
        var (initialData, subscription) = await subscribeTask.DefaultTimeout();
 
        Assert.Single(initialData);
    }
 
    private DashboardClient CreateResourceServiceClient()
    {
        return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, new TestDashboardClientStatus(), s_timeProvider, new MockKnownPropertyLookup());
    }
 
    private sealed class TestDashboardClientStatus : IDashboardClientStatus
    {
        public bool IsEnabled => true;
    }
}