File: Telemetry\DashboardTelemetrySenderTests.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.Security.Cryptography.X509Certificates;
using System.Text.Json;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.Telemetry;
using Aspire.Tests.Shared.Telemetry;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
 
namespace Aspire.Dashboard.Tests.Telemetry;
 
public class DashboardTelemetrySenderTests
{
    [Fact]
    public async Task CreateTelemetryService_WithNoDebugSession_ShowsTelemetryUnsupported()
    {
        var options = new TestDashboardOptions(new DashboardOptions
        {
            DebugSession = new DebugSessionOptions()
        });
 
        var telemetrySender = new DashboardTelemetrySender(options, NullLogger<DashboardTelemetrySender>.Instance);
        var result = await telemetrySender.TryStartTelemetrySessionAsync();
 
        Assert.False(result);
    }
 
    [Fact]
    public async Task CreateTelemetryService_WithDebugSession_Optout_ShowsTelemetryUnsupported()
    {
        var options = new TestDashboardOptions(new DashboardOptions
        {
            DebugSession = new DebugSessionOptions
            {
                Port = 5000,
                ServerCertificate = Convert.ToBase64String(TelemetryTestHelpers.GenerateDummyCertificate().Export(X509ContentType.Cert)),
                Token = "test",
                TelemetryOptOut = true
            }
        });
 
        Assert.True(options.Value.DebugSession.TryParseOptions(out _));
 
        var telemetrySender = new DashboardTelemetrySender(options, NullLogger<DashboardTelemetrySender>.Instance);
        var result = await telemetrySender.TryStartTelemetrySessionAsync();
 
        Assert.False(result);
    }
 
    [Theory]
    [MemberData(nameof(CreateTelemetryService_WithValidDebugSession_DifferentServerResponses_ShowsTelemetrySupported_MemberData))]
    public async Task CreateTelemetryService_WithValidDebugSession_DifferentServerResponses_ShowsTelemetrySupported(
        HttpStatusCode? telemetryEnabledResponseStatusCode,
        string? telemetryEnabledResponseBody,
        HttpStatusCode? startTelemetryResponseStatusCode,
        bool expectedTelemetryEnabled)
    {
        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 telemetrySender = new DashboardTelemetrySender(options, NullLogger<DashboardTelemetrySender>.Instance);
        telemetrySender.CreateHandler = handler => new TestHttpMessageHandler(
            (request, cancellationToken) =>
            {
                if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryEnabled)
                {
                    if (telemetryEnabledResponseStatusCode == null)
                    {
                        return Task.FromException<HttpResponseMessage>(new InvalidOperationException());
                    }
                    return Task.FromResult(new HttpResponseMessage(telemetryEnabledResponseStatusCode.Value) { Content = new StringContent(telemetryEnabledResponseBody ?? string.Empty) });
                }
                else if (request.RequestUri!.AbsolutePath == TelemetryEndpoints.TelemetryStart)
                {
                    if (startTelemetryResponseStatusCode == null)
                    {
                        return Task.FromException<HttpResponseMessage>(new InvalidOperationException());
                    }
                    return Task.FromResult(new HttpResponseMessage(startTelemetryResponseStatusCode.Value));
                }
                else
                {
                    throw new InvalidCastException($"Unexpected path: {request.RequestUri}");
                }
            });
        var result = await telemetrySender.TryStartTelemetrySessionAsync();
 
        Assert.Equal(expectedTelemetryEnabled, result);
    }
 
    [Theory]
    [InlineData(false, "http://localhost:5000/")]
    [InlineData(true, "https://localhost:5000/")]
    public void CreateTelemetrySender_WithDebugSession_UsesCorrectScheme(bool isHttps, string expectedUrl)
    {
        var options = new TestDashboardOptions(new DashboardOptions
        {
            DebugSession = new DebugSessionOptions
            {
                Port = 5000,
                ServerCertificate = isHttps ? Convert.ToBase64String(TelemetryTestHelpers.GenerateDummyCertificate().Export(X509ContentType.Cert)) : null,
                Token = "test"
            }
        });
 
        Assert.True(options.Value.DebugSession.TryParseOptions(out _));
 
        var telemetrySender = new DashboardTelemetrySender(options, NullLogger<DashboardTelemetrySender>.Instance);
        Assert.True(telemetrySender.TryCreateHttpClient(out var client));
 
        Assert.NotNull(client);
        Assert.Equal(expectedUrl, client.BaseAddress?.ToString());
    }
 
    public static TheoryData<HttpStatusCode?, string?, HttpStatusCode?, bool> CreateTelemetryService_WithValidDebugSession_DifferentServerResponses_ShowsTelemetrySupported_MemberData()
    {
        return new TheoryData<HttpStatusCode?, string?, HttpStatusCode?, bool>
        {
            // No result (connection refused)
            { null, null, null, false },
            // 404 (old version of VS/VSC)
            { HttpStatusCode.NotFound, null, null, false},
            // 200 OK but false (telemetry not supported)
            { HttpStatusCode.OK, JsonSerializer.Serialize(new TelemetryEnabledResponse(IsEnabled: false)), null, false },
            // 200 OK but true (telemetry supported)
            { HttpStatusCode.OK, JsonSerializer.Serialize(new TelemetryEnabledResponse(IsEnabled: true)), HttpStatusCode.OK, true },
        };
    }
 
    public class TestDashboardOptions(DashboardOptions value) : IOptions<DashboardOptions>
    {
        public DashboardOptions Value { get; } = value;
    }
}
 
internal sealed class TestHttpMessageHandler : DelegatingHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _value;
 
    public TestHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> value)
    {
        _value = value;
    }
 
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return _value(request, cancellationToken);
    }
}