Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -8,7 +8,8 @@ namespace StellaOps.Telemetry.Analyzers.Tests;
|
||||
|
||||
public sealed class MetricLabelAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ValidLabelKey_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -37,7 +38,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InvalidLabelKey_UpperCase_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -70,7 +72,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HighCardinalityLabelKey_UserId_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -103,7 +106,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HighCardinalityLabelKey_RequestId_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -136,7 +140,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HighCardinalityLabelKey_Email_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -169,7 +174,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DynamicLabelValue_Variable_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -201,7 +207,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DynamicLabelValue_InterpolatedString_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -233,7 +240,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StaticLabelValue_Constant_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -264,7 +272,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnumLabelValue_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -295,7 +304,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnumToStringLabelValue_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -326,7 +336,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TupleSyntax_ValidLabel_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -348,7 +359,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task KeyValuePairCreation_HighCardinalityKey_ReportsDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -375,7 +387,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NonMetricMethod_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
@@ -404,7 +417,8 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MultipleIssues_ReportsAllDiagnostics()
|
||||
{
|
||||
var test = """
|
||||
@@ -442,13 +456,15 @@ public sealed class MetricLabelAnalyzerTests
|
||||
await Verifier.VerifyAnalyzerAsync(test, expected1, expected2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StaticReadonlyField_LabelValue_NoDiagnostic()
|
||||
{
|
||||
var test = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace TestNamespace
|
||||
{
|
||||
public class GoldenSignalMetrics
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Telemetry.Analyzers.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace StellaOps.Telemetry.Core.Tests;
|
||||
/// </summary>
|
||||
public sealed class AsyncResumeTestHarness
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task JobScope_CaptureAndResume_PreservesContext()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -50,7 +51,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task JobScope_Resume_WithNullPayload_DoesNotThrow()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -61,7 +63,8 @@ public sealed class AsyncResumeTestHarness
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task JobScope_Resume_WithInvalidPayload_DoesNotThrow()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -72,7 +75,8 @@ public sealed class AsyncResumeTestHarness
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task JobScope_CreateQueueHeaders_IncludesAllContextFields()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -92,7 +96,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal("rule-789", headers["X-Imposed-Rule"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_Propagates_AcrossSimulatedJobQueue()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -133,7 +138,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal("tenant-B", results["job-2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_IsolatedBetween_ConcurrentJobWorkers()
|
||||
{
|
||||
var workerResults = new ConcurrentDictionary<int, (string? TenantId, string? CorrelationId)>();
|
||||
@@ -173,7 +179,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal(("tenant-3", "corr-3"), workerResults[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_FlowsThrough_NestedAsyncOperations()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -200,7 +207,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal(4, capturedTenants.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_Preserved_AcrossConfigureAwait()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -210,6 +218,7 @@ public sealed class AsyncResumeTestHarness
|
||||
using (accessor.CreateScope(new TelemetryContext { TenantId = "await-test" }))
|
||||
{
|
||||
capturedBefore = accessor.Context?.TenantId;
|
||||
using StellaOps.TestKit;
|
||||
await Task.Delay(10).ConfigureAwait(false);
|
||||
capturedAfter = accessor.Context?.TenantId;
|
||||
}
|
||||
@@ -218,7 +227,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal("await-test", capturedAfter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContextInjector_Inject_AddsAllHeaders()
|
||||
{
|
||||
var context = new TelemetryContext
|
||||
@@ -238,7 +248,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal("rule-789", headers["X-Imposed-Rule"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContextInjector_Extract_ReconstructsContext()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
@@ -257,7 +268,8 @@ public sealed class AsyncResumeTestHarness
|
||||
Assert.Equal("rule-789", context.ImposedRule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContextInjector_RoundTrip_PreservesAllFields()
|
||||
{
|
||||
var original = new TelemetryContext
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class CliTelemetryContextTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsTenantId_EqualsSyntax()
|
||||
{
|
||||
var args = new[] { "--tenant-id=my-tenant", "--other-arg", "value" };
|
||||
@@ -16,7 +17,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("my-tenant", result["tenant-id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsTenantId_SpaceSyntax()
|
||||
{
|
||||
var args = new[] { "--tenant-id", "my-tenant", "--other-arg", "value" };
|
||||
@@ -26,7 +28,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("my-tenant", result["tenant-id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsActor()
|
||||
{
|
||||
var args = new[] { "--actor=user@example.com" };
|
||||
@@ -36,7 +39,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("user@example.com", result["actor"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsCorrelationId()
|
||||
{
|
||||
var args = new[] { "--correlation-id", "corr-123" };
|
||||
@@ -46,7 +50,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("corr-123", result["correlation-id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsImposedRule()
|
||||
{
|
||||
var args = new[] { "--imposed-rule=policy-abc" };
|
||||
@@ -56,7 +61,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("policy-abc", result["imposed-rule"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_ExtractsMultipleArgs()
|
||||
{
|
||||
var args = new[]
|
||||
@@ -76,7 +82,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("rule-789", result["imposed-rule"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_IgnoresUnknownArgs()
|
||||
{
|
||||
var args = new[] { "--unknown-arg", "value", "--another", "thing" };
|
||||
@@ -86,7 +93,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseTelemetryArgs_CaseInsensitive()
|
||||
{
|
||||
var args = new[] { "--TENANT-ID=upper", "--Actor=mixed" };
|
||||
@@ -97,7 +105,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Equal("mixed", result["actor"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_SetsContextFromExplicitValues()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -120,7 +129,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_GeneratesCorrelationId_WhenNotProvided()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -134,7 +144,8 @@ public sealed class CliTelemetryContextTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InitializeFromArgs_UsesParseOutput()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -153,7 +164,8 @@ public sealed class CliTelemetryContextTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_ClearsContext_OnScopeDisposal()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -165,7 +177,8 @@ public sealed class CliTelemetryContextTests
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InitializeFromEnvironment_ReadsEnvVars()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -195,7 +208,8 @@ public sealed class CliTelemetryContextTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_ExplicitValues_OverrideEnvironment()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -209,6 +223,7 @@ public sealed class CliTelemetryContextTests
|
||||
using (CliTelemetryContext.Initialize(accessor, tenantId: "explicit-tenant"))
|
||||
{
|
||||
var context = accessor.Context;
|
||||
using StellaOps.TestKit;
|
||||
Assert.NotNull(context);
|
||||
Assert.Equal("explicit-tenant", context.TenantId);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class DeterministicLogFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NormalizeTimestamp_ConvertsToUtc()
|
||||
{
|
||||
var localTime = new DateTimeOffset(2025, 6, 15, 14, 30, 45, 123, TimeSpan.FromHours(5));
|
||||
@@ -17,7 +19,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("2025-06-15T09:30:45.123Z", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NormalizeTimestamp_TruncatesSubmilliseconds()
|
||||
{
|
||||
var timestamp1 = new DateTimeOffset(2025, 6, 15, 14, 30, 45, 123, TimeSpan.Zero).AddTicks(1234);
|
||||
@@ -30,7 +33,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("2025-06-15T14:30:45.123Z", result1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NormalizeTimestamp_DateTime_HandledCorrectly()
|
||||
{
|
||||
var dateTime = new DateTime(2025, 6, 15, 14, 30, 45, 123, DateTimeKind.Utc);
|
||||
@@ -40,7 +44,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("2025-06-15T14:30:45.123Z", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void OrderFields_ReservedFieldsFirst()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -59,7 +64,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("custom_field", result[3].Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void OrderFields_RemainingFieldsSortedAlphabetically()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -80,7 +86,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("zebra", result[3].Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void OrderFields_CaseInsensitiveSorting()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -99,7 +106,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal("Zebra", result[3].Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void OrderFields_DeterministicWithSameInput()
|
||||
{
|
||||
var fields1 = new List<KeyValuePair<string, object?>>
|
||||
@@ -124,7 +132,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsNdJson_FieldsInDeterministicOrder()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -145,7 +154,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.True(messageIndex < customIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsNdJson_WithTimestamp_NormalizesTimestamp()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -159,7 +169,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("\"timestamp\":\"2025-06-15T09:30:45.123Z\"", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsNdJson_ReplacesExistingTimestamp()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -175,7 +186,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("2025-06-15T14:30:45.123Z", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsNdJson_NullValues_Excluded()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -189,7 +201,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.DoesNotContain("null_field", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsKeyValue_FieldsInDeterministicOrder()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -209,7 +222,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.True(messageIndex < customIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsKeyValue_QuotesStringsWithSpaces()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -224,7 +238,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("simple=nospace", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsKeyValue_EscapesQuotesInValues()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -237,7 +252,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("\\\"quotes\\\"", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsKeyValue_WithTimestamp_NormalizesTimestamp()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -251,7 +267,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("timestamp=2025-06-15T09:30:45.123Z", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FormatAsKeyValue_NullValues_ShownAsNull()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -265,7 +282,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("null_field=null", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RepeatedFormatting_ProducesSameOutput()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -285,7 +303,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.All(results, r => Assert.Equal(results[0], r));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RepeatedKeyValueFormatting_ProducesSameOutput()
|
||||
{
|
||||
var fields = new List<KeyValuePair<string, object?>>
|
||||
@@ -304,7 +323,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.All(results, r => Assert.Equal(results[0], r));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DateTimeOffsetValuesInFields_NormalizedToUtc()
|
||||
{
|
||||
var localTimestamp = new DateTimeOffset(2025, 6, 15, 14, 30, 45, 123, TimeSpan.FromHours(5));
|
||||
@@ -318,7 +338,8 @@ public sealed class DeterministicLogFormatterTests
|
||||
Assert.Contains("2025-06-15T09:30:45.123Z", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReservedFieldOrder_MatchesSpecification()
|
||||
{
|
||||
var expectedOrder = new[]
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Diagnostics.Metrics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
@@ -39,7 +41,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
_listener.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordLatency_RecordsMeasurement()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -49,7 +52,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "stellaops_latency_seconds" && (double)m.Value == 0.123);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordLatency_AcceptsStopwatch()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -61,7 +65,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "stellaops_latency_seconds");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MeasureLatency_RecordsDurationOnDispose()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -75,7 +80,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
m.Name == "stellaops_latency_seconds" && (double)m.Value >= 0.01);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncrementErrors_IncreasesCounter()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -87,7 +93,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "stellaops_errors_total" && (long)m.Value == 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncrementRequests_IncreasesCounter()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -99,7 +106,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "stellaops_requests_total" && (long)m.Value == 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordLatency_WithTags_Works()
|
||||
{
|
||||
using var metrics = new GoldenSignalMetrics();
|
||||
@@ -111,7 +119,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "stellaops_latency_seconds");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Options_PrefixIsApplied()
|
||||
{
|
||||
var options = new GoldenSignalMetricsOptions { Prefix = "custom_" };
|
||||
@@ -122,7 +131,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_recordedMeasurements, m => m.Name == "custom_latency_seconds");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SetSaturationProvider_IsInvoked()
|
||||
{
|
||||
var options = new GoldenSignalMetricsOptions { EnableSaturationGauge = true };
|
||||
@@ -134,7 +144,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.NotNull(metrics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CardinalityGuard_WarnsOnHighCardinality()
|
||||
{
|
||||
var logEntries = new List<string>();
|
||||
@@ -157,7 +168,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.Contains(logEntries, e => e.Contains("High cardinality"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CardinalityGuard_DropsMetrics_WhenConfigured()
|
||||
{
|
||||
var options = new GoldenSignalMetricsOptions
|
||||
@@ -176,7 +188,8 @@ public sealed class GoldenSignalMetricsTests : IDisposable
|
||||
Assert.True(requestCount <= 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Tag_CreatesKeyValuePair()
|
||||
{
|
||||
var tag = GoldenSignalMetrics.Tag("key", "value");
|
||||
|
||||
@@ -7,6 +7,8 @@ using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class IncidentModeServiceTests : IDisposable
|
||||
@@ -39,7 +41,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
return new IncidentModeService(monitor, _contextAccessor.Object, _logger.Object, _timeProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_ValidActor_ReturnsSuccess()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -52,7 +55,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(service.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_NullActor_ThrowsArgumentException()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -61,7 +65,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
service.ActivateAsync(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_EmptyActor_ThrowsArgumentException()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -70,7 +75,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
service.ActivateAsync(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_WithTenantId_StoresTenantId()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -82,7 +88,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal("tenant-123", result.State.TenantId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_WithReason_StoresReason()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -94,7 +101,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal("Production incident INC-001", result.State.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_DefaultTtl_UsesConfiguredDefault()
|
||||
{
|
||||
using var service = CreateService(opt => opt.DefaultTtl = TimeSpan.FromMinutes(45));
|
||||
@@ -107,7 +115,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(expectedExpiry, result.State.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_CustomTtl_UsesTtlOverride()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -120,7 +129,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(expectedExpiry, result.State.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_TtlBelowMin_ClampedToMin()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -136,7 +146,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(expectedExpiry, result.State.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_TtlAboveMax_ClampedToMax()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -152,7 +163,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(expectedExpiry, result.State.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_AlreadyActive_ExtendsTtlAndReturnsWasAlreadyActive()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -167,7 +179,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(firstActivationId, secondResult.State!.ActivationId); // Same activation
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_RaisesActivatedEvent()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -181,7 +194,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(eventArgs.WasReactivation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_WhenAlreadyActive_RaisesReactivationEvent()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -196,7 +210,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(eventArgs.WasReactivation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeactivateAsync_WhenActive_ReturnsSuccessWithWasActive()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -210,7 +225,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(service.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeactivateAsync_WhenNotActive_ReturnsSuccessWithWasNotActive()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -221,7 +237,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(result.WasActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeactivateAsync_RaisesDeactivatedEvent()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -238,7 +255,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal("deactivator", eventArgs.DeactivatedBy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtendTtlAsync_WhenActive_ExtendsExpiry()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -255,7 +273,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(originalExpiry + TimeSpan.FromMinutes(15), newExpiry);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtendTtlAsync_WhenNotActive_ReturnsNull()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -265,7 +284,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtendTtlAsync_WhenDisabled_ReturnsNull()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -279,7 +299,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtendTtlAsync_ExceedsMaxExtensions_ReturnsNull()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -296,7 +317,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(thirdExtension);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtendTtlAsync_WouldExceedMaxTtl_ClampedToMax()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -314,7 +336,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(activatedAt + TimeSpan.FromHours(24), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetIncidentTags_WhenActive_ReturnsTagDictionary()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -332,7 +355,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(tags.ContainsKey("incident_activation_id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetIncidentTags_WhenNotActive_ReturnsEmptyDictionary()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -342,7 +366,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Empty(tags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetIncidentTags_WithAdditionalTags_IncludesThem()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -358,7 +383,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal("us-east-1", tags["region"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CurrentState_WhenActive_ReturnsState()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -372,7 +398,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(IncidentModeSource.Api, state.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CurrentState_WhenNotActive_ReturnsNull()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -382,7 +409,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(state);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsActive_WhenNotActivated_ReturnsFalse()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -390,7 +418,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(service.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task IsActive_WhenActivated_ReturnsTrue()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -399,7 +428,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(service.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task IsActive_WhenExpired_ReturnsFalse()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -414,7 +444,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(service.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateFromCliAsync_SetsSourceToCli()
|
||||
{
|
||||
using var service = CreateService();
|
||||
@@ -426,7 +457,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(IncidentModeSource.Cli, result.State.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateFromConfigAsync_WhenEnabled_Activates()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -441,7 +473,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(IncidentModeSource.Configuration, result.State.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateFromConfigAsync_WhenDisabled_FailsActivation()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -455,7 +488,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.NotNull(result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_Validate_ValidOptions_ReturnsNoErrors()
|
||||
{
|
||||
var options = new IncidentModeOptions();
|
||||
@@ -465,7 +499,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_Validate_DefaultTtlBelowMin_ReturnsError()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -480,7 +515,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Contains("DefaultTtl", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_Validate_DefaultTtlAboveMax_ReturnsError()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -495,7 +531,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Contains("DefaultTtl", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_Validate_InvalidSamplingRate_ReturnsError()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -509,7 +546,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Contains("IncidentSamplingRate", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_Validate_NegativeMaxExtensions_ReturnsError()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -523,7 +561,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Contains("MaxExtensions", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_ClampTtl_BelowMin_ReturnsMin()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -537,7 +576,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(TimeSpan.FromMinutes(10), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_ClampTtl_AboveMax_ReturnsMax()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -551,7 +591,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(TimeSpan.FromHours(4), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeOptions_ClampTtl_WithinRange_ReturnsSame()
|
||||
{
|
||||
var options = new IncidentModeOptions
|
||||
@@ -565,7 +606,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(TimeSpan.FromHours(2), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeState_IsExpired_BeforeExpiry_ReturnsFalse()
|
||||
{
|
||||
var state = new IncidentModeState
|
||||
@@ -581,7 +623,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.False(state.IsExpired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeState_IsExpired_AfterExpiry_ReturnsTrue()
|
||||
{
|
||||
var state = new IncidentModeState
|
||||
@@ -597,7 +640,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(state.IsExpired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeState_RemainingTime_WhenNotExpired_ReturnsPositive()
|
||||
{
|
||||
var state = new IncidentModeState
|
||||
@@ -613,7 +657,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.True(state.RemainingTime > TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeState_RemainingTime_WhenExpired_ReturnsZero()
|
||||
{
|
||||
var state = new IncidentModeState
|
||||
@@ -629,7 +674,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal(TimeSpan.Zero, state.RemainingTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeActivationResult_Succeeded_CreatesSuccessResult()
|
||||
{
|
||||
var state = new IncidentModeState
|
||||
@@ -650,7 +696,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeActivationResult_Failed_CreatesFailureResult()
|
||||
{
|
||||
var result = IncidentModeActivationResult.Failed("Test error message");
|
||||
@@ -660,7 +707,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Equal("Test error message", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeDeactivationResult_Succeeded_CreatesSuccessResult()
|
||||
{
|
||||
var result = IncidentModeDeactivationResult.Succeeded(wasActive: true, IncidentModeDeactivationReason.Manual);
|
||||
@@ -671,7 +719,8 @@ public sealed class IncidentModeServiceTests : IDisposable
|
||||
Assert.Null(result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentModeDeactivationResult_Failed_CreatesFailureResult()
|
||||
{
|
||||
var result = IncidentModeDeactivationResult.Failed("Test error");
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class LogRedactorTests
|
||||
@@ -15,7 +16,8 @@ public sealed class LogRedactorTests
|
||||
return new LogRedactor(monitor);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("password")]
|
||||
[InlineData("Password")]
|
||||
[InlineData("PASSWORD")]
|
||||
@@ -34,7 +36,8 @@ public sealed class LogRedactorTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("TraceId")]
|
||||
[InlineData("SpanId")]
|
||||
[InlineData("RequestId")]
|
||||
@@ -48,7 +51,8 @@ public sealed class LogRedactorTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("status")]
|
||||
[InlineData("operation")]
|
||||
[InlineData("duration")]
|
||||
@@ -62,7 +66,8 @@ public sealed class LogRedactorTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_JwtToken_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -75,7 +80,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_BearerToken_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -87,7 +93,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_EmailAddress_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -99,7 +106,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_CreditCardNumber_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -111,7 +119,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_SSN_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -123,7 +132,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_IPAddress_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -135,7 +145,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_ConnectionString_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -147,7 +158,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_AWSAccessKey_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -159,7 +171,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("[REDACTED]", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_NullOrEmpty_ReturnsOriginal()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -168,7 +181,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal("", redactor.RedactString(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_NoSensitiveData_ReturnsOriginal()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -179,7 +193,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal(input, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactString_DisabledRedaction_ReturnsOriginal()
|
||||
{
|
||||
var redactor = CreateRedactor(options => options.Enabled = false);
|
||||
@@ -190,7 +205,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal(input, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactAttributes_SensitiveFieldName_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -210,7 +226,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("password", result.RedactedFieldNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactAttributes_PatternInValue_Redacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -226,7 +243,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal("login", attributes["operation"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactAttributes_EmptyDictionary_ReturnsNone()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -237,7 +255,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal(0, result.RedactedFieldCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactAttributes_ExcludedField_NotRedacted()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -253,7 +272,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Equal("[REDACTED]", attributes["password"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantOverride_AdditionalSensitiveFields_Applied()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
@@ -271,7 +291,8 @@ public sealed class LogRedactorTests
|
||||
Assert.True(redactor.IsSensitiveField("customer_id", "tenant-a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantOverride_ExcludedFields_Applied()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
@@ -290,7 +311,8 @@ public sealed class LogRedactorTests
|
||||
Assert.False(redactor.IsSensitiveField("password", "tenant-a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantOverride_DisableRedaction_Applied()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
@@ -306,7 +328,8 @@ public sealed class LogRedactorTests
|
||||
Assert.False(redactor.IsRedactionEnabled("tenant-a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantOverride_ExpiredOverride_NotApplied()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
@@ -321,7 +344,8 @@ public sealed class LogRedactorTests
|
||||
Assert.True(redactor.IsRedactionEnabled("tenant-a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantOverride_FutureExpiry_Applied()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
@@ -336,7 +360,8 @@ public sealed class LogRedactorTests
|
||||
Assert.False(redactor.IsRedactionEnabled("tenant-a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactAttributes_TracksMatchedPatterns()
|
||||
{
|
||||
var redactor = CreateRedactor();
|
||||
@@ -352,7 +377,8 @@ public sealed class LogRedactorTests
|
||||
Assert.Contains("Email", result.MatchedPatterns);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CustomPlaceholder_Used()
|
||||
{
|
||||
var redactor = CreateRedactor(options =>
|
||||
|
||||
@@ -4,7 +4,8 @@ using StellaOps.Telemetry.Core;
|
||||
|
||||
public class MetricLabelGuardTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Coerce_Enforces_Cardinality_Limit()
|
||||
{
|
||||
var options = Options.Create(new StellaOpsTelemetryOptions
|
||||
@@ -27,7 +28,8 @@ public class MetricLabelGuardTests
|
||||
Assert.Equal("other", third); // budget exceeded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordRequestDuration_Truncates_Long_Labels()
|
||||
{
|
||||
var options = Options.Create(new StellaOpsTelemetryOptions
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class ProofCoverageMetricsTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0, 0, 1.0)]
|
||||
[InlineData(0, 10, 0.0)]
|
||||
[InlineData(5, 10, 0.5)]
|
||||
@@ -16,7 +19,8 @@ public sealed class ProofCoverageMetricsTests
|
||||
Assert.Equal(expected, ratio, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordScanCoverage_StoresLatestValues()
|
||||
{
|
||||
using var metrics = new ProofCoverageMetrics();
|
||||
|
||||
@@ -6,6 +6,8 @@ using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class SealedModeFileExporterTests : IDisposable
|
||||
@@ -51,7 +53,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
return new SealedModeFileExporter(monitor, _logger.Object, _timeProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_CreatesFile()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -63,7 +66,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.True(File.Exists(exporter.CurrentFilePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_CreatesDirectory_WhenNotExists()
|
||||
{
|
||||
var newDir = Path.Combine(_testDirectory, "subdir", "nested");
|
||||
@@ -77,7 +81,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.True(Directory.Exists(newDir));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_CalledMultipleTimes_DoesNotThrow()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -88,7 +93,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.True(exporter.IsInitialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_WritesDataToFile()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -103,7 +109,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Contains("[Traces]", fileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_IncludesTimestamp()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -117,7 +124,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Contains("2025-11-27", fileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_AutoInitializesIfNotCalled()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -131,7 +139,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Contains("auto-init test", fileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WriteRecord_WritesStringData()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -145,7 +154,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Contains("[Logs]", fileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_RotatesFile_WhenMaxBytesExceeded()
|
||||
{
|
||||
using var exporter = CreateExporter(opt =>
|
||||
@@ -167,7 +177,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.True(File.Exists($"{filePath}.1") || exporter.CurrentSize < 100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CurrentSize_TracksWrittenBytes()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -180,7 +191,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.True(exporter.CurrentSize > initialSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Flush_DoesNotThrow()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -192,7 +204,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
// Should not throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
var exporter = CreateExporter();
|
||||
@@ -203,7 +216,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
exporter.Write(Encoding.UTF8.GetBytes("test"), TelemetrySignal.Traces));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Initialize_WithEmptyFilePath_Throws()
|
||||
{
|
||||
using var exporter = CreateExporter(opt =>
|
||||
@@ -214,7 +228,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Throws<InvalidOperationException>(() => exporter.Initialize());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Write_DifferentSignals_IncludesSignalType()
|
||||
{
|
||||
using var exporter = CreateExporter();
|
||||
@@ -231,7 +246,8 @@ public sealed class SealedModeFileExporterTests : IDisposable
|
||||
Assert.Contains("[Logs]", fileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Rotation_DeletesOldestFile_WhenMaxRotatedFilesExceeded()
|
||||
{
|
||||
using var exporter = CreateExporter(opt =>
|
||||
|
||||
@@ -6,6 +6,8 @@ using Moq;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
@@ -44,7 +46,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
_timeProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsSealed_WhenOptionsEnabled_ReturnsTrue()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = true);
|
||||
@@ -52,7 +55,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.True(service.IsSealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsSealed_WhenOptionsDisabled_ReturnsFalse()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -60,7 +64,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.False(service.IsSealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsSealed_WhenEgressPolicySealed_ReturnsTrue()
|
||||
{
|
||||
_egressPolicy.Setup(p => p.IsSealed).Returns(true);
|
||||
@@ -69,7 +74,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.True(service.IsSealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsSealed_WhenEgressPolicyNotSealed_ReturnsFalse()
|
||||
{
|
||||
_egressPolicy.Setup(p => p.IsSealed).Returns(false);
|
||||
@@ -78,7 +84,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.False(service.IsSealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EffectiveSamplingRate_WhenNotSealed_ReturnsFullSampling()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -86,7 +93,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(1.0, service.EffectiveSamplingRate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EffectiveSamplingRate_WhenSealed_ReturnsMaxPercent()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -98,7 +106,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(0.1, service.EffectiveSamplingRate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EffectiveSamplingRate_WhenSealedWithIncidentMode_ReturnsFullSampling()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(true);
|
||||
@@ -112,7 +121,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(1.0, service.EffectiveSamplingRate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EffectiveSamplingRate_WhenSealedWithDisabledIncidentOverride_ReturnsCapped()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(true);
|
||||
@@ -126,7 +136,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(0.1, service.EffectiveSamplingRate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsIncidentModeOverrideActive_WhenConditionsMet_ReturnsTrue()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(true);
|
||||
@@ -139,7 +150,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.True(service.IsIncidentModeOverrideActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsIncidentModeOverrideActive_WhenNotSealed_ReturnsFalse()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(true);
|
||||
@@ -152,7 +164,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.False(service.IsIncidentModeOverrideActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsIncidentModeOverrideActive_WhenIncidentNotActive_ReturnsFalse()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(false);
|
||||
@@ -165,7 +178,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.False(service.IsIncidentModeOverrideActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSealedModeTags_WhenNotSealed_ReturnsEmpty()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -175,7 +189,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Empty(tags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSealedModeTags_WhenSealed_ReturnsSealedTag()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -189,7 +204,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("true", tags["sealed"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSealedModeTags_WhenSealedWithForceScrub_ReturnsScrubbedTag()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -204,7 +220,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("true", tags["scrubbed"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSealedModeTags_WhenSealedWithIncidentOverride_ReturnsOverrideTag()
|
||||
{
|
||||
_incidentModeService.Setup(s => s.IsActive).Returns(true);
|
||||
@@ -219,7 +236,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("true", tags["incident_override"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSealedModeTags_WithAdditionalTags_IncludesThem()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -235,7 +253,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("us-east-1", tags["region"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsExternalExportAllowed_WhenNotSealed_ReturnsTrue()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -246,7 +265,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsExternalExportAllowed_WhenSealed_ReturnsFalse()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = true);
|
||||
@@ -257,7 +277,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetLocalExporterConfig_WhenNotSealed_ReturnsNull()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -267,7 +288,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Null(config);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetLocalExporterConfig_WhenSealed_ReturnsConfig()
|
||||
{
|
||||
using var service = CreateService(opt =>
|
||||
@@ -288,7 +310,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(5, config.MaxRotatedFiles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordSealEvent_RaisesStateChangedEvent()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = true);
|
||||
@@ -303,7 +326,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("test-actor", eventArgs.Actor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordUnsealEvent_RaisesStateChangedEvent()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = false);
|
||||
@@ -318,7 +342,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal("admin", eventArgs.Actor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordDriftEvent_DoesNotThrow()
|
||||
{
|
||||
using var service = CreateService(opt => opt.Enabled = true);
|
||||
@@ -328,7 +353,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
service.RecordDriftEvent(endpoint, TelemetrySignal.Traces);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_Validate_ValidOptions_ReturnsNoErrors()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions();
|
||||
@@ -338,7 +364,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_Validate_InvalidSamplingPercent_ReturnsError()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -352,7 +379,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Contains("MaxSamplingPercent", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_Validate_NegativeSamplingPercent_ReturnsError()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -366,7 +394,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Contains("MaxSamplingPercent", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_Validate_InvalidMaxBytes_ReturnsError()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -380,7 +409,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Contains("MaxBytes", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_Validate_MissingFilePath_ReturnsError()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -395,7 +425,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Contains("FilePath", errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_GetEffectiveSamplingRate_WithoutIncident_ReturnsCapped()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -408,7 +439,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(0.25, rate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_GetEffectiveSamplingRate_WithIncidentOverride_ReturnsRequested()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -422,7 +454,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(0.5, rate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeTelemetryOptions_GetEffectiveSamplingRate_WithIncidentOverride_CapsAtOne()
|
||||
{
|
||||
var options = new SealedModeTelemetryOptions
|
||||
@@ -436,7 +469,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(1.0, rate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeExporterConfig_PropertiesAreSet()
|
||||
{
|
||||
var config = new SealedModeExporterConfig
|
||||
@@ -453,7 +487,8 @@ public sealed class SealedModeTelemetryServiceTests : IDisposable
|
||||
Assert.Equal(3, config.MaxRotatedFiles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeStateChangedEventArgs_PropertiesAreSet()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\StellaOps.Telemetry.Core\\StellaOps.Telemetry.Core.csproj" />
|
||||
<ProjectReference Include="..\\..\\..\\AirGap\\StellaOps.AirGap.Policy\\StellaOps.AirGap.Policy\\StellaOps.AirGap.Policy.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,14 +5,16 @@ namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class TelemetryContextAccessorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_StartsNull()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_CanBeSetAndRead()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -23,7 +25,8 @@ public sealed class TelemetryContextAccessorTests
|
||||
Assert.Same(context, accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_CanBeCleared()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -34,7 +37,8 @@ public sealed class TelemetryContextAccessorTests
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateScope_SetsContextForDuration()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -46,7 +50,8 @@ public sealed class TelemetryContextAccessorTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateScope_RestoresPreviousContextOnDispose()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -63,7 +68,8 @@ public sealed class TelemetryContextAccessorTests
|
||||
Assert.Same(originalContext, accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateScope_RestoresNull_WhenNoPreviousContext()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -72,12 +78,14 @@ public sealed class TelemetryContextAccessorTests
|
||||
using (accessor.CreateScope(scopeContext))
|
||||
{
|
||||
Assert.Same(scopeContext, accessor.Context);
|
||||
using StellaOps.TestKit;
|
||||
}
|
||||
|
||||
Assert.Null(accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_FlowsAcrossAsyncBoundaries()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
@@ -89,7 +97,8 @@ public sealed class TelemetryContextAccessorTests
|
||||
Assert.Same(context, accessor.Context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Context_IsIsolatedBetweenAsyncContexts()
|
||||
{
|
||||
var accessor = new TelemetryContextAccessor();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class TelemetryContextTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_Clone_CopiesAllFields()
|
||||
{
|
||||
var context = new TelemetryContext
|
||||
@@ -24,7 +27,8 @@ public sealed class TelemetryContextTests
|
||||
Assert.Equal(context.CorrelationId, clone.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_Clone_IsIndependent()
|
||||
{
|
||||
var context = new TelemetryContext
|
||||
@@ -39,35 +43,40 @@ public sealed class TelemetryContextTests
|
||||
Assert.Equal("different-tenant", clone.TenantId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsInitialized_ReturnsTrueWhenTenantIdSet()
|
||||
{
|
||||
var context = new TelemetryContext { TenantId = "tenant-123" };
|
||||
Assert.True(context.IsInitialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsInitialized_ReturnsTrueWhenActorSet()
|
||||
{
|
||||
var context = new TelemetryContext { Actor = "user@example.com" };
|
||||
Assert.True(context.IsInitialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsInitialized_ReturnsTrueWhenCorrelationIdSet()
|
||||
{
|
||||
var context = new TelemetryContext { CorrelationId = "corr-789" };
|
||||
Assert.True(context.IsInitialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsInitialized_ReturnsFalseWhenEmpty()
|
||||
{
|
||||
var context = new TelemetryContext();
|
||||
Assert.False(context.IsInitialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TraceId_ReturnsActivityTraceId_WhenActivityExists()
|
||||
{
|
||||
using var activity = new Activity("test-operation");
|
||||
@@ -78,7 +87,8 @@ public sealed class TelemetryContextTests
|
||||
Assert.Equal(activity.TraceId.ToString(), context.TraceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TraceId_ReturnsEmpty_WhenNoActivity()
|
||||
{
|
||||
Activity.Current = null;
|
||||
|
||||
@@ -5,11 +5,14 @@ using StellaOps.AirGap.Policy;
|
||||
using StellaOps.Telemetry.Core;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class TelemetryExporterGuardTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AllowsExporterWhenPolicyMissing()
|
||||
{
|
||||
var loggerFactory = CreateLoggerFactory();
|
||||
@@ -32,7 +35,8 @@ public sealed class TelemetryExporterGuardTests
|
||||
Assert.Null(decision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BlocksRemoteEndpointWhenSealed()
|
||||
{
|
||||
var policyOptions = new EgressPolicyOptions
|
||||
|
||||
@@ -8,7 +8,8 @@ using StellaOps.Telemetry.Core;
|
||||
|
||||
public class TelemetryPropagationHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Handler_Forwards_Context_Headers()
|
||||
{
|
||||
var options = Options.Create(new StellaOpsTelemetryOptions());
|
||||
@@ -36,7 +37,8 @@ public class TelemetryPropagationHandlerTests
|
||||
Assert.Equal("rule-b", terminal.SeenHeaders[options.Value.Propagation.ImposedRuleHeader]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Handler_Propagates_Trace_When_Context_Missing()
|
||||
{
|
||||
var options = Options.Create(new StellaOpsTelemetryOptions());
|
||||
|
||||
@@ -6,7 +6,8 @@ using StellaOps.Telemetry.Core;
|
||||
|
||||
public class TelemetryPropagationMiddlewareTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Middleware_Populates_Accessor_And_Activity_Tags()
|
||||
{
|
||||
var options = Options.Create(new StellaOpsTelemetryOptions());
|
||||
@@ -17,6 +18,7 @@ public class TelemetryPropagationMiddlewareTests
|
||||
// Assert using HttpContext.Items (source of truth for propagation in tests)
|
||||
var ctx = context.Items[typeof(TelemetryContext)] as TelemetryContext
|
||||
?? context.Items["TelemetryContext"] as TelemetryContext;
|
||||
using StellaOps.TestKit;
|
||||
Assert.NotNull(ctx);
|
||||
Assert.Equal("tenant-a", ctx!.TenantId);
|
||||
Assert.Equal("service-x", ctx.Actor);
|
||||
|
||||
@@ -32,7 +32,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
|
||||
public void Dispose() => _listener.Dispose();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordSignalRendered_WithValidData_RecordsHistogram()
|
||||
{
|
||||
using var metrics = new TimeToFirstSignalMetrics();
|
||||
@@ -52,7 +53,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_cache_hit_total" && m.Value is long v && v == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordSignalRendered_ExceedsSlo_IncrementsBreachCounter()
|
||||
{
|
||||
var options = new TimeToFirstSignalOptions
|
||||
@@ -73,7 +75,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_slo_breach_total" && m.Value is long v && v == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordCacheHit_IncrementsCounter()
|
||||
{
|
||||
using var metrics = new TimeToFirstSignalMetrics();
|
||||
@@ -90,7 +93,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_cache_hit_total" && m.Value is long v && v == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordCacheMiss_IncrementsCounter()
|
||||
{
|
||||
using var metrics = new TimeToFirstSignalMetrics();
|
||||
@@ -107,7 +111,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_cache_miss_total" && m.Value is long v && v == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MeasureSignal_Scope_RecordsLatencyOnDispose()
|
||||
{
|
||||
using var metrics = new TimeToFirstSignalMetrics();
|
||||
@@ -125,7 +130,8 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_latency_seconds" && m.Value is double v && v >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MeasureSignal_Scope_RecordsFailureOnException()
|
||||
{
|
||||
using var metrics = new TimeToFirstSignalMetrics();
|
||||
@@ -140,13 +146,15 @@ public sealed class TimeToFirstSignalMetricsTests : IDisposable
|
||||
phase: TtfsPhase.Unknown))
|
||||
{
|
||||
throw new InvalidOperationException("boom");
|
||||
using StellaOps.TestKit;
|
||||
}
|
||||
}));
|
||||
|
||||
Assert.Contains(_measurements, m => m.Name == "ttfs_error_total" && m.Value is long v && v == 1 && m.HasTag("error_type", "exception"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Options_DefaultValues_MatchAdvisory()
|
||||
{
|
||||
var options = new TimeToFirstSignalOptions();
|
||||
|
||||
@@ -2,6 +2,8 @@ using System.Diagnostics.Metrics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Telemetry.Core.Triage;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
@@ -39,7 +41,8 @@ public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
|
||||
public void Dispose() => _listener.Dispose();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidenceBitset_From_ComputesScoreAndFlags()
|
||||
{
|
||||
var bitset = EvidenceBitset.From(reachability: true, callstack: false, provenance: true, vex: true);
|
||||
@@ -51,7 +54,8 @@ public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
Assert.Equal(3, bitset.CompletenessScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestEvent_Skeleton_RecordsDurationAndBudgetViolation()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(_ => { });
|
||||
@@ -73,7 +77,8 @@ public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
m.HasTag("phase", "skeleton"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestEvent_FirstEvidence_RecordsDurationAndEvidenceType()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(_ => { });
|
||||
@@ -99,7 +104,8 @@ public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
m.HasTag("phase", "first_evidence"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestEvent_FullEvidence_RecordsCompletenessAndEvidenceByType()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(_ => { });
|
||||
@@ -129,7 +135,8 @@ public sealed class TtfsIngestionServiceTests : IDisposable
|
||||
Assert.Contains("vex", evidenceByType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestEvent_DecisionRecorded_RecordsDecisionMetricsAndClickBudgetViolation()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(_ => { });
|
||||
|
||||
Reference in New Issue
Block a user