| File: Telemetry\DashboardTelemetryServiceTests.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 System.Net; using System.Net.Http.Json; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using Aspire.Dashboard.Configuration; using Aspire.Dashboard.Telemetry; using Aspire.Tests.Shared.Telemetry; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using Xunit; namespace Aspire.Dashboard.Tests.Telemetry; public class DashboardTelemetryServiceTests(ITestOutputHelper output) { [Fact] public async Task CreateTelemetryService_WithDebugSession_Optout_ShowsTelemetryUnsupported() { var options = new TestDashboardOptions(new DashboardOptions { DebugSession = new DebugSessionOptions { Port = 5000, Token = "test", TelemetryOptOut = true } }); Assert.True(options.Value.DebugSession.TryParseOptions(out _)); var telemetrySender = new DashboardTelemetrySender(options, NullLogger<DashboardTelemetrySender>.Instance); var telemetryService = await CreateTelemetryServiceAsync(telemetrySender); await telemetryService.InitializeAsync(); Assert.False(telemetryService.IsTelemetryEnabled); var callbackExecuted = false; telemetrySender.QueueRequest(OperationContext.Empty, (_, _) => { callbackExecuted = true; return Task.FromResult(0); }); await telemetrySender.DisposeAsync(); Assert.False(callbackExecuted, "Telemetry request should not be sent when telemetry is disabled."); } [Theory] [InlineData(true, 4)] [InlineData(false, 0)] public async Task CreateTelemetryService_WithoutValidDebugSession_ShowsTelemetryUnsupported(bool enabled, int expectCount) { var sender = new TestDashboardTelemetrySender { IsTelemetryEnabled = enabled }; var telemetryService = await CreateTelemetryServiceAsync(sender); var context = telemetryService.PostUserTask("testTask", TelemetryResult.Success); telemetryService.PostOperation("testOperation", TelemetryResult.Success, correlatedWith: context.Properties); await sender.DisposeAsync(); var count = 0; await foreach (var item in sender.ContextChannel.Reader.ReadAllAsync()) { count++; } Assert.Equal(expectCount, count); } [Fact] public async Task WriteTelemetry_IntegrationTest_TelemetrySent() { var testSink = new TestSink(); var loggerFactory = LoggerFactory.Create(b => { b.AddProvider(new TestLoggerProvider(testSink)); b.AddXunit(output); }); var options = new TestDashboardOptions(new DashboardOptions { DebugSession = new DebugSessionOptions { Port = 5000, ServerCertificate = Convert.ToBase64String(TelemetryTestHelpers.GenerateDummyCertificate().Export(X509ContentType.Cert)), Token = "test" } }); Assert.True(options.Value.DebugSession.TryParseOptions(out _)); var userTaskCorrelationId = Guid.NewGuid(); var tcs = new TaskCompletionSource<PostOperationRequest>(TaskCreationOptions.RunContinuationsAsynchronously); var telemetrySender = new DashboardTelemetrySender(options, loggerFactory.CreateLogger<DashboardTelemetrySender>()) { CreateHandler = _ => new TestHttpMessageHandler( (request, _) => { if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryEnabled) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(new TelemetryEnabledResponse(IsEnabled: true)) ?? string.Empty) }); } else if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryStart) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); } else if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryPostProperty) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); } else if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryPostUserTask) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(new TelemetryEventCorrelation { Id = userTaskCorrelationId })) }); } else if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryPostOperation) { var requestContent = (JsonContent)request.Content!; tcs.SetResult((PostOperationRequest)requestContent.Value!); return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(new TelemetryEventCorrelation { Id = Guid.NewGuid() })) }); } else { throw new InvalidCastException($"Unexpected path: {request.RequestUri}"); } }) }; var telemetryService = await CreateTelemetryServiceAsync(telemetrySender, loggerFactory: loggerFactory); await telemetryService.InitializeAsync(); var context = telemetryService.PostUserTask("testTask", TelemetryResult.Success); telemetryService.PostOperation("testOperation", TelemetryResult.Success, correlatedWith: context.Properties); var operationRequest = await tcs.Task.DefaultTimeout(); var correlatedProperty = Assert.Single(operationRequest.CorrelatedWith!); Assert.Equal(userTaskCorrelationId, correlatedProperty.Id); await telemetrySender.DisposeAsync(); Assert.False(testSink.Writes.Any(w => w.LogLevel >= LogLevel.Warning), "Test ran without any warnings or errors logged."); } private static async Task<DashboardTelemetryService> CreateTelemetryServiceAsync(IDashboardTelemetrySender? dashboardTelemetrySender = null, ILoggerFactory? loggerFactory = null) { loggerFactory ??= NullLoggerFactory.Instance; var telemetryService = new DashboardTelemetryService(loggerFactory.CreateLogger<DashboardTelemetryService>(), dashboardTelemetrySender ?? new TestDashboardTelemetrySender()); await telemetryService.InitializeAsync(); return telemetryService; } public class TestDashboardOptions(DashboardOptions value) : IOptions<DashboardOptions> { public DashboardOptions Value { get; } = value; } }