| 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.Utils; using Aspire.DashboardService.Proto.V1; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Xunit; namespace Aspire.Dashboard.Tests.Model; public sealed class DashboardClientTests { 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); } [Fact] public async Task SubscribeInteractions_OnCancel_ChannelRemoved() { await using var instance = CreateResourceServiceClient(); IDashboardClient client = instance; var cts = new CancellationTokenSource(); Assert.Equal(0, instance.OutgoingInteractionSubscriberCount); var subscription = client.SubscribeInteractionsAsync(CancellationToken.None); Assert.Equal(1, instance.OutgoingInteractionSubscriberCount); 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.OutgoingInteractionSubscriberCount); } [Fact] public async Task SubscribeInteractions_OnDispose_ChannelRemoved() { await using var instance = CreateResourceServiceClient(); IDashboardClient client = instance; Assert.Equal(0, instance.OutgoingInteractionSubscriberCount); var subscription = client.SubscribeInteractionsAsync(CancellationToken.None); Assert.Equal(1, instance.OutgoingInteractionSubscriberCount); var readTask = Task.Run(async () => { await foreach (var item in subscription) { } }); await instance.DisposeAsync().DefaultTimeout(); Assert.Equal(0, instance.OutgoingInteractionSubscriberCount); await TaskHelpers.WaitIgnoreCancelAsync(readTask).DefaultTimeout(); } [Fact] public async Task SubscribeInteractions_ThrowsIfDisposed() { await using IDashboardClient client = CreateResourceServiceClient(); await client.DisposeAsync().DefaultTimeout(); Assert.Throws<ObjectDisposedException>(() => client.SubscribeInteractionsAsync(CancellationToken.None)); } [Fact] public async Task SubscribeInteractions_IncreasesSubscriberCount() { await using var instance = CreateResourceServiceClient(); IDashboardClient client = instance; Assert.Equal(0, instance.OutgoingInteractionSubscriberCount); _ = client.SubscribeInteractionsAsync(CancellationToken.None); Assert.Equal(1, instance.OutgoingInteractionSubscriberCount); await instance.DisposeAsync().DefaultTimeout(); Assert.Equal(0, instance.OutgoingInteractionSubscriberCount); } [Fact] public async Task WhenConnected_InteractionMethodUnimplemented_InteractionWatchCompleted() { await using var instance = CreateResourceServiceClient(); instance.SetDashboardServiceClient(new MockDashboardServiceClient()); await instance.WhenConnected.DefaultTimeout(); await instance.InteractionWatchCompleteTask.DefaultTimeout(); } private sealed class MockDashboardServiceClient : Aspire.DashboardService.Proto.V1.DashboardService.DashboardServiceClient { public override AsyncDuplexStreamingCall<WatchInteractionsRequestUpdate, WatchInteractionsResponseUpdate> WatchInteractions(CallOptions options) { return new AsyncDuplexStreamingCall<WatchInteractionsRequestUpdate, WatchInteractionsResponseUpdate>( new ClientStreamWriter<WatchInteractionsRequestUpdate>(), new AsyncStreamReader<WatchInteractionsResponseUpdate>(), Task.FromResult(new Metadata()), () => new Status(StatusCode.Unimplemented, "Unimplemented!"), () => new Metadata(), () => { }); } public override AsyncUnaryCall<ApplicationInformationResponse> GetApplicationInformationAsync(ApplicationInformationRequest request, CallOptions options) { return new AsyncUnaryCall<ApplicationInformationResponse>( Task.FromResult(new ApplicationInformationResponse { ApplicationName = "TestApplication" }), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); } public override AsyncServerStreamingCall<WatchResourcesUpdate> WatchResources(WatchResourcesRequest request, CallOptions options) { return new AsyncServerStreamingCall<WatchResourcesUpdate>( new AsyncStreamReader<WatchResourcesUpdate>(), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); } } private sealed class AsyncStreamReader<T> : IAsyncStreamReader<T> { public T Current { get; } = default!; public Task<bool> MoveNext(CancellationToken cancellationToken) { return Task.FromResult(false); } } private sealed class ClientStreamWriter<T> : IClientStreamWriter<T> { public WriteOptions? WriteOptions { get; set; } public Task CompleteAsync() { throw new NotImplementedException(); } public Task WriteAsync(T message) { throw new NotImplementedException(); } } private DashboardClient CreateResourceServiceClient() { return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, new MockKnownPropertyLookup()); } }