File: TelemetryRepositoryTests\TelemetryReceiptTests.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.Model;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Tests.Shared;
using Dapper;
using Microsoft.Data.Sqlite;
using OpenTelemetry.Proto.Logs.V1;
using OpenTelemetry.Proto.Metrics.V1;
using OpenTelemetry.Proto.Trace.V1;
using Xunit;
using static Aspire.Tests.Shared.Telemetry.TelemetryTestHelpers;
 
namespace Aspire.Dashboard.Tests.TelemetryRepositoryTests;
 
public sealed class TelemetryReceiptTests(ITestOutputHelper output) : IDisposable
{
    private readonly TemporaryWorkspace _workspace = TemporaryWorkspace.Create(output);
 
    [Theory]
    [InlineData(AspireDataType.StructuredLogs)]
    [InlineData(AspireDataType.Traces)]
    [InlineData(AspireDataType.Metrics)]
    public async Task AcceptedTelemetry_RemainsReceivedAfterRemoval(AspireDataType signal)
    {
        var path = Path.Combine(_workspace.Path, "dashboard.db");
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(path);
        var repository = context.Repository;
        Assert.False(repository.HasReceivedTelemetry);
 
        await AddSignalAsync(repository, signal);
        Assert.True(repository.HasReceivedTelemetry);
 
        using (var resumed = SqliteRepositoryTestHelpers.CreateTelemetryRepository(path))
        {
            Assert.True(resumed.Repository.HasReceivedTelemetry);
        }
 
        await repository.ClearSelectedSignalsAsync(new()
        {
            [repository.GetResources().Single().ResourceKey.GetCompositeName()] = [AspireDataType.Resource, AspireDataType.StructuredLogs, AspireDataType.Traces, AspireDataType.Metrics]
        });
        await repository.ClearStructuredLogsAsync();
        await repository.ClearTracesAsync();
        await repository.ClearMetricsAsync();
        Assert.Empty(repository.GetResources());
        Assert.True(repository.HasReceivedTelemetry);
 
        using var restarted = SqliteRepositoryTestHelpers.CreateTelemetryRepository(path);
        Assert.False(restarted.Repository.HasReceivedTelemetry);
    }
 
    [Fact]
    public async Task EmptyEnvelopes_DoNotCountAsReceived()
    {
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(Path.Combine(_workspace.Path, "dashboard.db"));
        var repository = context.Repository;
        await repository.AddLogsAsync(new(), [new ResourceLogs { Resource = CreateResource("test") }]);
        await repository.AddTracesAsync(new(), [new ResourceSpans { Resource = CreateResource("test") }]);
        await repository.AddMetricsAsync(new(), [new ResourceMetrics { Resource = CreateResource("test") }]);
 
        Assert.NotEmpty(repository.GetResources());
        Assert.False(repository.HasReceivedTelemetry);
    }
 
    [Theory]
    [InlineData(AspireDataType.StructuredLogs)]
    [InlineData(AspireDataType.Traces)]
    [InlineData(AspireDataType.Metrics)]
    public async Task PausedTelemetry_DoesNotCountAsReceived(AspireDataType signal)
    {
        var pauseManager = new PauseManager();
        pauseManager.SetStructuredLogsPaused(true);
        pauseManager.SetTracesPaused(true);
        pauseManager.SetMetricsPaused(true);
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(
            Path.Combine(_workspace.Path, "dashboard.db"), pauseManager: pauseManager);
 
        await AddSignalAsync(context.Repository, signal);
 
        Assert.False(context.Repository.HasReceivedTelemetry);
    }
 
    [Fact]
    public async Task RejectedMetric_DoesNotCountAsReceived()
    {
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(Path.Combine(_workspace.Path, "dashboard.db"));
        var metric = CreateSumMetric("test", DateTime.UnixEpoch);
        metric.Sum.AggregationTemporality = (AggregationTemporality)42;
        var addContext = new AddContext();
        await context.Repository.AddMetricsAsync(addContext,
        [
            new ResourceMetrics
            {
                Resource = CreateResource("test"),
                ScopeMetrics = { new ScopeMetrics { Scope = CreateScope(), Metrics = { metric } } }
            }
        ]);
 
        Assert.True(addContext.FailureCount > 0);
        Assert.False(context.Repository.HasReceivedTelemetry);
    }
 
    [Fact]
    public async Task FailedTransaction_DoesNotCountAsReceived()
    {
        var path = Path.Combine(_workspace.Path, "dashboard.db");
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(path);
        using var connection = new SqliteConnection($"Data Source={path};Pooling=False");
        connection.Open();
        connection.Execute("""
            CREATE TRIGGER reject_logs BEFORE INSERT ON telemetry_logs
            BEGIN
                SELECT RAISE(ABORT, 'Test write failure');
            END;
            """);
 
        await Assert.ThrowsAsync<SqliteException>(() => AddSignalAsync(context.Repository, AspireDataType.StructuredLogs));
 
        Assert.False(context.Repository.HasReceivedTelemetry);
    }
 
    [Fact]
    public async Task ConcurrentSignals_KeepReceivedState()
    {
        using var context = SqliteRepositoryTestHelpers.CreateTelemetryRepository(Path.Combine(_workspace.Path, "dashboard.db"));
        Assert.False(context.Repository.HasReceivedTelemetry);
 
        await Task.WhenAll(
            AddSignalAsync(context.Repository, AspireDataType.StructuredLogs),
            AddSignalAsync(context.Repository, AspireDataType.Traces),
            AddSignalAsync(context.Repository, AspireDataType.Metrics));
 
        Assert.True(context.Repository.HasReceivedTelemetry);
    }
 
    private static Task AddSignalAsync(SqliteTelemetryRepository repository, AspireDataType signal) => signal switch
    {
        AspireDataType.StructuredLogs => repository.AddLogsAsync(new(),
        [
            new ResourceLogs
            {
                Resource = CreateResource("test"),
                ScopeLogs = { new ScopeLogs { Scope = CreateScope(), LogRecords = { CreateLogRecord(message: "test") } } }
            }
        ]),
        AspireDataType.Traces => repository.AddTracesAsync(new(),
        [
            new ResourceSpans
            {
                Resource = CreateResource("test"),
                ScopeSpans = { new ScopeSpans { Scope = CreateScope(), Spans = { CreateSpan("trace", "span", DateTime.UnixEpoch, DateTime.UnixEpoch.AddSeconds(1)) } } }
            }
        ]),
        AspireDataType.Metrics => repository.AddMetricsAsync(new(),
        [
            new ResourceMetrics
            {
                Resource = CreateResource("test"),
                ScopeMetrics = { new ScopeMetrics { Scope = CreateScope(), Metrics = { CreateSumMetric("test", DateTime.UnixEpoch) } } }
            }
        ]),
        _ => throw new ArgumentOutOfRangeException(nameof(signal))
    };
 
    public void Dispose() => _workspace.Dispose();
}