Files
git.stella-ops.org/src/Telemetry/StellaOps.Telemetry.Analyzers/StellaOps.Telemetry.Analyzers.Tests/MetricLabelAnalyzerTests.cs
StellaOps Bot ef6e4b2067
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
2025-11-27 21:45:32 +02:00

479 lines
16 KiB
C#

using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<StellaOps.Telemetry.Analyzers.MetricLabelAnalyzer>;
namespace StellaOps.Telemetry.Analyzers.Tests;
public sealed class MetricLabelAnalyzerTests
{
[Fact]
public async Task ValidLabelKey_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status_code", "200"));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task InvalidLabelKey_UpperCase_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag({|#0:"StatusCode"|}, "200"));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.InvalidLabelKeyDiagnosticId)
.WithLocation(0)
.WithArguments("StatusCode");
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task HighCardinalityLabelKey_UserId_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag({|#0:"user_id"|}, "123"));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
.WithLocation(0)
.WithArguments("user_id");
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task HighCardinalityLabelKey_RequestId_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void IncrementRequests(params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.IncrementRequests(GoldenSignalMetrics.Tag({|#0:"request_id"|}, "abc-123"));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
.WithLocation(0)
.WithArguments("request_id");
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task HighCardinalityLabelKey_Email_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void IncrementErrors(params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.IncrementErrors(GoldenSignalMetrics.Tag({|#0:"user_email"|}, "test@example.com"));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
.WithLocation(0)
.WithArguments("user_email");
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task DynamicLabelValue_Variable_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod(string dynamicValue)
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("operation", {|#0:dynamicValue|}));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
.WithLocation(0);
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task DynamicLabelValue_InterpolatedString_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod(int code)
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", {|#0:$"code_{code}"|}));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
.WithLocation(0);
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task StaticLabelValue_Constant_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
private const string StatusOk = "ok";
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", StatusOk));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task EnumLabelValue_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public enum Status { Ok, Error }
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Status.Ok));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task EnumToStringLabelValue_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public enum Status { Ok, Error }
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Status.Ok.ToString()));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task TupleSyntax_ValidLabel_NoDiagnostic()
{
var test = """
using System;
using System.Diagnostics.Metrics;
namespace TestNamespace
{
public class TestClass
{
public void TestMethod(Counter<int> counter)
{
counter.Add(1, ("status_code", "200"));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task KeyValuePairCreation_HighCardinalityKey_ReportsDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
namespace TestNamespace
{
public class TestClass
{
public void TestMethod(Counter<int> counter)
{
counter.Add(1, new KeyValuePair<string, object?>({|#0:"session_id"|}, "abc"));
}
}
}
""";
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
.WithLocation(0)
.WithArguments("session_id");
await Verifier.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task NonMetricMethod_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class RegularClass
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void SomeMethod(params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod()
{
var obj = new RegularClass();
obj.SomeMethod(RegularClass.Tag("user_id", "123"));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task MultipleIssues_ReportsAllDiagnostics()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public class TestClass
{
public void TestMethod(string dynamicValue)
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0,
GoldenSignalMetrics.Tag({|#0:"UserId"|}, "static"),
GoldenSignalMetrics.Tag("operation", {|#1:dynamicValue|}));
}
}
}
""";
var expected1 = Verifier.Diagnostic(MetricLabelAnalyzer.InvalidLabelKeyDiagnosticId)
.WithLocation(0)
.WithArguments("UserId");
var expected2 = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
.WithLocation(1);
await Verifier.VerifyAnalyzerAsync(test, expected1, expected2);
}
[Fact]
public async Task StaticReadonlyField_LabelValue_NoDiagnostic()
{
var test = """
using System;
using System.Collections.Generic;
namespace TestNamespace
{
public class GoldenSignalMetrics
{
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
}
public static class Labels
{
public static readonly string StatusOk = "ok";
}
public class TestClass
{
public void TestMethod()
{
var metrics = new GoldenSignalMetrics();
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Labels.StatusOk));
}
}
}
""";
await Verifier.VerifyAnalyzerAsync(test);
}
}