File: Model\SpanWaterfallViewModelTests.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.Model.Otlp;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Tests.Shared.Telemetry;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
 
namespace Aspire.Dashboard.Tests.Model;
 
public sealed class SpanWaterfallViewModelTests
{
    [Fact]
    public void Create_HasChildren_ChildrenPopulated()
    {
        var context = new OtlpContext { Logger = NullLogger.Instance, Options = new() };
        var app1 = new OtlpResource("app1", "instance", uninstrumentedPeer: false, context);
        var app2 = new OtlpResource("app2", "instance", uninstrumentedPeer: false, context);
        var trace = new OtlpTrace(new byte[] { 1, 2, 3 }, DateTime.MinValue);
        var scope = TelemetryTestHelpers.CreateOtlpScope(context);
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app1, trace, scope, spanId: "1", parentSpanId: null, startDate: new DateTime(2001, 1, 1, 1, 1, 2, DateTimeKind.Utc)));
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app2, trace, scope, spanId: "1-1", parentSpanId: "1", startDate: new DateTime(2001, 1, 1, 1, 1, 3, DateTimeKind.Utc)));
 
        var viewModels = SpanWaterfallViewModel.Create(trace, [], new SpanWaterfallViewModel.TraceDetailState([], []));
 
        Assert.Collection(viewModels,
            item =>
            {
                Assert.Equal("1", item.Span.SpanId);
                Assert.Equal("1-1", Assert.Single(item.Children).Span.SpanId);
            },
            item =>
            {
                Assert.Equal("1-1", item.Span.SpanId);
                Assert.Empty(item.Children);
            });
    }
 
    [Fact]
    public void Create_RootSpanZeroDuration_ZeroPercentage()
    {
        var context = new OtlpContext { Logger = NullLogger.Instance, Options = new() };
        var app1 = new OtlpResource("app1", "instance", uninstrumentedPeer: false, context);
        var date = new DateTime(2001, 1, 1, 1, 1, 2, DateTimeKind.Utc);
        var trace = new OtlpTrace(new byte[] { 1, 2, 3 }, DateTime.MinValue);
        var scope = TelemetryTestHelpers.CreateOtlpScope(context);
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app1, trace, scope, spanId: "31", parentSpanId: null, startDate: date, endDate: date));
        var log = new LogSummary
        {
            InternalId = 1,
            TimeStamp = date,
            Severity = LogLevel.Information,
            Message = "Test log",
            SpanId = "31",
            TraceId = trace.TraceId,
            ScopeName = scope.Name,
            Resource = app1,
            ExceptionText = null,
            HasGenAI = false
        };
 
        var viewModels = SpanWaterfallViewModel.Create(trace, [log], new SpanWaterfallViewModel.TraceDetailState([], []));
 
        var root = Assert.Single(viewModels);
        Assert.Equal("31", root.Span.SpanId);
        Assert.Equal(0, root.LeftOffset);
        Assert.Equal(0, root.Width);
        var spanLog = Assert.Single(root.SpanLogs);
        Assert.Equal(0, spanLog.LeftOffset);
    }
 
    [Fact]
    public void Create_OutgoingPeers_UsesPeerAddressWhenPeerIsNotPersisted()
    {
        var context = new OtlpContext { Logger = NullLogger.Instance, Options = new() };
        var app1 = new OtlpResource("app1", "instance", uninstrumentedPeer: false, context);
        var app2 = new OtlpResource("app2", "instance", uninstrumentedPeer: false, context);
        var trace = new OtlpTrace(new byte[] { 1, 2, 3 }, DateTime.MinValue);
        var scope = TelemetryTestHelpers.CreateOtlpScope(context);
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app1, trace, scope, spanId: "1", parentSpanId: null, startDate: new DateTime(2001, 1, 1, 1, 1, 2, DateTimeKind.Utc), kind: OtlpSpanKind.Client, attributes: [KeyValuePair.Create("http.url", "http://localhost:59267/getScriptTag"), KeyValuePair.Create("server.address", "localhost")]));
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(app2, trace, scope, spanId: "2", parentSpanId: null, startDate: new DateTime(2001, 2, 1, 1, 1, 2, DateTimeKind.Utc), kind: OtlpSpanKind.Client));
 
        var viewModels = SpanWaterfallViewModel.Create(trace, [], new SpanWaterfallViewModel.TraceDetailState([], []));
 
        Assert.Collection(viewModels,
            item =>
            {
                Assert.Equal("1", item.Span.SpanId);
                Assert.Equal("localhost", item.UninstrumentedPeer);
            },
            item =>
            {
                Assert.Equal("2", item.Span.SpanId);
                Assert.Null(item.UninstrumentedPeer);
            });
    }
 
    [Fact]
    public void Create_OutgoingPeers_UsesPersistedNameOnlyPeer()
    {
        var context = new OtlpContext { Logger = NullLogger.Instance, Options = new() };
        var app = new OtlpResource("app", "instance", uninstrumentedPeer: false, context);
        var browserLink = new OtlpResource("Browser Link", instanceId: null, uninstrumentedPeer: true, context);
        var trace = new OtlpTrace(new byte[] { 1, 2, 3 }, DateTime.MinValue);
        var scope = TelemetryTestHelpers.CreateOtlpScope(context);
        trace.AddSpan(TelemetryTestHelpers.CreateOtlpSpan(
            app,
            trace,
            scope,
            spanId: "1",
            parentSpanId: null,
            startDate: new DateTime(2001, 1, 1, 1, 1, 2, DateTimeKind.Utc),
            kind: OtlpSpanKind.Client,
            attributes: [KeyValuePair.Create(OtlpSpan.ServerAddressAttributeKey, "localhost")],
            uninstrumentedPeer: browserLink));
 
        var viewModel = Assert.Single(SpanWaterfallViewModel.Create(trace, [], new SpanWaterfallViewModel.TraceDetailState([], [app, browserLink])));
 
        Assert.Equal("Browser Link", viewModel.UninstrumentedPeer);
    }
 
    [Fact]
    public void SpanTypeFilters_EmptyOperandsThrow()
    {
        Assert.Throws<ArgumentException>(() => new SpanHasAttributeTelemetryFilter([]));
        Assert.Throws<ArgumentException>(() => new SpanScopePrefixTelemetryFilter([]));
        Assert.Throws<ArgumentException>(() => new SpanNoMatchTelemetryFilter([]));
    }
}