using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Concelier.Models; using Xunit; using StellaOps.TestKit; namespace StellaOps.Concelier.Models.Tests; public sealed class ProvenanceDiagnosticsTests { [Trait("Category", TestCategories.Unit)] [Fact] public void RecordMissing_AddsExpectedTagsAndDeduplicates() { ResetState(); var measurements = new List<(string Instrument, long Value, IReadOnlyDictionary Tags)>(); using var listener = CreateListener(measurements); var baseline = DateTimeOffset.UtcNow; ProvenanceDiagnostics.RecordMissing("source-A", "range:pkg", baseline, new[] { ProvenanceFieldMasks.VersionRanges }); ProvenanceDiagnostics.RecordMissing("source-A", "range:pkg", baseline.AddMinutes(5), new[] { ProvenanceFieldMasks.VersionRanges }); ProvenanceDiagnostics.RecordMissing("source-A", "reference:https://example", baseline.AddMinutes(10), new[] { ProvenanceFieldMasks.References }); listener.Dispose(); Assert.Equal(2, measurements.Count); var first = measurements[0]; Assert.Equal(1, first.Value); Assert.Equal("concelier.provenance.missing", first.Instrument); Assert.Equal("source-A", first.Tags["source"]); Assert.Equal("range:pkg", first.Tags["component"]); Assert.Equal("range", first.Tags["category"]); Assert.Equal("high", first.Tags["severity"]); Assert.Equal(ProvenanceFieldMasks.VersionRanges, first.Tags["fieldMask"]); var second = measurements[1]; Assert.Equal("concelier.provenance.missing", second.Instrument); Assert.Equal("reference", second.Tags["category"]); Assert.Equal("low", second.Tags["severity"]); Assert.Equal(ProvenanceFieldMasks.References, second.Tags["fieldMask"]); } [Trait("Category", TestCategories.Unit)] [Fact] public void ReportResumeWindow_ClearsTrackedEntries_WhenWindowBackfills() { ResetState(); var timestamp = DateTimeOffset.UtcNow; ProvenanceDiagnostics.RecordMissing("source-B", "package:lib", timestamp); var (recorded, earliest, syncRoot) = GetInternalState(); lock (syncRoot) { Assert.True(earliest.ContainsKey("source-B")); Assert.Contains(recorded, entry => entry.StartsWith("source-B|", StringComparison.OrdinalIgnoreCase)); } ProvenanceDiagnostics.ReportResumeWindow("source-B", timestamp.AddMinutes(-5), NullLogger.Instance); lock (syncRoot) { Assert.False(earliest.ContainsKey("source-B")); Assert.DoesNotContain(recorded, entry => entry.StartsWith("source-B|", StringComparison.OrdinalIgnoreCase)); } } [Trait("Category", TestCategories.Unit)] [Fact] public void ReportResumeWindow_RetainsEntries_WhenWindowTooRecent() { ResetState(); var timestamp = DateTimeOffset.UtcNow; ProvenanceDiagnostics.RecordMissing("source-C", "range:pkg", timestamp); ProvenanceDiagnostics.ReportResumeWindow("source-C", timestamp.AddMinutes(1), NullLogger.Instance); var (recorded, earliest, syncRoot) = GetInternalState(); lock (syncRoot) { Assert.True(earliest.ContainsKey("source-C")); Assert.Contains(recorded, entry => entry.StartsWith("source-C|", StringComparison.OrdinalIgnoreCase)); } } [Trait("Category", TestCategories.Unit)] [Fact] public void RecordRangePrimitive_EmitsCoverageMetric() { var range = new AffectedVersionRange( rangeKind: "evr", introducedVersion: "1:1.1.1n-0+deb11u2", fixedVersion: null, lastAffectedVersion: null, rangeExpression: null, provenance: new AdvisoryProvenance("source-D", "range", "pkg", DateTimeOffset.UtcNow), primitives: new RangePrimitives( SemVer: null, Nevra: null, Evr: new EvrPrimitive( new EvrComponent(1, "1.1.1n", "0+deb11u2"), null, null), VendorExtensions: new Dictionary { ["debian.release"] = "bullseye" })); var measurements = new List<(string Instrument, long Value, IReadOnlyDictionary Tags)>(); using var listener = CreateListener(measurements, "concelier.range.primitives"); ProvenanceDiagnostics.RecordRangePrimitive("source-D", range); listener.Dispose(); var record = Assert.Single(measurements); Assert.Equal("concelier.range.primitives", record.Instrument); Assert.Equal(1, record.Value); Assert.Equal("source-D", record.Tags["source"]); Assert.Equal("evr", record.Tags["rangeKind"]); Assert.Equal("evr", record.Tags["primitiveKinds"]); Assert.Equal("true", record.Tags["hasVendorExtensions"]); } private static MeterListener CreateListener( List<(string Instrument, long Value, IReadOnlyDictionary Tags)> measurements, params string[] instrumentNames) { var allowed = instrumentNames is { Length: > 0 } ? instrumentNames : new[] { "concelier.provenance.missing" }; var allowedSet = new HashSet(allowed, StringComparer.OrdinalIgnoreCase); var listener = new MeterListener { InstrumentPublished = (instrument, l) => { if (instrument.Meter.Name == "StellaOps.Concelier.Models.Provenance" && allowedSet.Contains(instrument.Name)) { l.EnableMeasurementEvents(instrument); } } }; listener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var tag in tags) { dict[tag.Key] = tag.Value; } measurements.Add((instrument.Name, measurement, dict)); }); listener.Start(); return listener; } private static void ResetState() { var (_, _, syncRoot) = GetInternalState(); lock (syncRoot) { var (recorded, earliest, _) = GetInternalState(); recorded.Clear(); earliest.Clear(); } } private static (HashSet Recorded, Dictionary Earliest, object SyncRoot) GetInternalState() { var type = typeof(ProvenanceDiagnostics); var recordedField = type.GetField("RecordedComponents", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("RecordedComponents not found"); var earliestField = type.GetField("EarliestMissing", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("EarliestMissing not found"); var syncField = type.GetField("SyncRoot", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("SyncRoot not found"); var recorded = (HashSet)recordedField.GetValue(null)!; var earliest = (Dictionary)earliestField.GetValue(null)!; var sync = syncField.GetValue(null)!; return (recorded, earliest, sync); } }