File: Telemetry\DashboardStartupTelemetryTests.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.Diagnostics;
using System.Runtime.InteropServices;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.Telemetry;
using Aspire.Shared;
using Xunit;
 
namespace Aspire.Dashboard.Tests.Telemetry;
 
public class DashboardStartupTelemetryTests
{
    [Theory]
    [InlineData(true, null, DashboardPersistenceMode.None, "none")]
    [InlineData(true, null, DashboardPersistenceMode.Run, "run")]
    [InlineData(true, null, DashboardPersistenceMode.Resume, "resume")]
    [InlineData(false, KnownDashboardStartupFailureReasons.AddressInUse, DashboardPersistenceMode.None, "none")]
    [InlineData(false, KnownDashboardStartupFailureReasons.AddressInUse, DashboardPersistenceMode.Run, "run")]
    [InlineData(false, KnownDashboardStartupFailureReasons.AddressInUse, DashboardPersistenceMode.Resume, "resume")]
    [InlineData(false, KnownDashboardStartupFailureReasons.ConfigurationValidation, (DashboardPersistenceMode)999, "unknown")]
    [InlineData(false, KnownDashboardStartupFailureReasons.ConfigurationValidation, (DashboardPersistenceMode)(-1), "unknown")]
    public void Record_RecordsStartupResultOnce(bool success, string? errorType, DashboardPersistenceMode persistenceMode, string expectedPersistenceMode)
    {
        using var fixture = new DashboardTelemetryFixture();
        var options = new DashboardOptions
        {
            LaunchContext = "AppHost",
            Data = new DashboardDataOptions { PersistenceMode = persistenceMode }
        };
        var startupTimestamp = Stopwatch.GetTimestamp() - Stopwatch.Frequency;
        var startupTelemetry = new DashboardStartupTelemetry(fixture.Telemetry, options, startupTimestamp);
 
        if (success)
        {
            startupTelemetry.RecordSuccess();
            startupTelemetry.RecordFailure("ignored");
        }
        else
        {
            startupTelemetry.RecordFailure(errorType!);
            startupTelemetry.RecordSuccess();
        }
 
        Assert.True(fixture.LogChannel.Reader.TryRead(out var log));
        Assert.Equal(TelemetryEventKeys.Startup, log.Message);
        var attributes = log.Attributes.ToDictionary(p => p.Key, p => p.Value);
        var duration = Assert.IsType<double>(attributes[TelemetryPropertyKeys.StartupDurationMilliseconds]);
        Assert.True(duration >= 900);
        List<KeyValuePair<string, object?>> expectedEventProperties =
        [
            new("microsoft.operation_name", TelemetryEventKeys.Startup),
            new("{OriginalFormat}", TelemetryEventKeys.Startup),
            new(TelemetryPropertyKeys.StartupSuccess, success),
            new(TelemetryPropertyKeys.StartupLaunchContext, KnownDashboardLaunchContexts.AppHost),
            new(TelemetryPropertyKeys.StartupPersistenceMode, expectedPersistenceMode),
            new(TelemetryPropertyKeys.StartupDurationMilliseconds, duration)
        ];
        if (errorType is not null)
        {
            expectedEventProperties.Add(new(TelemetryPropertyKeys.ErrorType, errorType));
        }
        fixture.AssertAttributes(log.Attributes, [.. expectedEventProperties]);
        Assert.False(fixture.LogChannel.Reader.TryPeek(out _));
    }
 
    [Theory]
    [InlineData(DashboardPersistenceMode.None)]
    [InlineData(DashboardPersistenceMode.Run)]
    [InlineData(DashboardPersistenceMode.Resume)]
    public void Record_RecordsUnknownPersistenceModeWhenParsingFails(DashboardPersistenceMode persistenceMode)
    {
        using var fixture = new DashboardTelemetryFixture();
        var options = new DashboardOptions
        {
            Data = new DashboardDataOptions
            {
                PersistenceMode = persistenceMode,
                PersistenceModeParseError = "Invalid persistence mode."
            }
        };
        var startupTelemetry = new DashboardStartupTelemetry(fixture.Telemetry, options, Stopwatch.GetTimestamp());
 
        startupTelemetry.RecordFailure(KnownDashboardStartupFailureReasons.ConfigurationValidation);
 
        Assert.True(fixture.LogChannel.Reader.TryRead(out var log));
        var attributes = log.Attributes.ToDictionary(p => p.Key, p => p.Value);
        Assert.Equal("unknown", attributes[TelemetryPropertyKeys.StartupPersistenceMode]);
        Assert.Equal(false, attributes[TelemetryPropertyKeys.StartupSuccess]);
        Assert.Equal(KnownDashboardStartupFailureReasons.ConfigurationValidation, attributes[TelemetryPropertyKeys.ErrorType]);
        Assert.False(fixture.LogChannel.Reader.TryPeek(out _));
    }
 
    [Theory]
    [InlineData("AppHost", KnownDashboardLaunchContexts.AppHost)]
    [InlineData(" CLI ", KnownDashboardLaunchContexts.Cli)]
    [InlineData("Container", KnownDashboardLaunchContexts.Container)]
    [InlineData("unknown", KnownDashboardLaunchContexts.Unknown)]
    [InlineData("custom-context", KnownDashboardLaunchContexts.Unknown)]
    [InlineData("", KnownDashboardLaunchContexts.Unknown)]
    [InlineData(" ", KnownDashboardLaunchContexts.Unknown)]
    [InlineData(null, KnownDashboardLaunchContexts.Unknown)]
    public void Record_NormalizesLaunchContext(string? launchContext, string expected)
    {
        using var fixture = new DashboardTelemetryFixture();
        var options = new DashboardOptions { LaunchContext = launchContext };
        var startupTelemetry = new DashboardStartupTelemetry(fixture.Telemetry, options, Stopwatch.GetTimestamp());
 
        startupTelemetry.RecordSuccess();
 
        Assert.True(fixture.LogChannel.Reader.TryRead(out var log));
        var attributes = log.Attributes.ToDictionary(p => p.Key, p => p.Value);
        Assert.Equal(expected, attributes[TelemetryPropertyKeys.StartupLaunchContext]);
        Assert.Equal("none", attributes[TelemetryPropertyKeys.StartupPersistenceMode]);
    }
 
    [Theory]
    [InlineData(true, true, "ci")]
    [InlineData(true, false, "local")]
    [InlineData(false, true, "ci")]
    [InlineData(false, false, "local")]
    public void Record_RecordsDeploymentEnvironment(bool success, bool isCIEnvironment, string expectedEnvironment)
    {
        using var fixture = new DashboardTelemetryFixture(isCIEnvironment: isCIEnvironment);
        var startupTelemetry = new DashboardStartupTelemetry(
            fixture.Telemetry, new DashboardOptions(), Stopwatch.GetTimestamp());
 
        if (success)
        {
            startupTelemetry.RecordSuccess();
        }
        else
        {
            startupTelemetry.RecordFailure(KnownDashboardStartupFailureReasons.ConfigurationValidation);
        }
 
        Assert.True(fixture.LogChannel.Reader.TryRead(out var log));
        var attributes = log.Attributes.ToDictionary(p => p.Key, p => p.Value);
        Assert.Equal(expectedEnvironment, Assert.IsType<string>(attributes["deployment.environment.name"]));
        Assert.Equal(success, attributes[TelemetryPropertyKeys.StartupSuccess]);
        Assert.False(fixture.LogChannel.Reader.TryPeek(out _));
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void Record_RecordsOperatingSystem(bool success)
    {
        using var fixture = new DashboardTelemetryFixture();
        var startupTelemetry = new DashboardStartupTelemetry(
            fixture.Telemetry, new DashboardOptions(), Stopwatch.GetTimestamp());
 
        if (success)
        {
            startupTelemetry.RecordSuccess();
        }
        else
        {
            startupTelemetry.RecordFailure(KnownDashboardStartupFailureReasons.ConfigurationValidation);
        }
 
        var expectedOsName = OperatingSystem.IsWindows() ? "Windows"
            : OperatingSystem.IsLinux() ? "Linux"
            : OperatingSystem.IsMacOS() ? "macOS"
            : RuntimeInformation.OSDescription;
        var expectedOsType = OperatingSystem.IsWindows() ? "windows"
            : OperatingSystem.IsLinux() ? "linux"
            : OperatingSystem.IsMacOS() ? "darwin"
            : "unknown";
        Assert.True(fixture.LogChannel.Reader.TryRead(out var log));
        Assert.Collection(log.Attributes.Where(p => p.Key.StartsWith("os.", StringComparison.Ordinal)).OrderBy(p => p.Key, StringComparer.Ordinal),
            attribute => Assert.Equal(new KeyValuePair<string, object?>("os.name", expectedOsName), attribute),
            attribute => Assert.Equal(new KeyValuePair<string, object?>("os.type", expectedOsType), attribute),
            attribute => Assert.Equal(new KeyValuePair<string, object?>("os.version", Environment.OSVersion.Version.ToString()), attribute));
        Assert.False(fixture.LogChannel.Reader.TryPeek(out _));
    }
}