This commit is contained in:
StellaOps Bot
2025-12-09 00:20:52 +02:00
parent 3d01bf9edc
commit bc0762e97d
261 changed files with 14033 additions and 4427 deletions

View File

@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Concelier.Core.Diagnostics;
using StellaOps.Concelier.Core.Observations;
using StellaOps.Concelier.Models.Observations;
using StellaOps.Concelier.RawModels;
using Xunit;
namespace StellaOps.Concelier.Core.Tests.Diagnostics;
public sealed class VulnExplorerTelemetryTests
{
private static readonly AdvisoryObservationSource DefaultSource = new("ghsa", "stream", "https://example.test/api");
private static readonly AdvisoryObservationSignature DefaultSignature = new(false, null, null, null);
[Fact]
public async Task QueryAsync_RecordsIdentifierCollisionMetric()
{
var (listener, measurements) = CreateListener(
VulnExplorerTelemetry.MeterName,
"vuln.identifier_collisions_total");
var observations = new[]
{
CreateObservation(
"tenant-a:ghsa:1",
"tenant-a",
aliases: new[] { "CVE-2025-0001" }),
CreateObservation(
"tenant-a:osv:2",
"tenant-a",
aliases: new[] { "GHSA-aaaa-bbbb-cccc" })
};
var service = new AdvisoryObservationQueryService(new TestObservationLookup(observations));
await service.QueryAsync(new AdvisoryObservationQueryOptions("tenant-a"), CancellationToken.None);
listener.Dispose();
var collision = measurements.Single(m => m.Instrument == "vuln.identifier_collisions_total");
Assert.Equal(1, collision.Value);
Assert.Equal("tenant-a", collision.Tags.Single(t => t.Key == "tenant").Value);
}
[Fact]
public void RecordChunkRequest_EmitsCounterAndLatency()
{
var (listener, measurements) = CreateListener(
VulnExplorerTelemetry.MeterName,
"vuln.chunk_requests_total",
"vuln.chunk_latency_ms");
VulnExplorerTelemetry.RecordChunkRequest("tenant-a", "ok", cacheHit: true, chunkCount: 3, latencyMs: 42.5);
listener.Dispose();
Assert.Equal(1, measurements.Single(m => m.Instrument == "vuln.chunk_requests_total").Value);
Assert.Equal(42.5, measurements.Single(m => m.Instrument == "vuln.chunk_latency_ms").Value);
}
[Fact]
public void RecordWithdrawnStatement_EmitsCounter()
{
var (listener, measurements) = CreateListener(
VulnExplorerTelemetry.MeterName,
"vuln.withdrawn_statements_total");
VulnExplorerTelemetry.RecordWithdrawnStatement("tenant-a", "nvd");
listener.Dispose();
var withdrawn = measurements.Single(m => m.Instrument == "vuln.withdrawn_statements_total");
Assert.Equal(1, withdrawn.Value);
Assert.Equal("tenant-a", withdrawn.Tags.Single(t => t.Key == "tenant").Value);
Assert.Equal("nvd", withdrawn.Tags.Single(t => t.Key == "source").Value);
}
private static AdvisoryObservation CreateObservation(
string observationId,
string tenant,
IEnumerable<string>? aliases = null)
{
var upstream = new AdvisoryObservationUpstream(
upstreamId: $"upstream-{observationId}",
documentVersion: null,
fetchedAt: DateTimeOffset.UtcNow,
receivedAt: DateTimeOffset.UtcNow,
contentHash: "sha256:d41d8cd98f00b204e9800998ecf8427e",
signature: DefaultSignature);
var content = new AdvisoryObservationContent(
"json",
"1.0",
new JsonObject());
var aliasArray = aliases?.ToImmutableArray() ?? ImmutableArray<string>.Empty;
var linkset = new AdvisoryObservationLinkset(
aliasArray,
Enumerable.Empty<string>(),
Enumerable.Empty<string>(),
Enumerable.Empty<AdvisoryObservationReference>());
var rawLinkset = new RawLinkset
{
Aliases = aliasArray
};
return new AdvisoryObservation(
observationId,
tenant,
DefaultSource,
upstream,
content,
linkset,
rawLinkset,
DateTimeOffset.UtcNow);
}
private static (MeterListener Listener, List<MeasurementRecord> Measurements) CreateListener(
string meterName,
params string[] instruments)
{
var measurements = new List<MeasurementRecord>();
var instrumentSet = instruments.ToHashSet(StringComparer.Ordinal);
var listener = new MeterListener
{
InstrumentPublished = (instrument, meterListener) =>
{
if (string.Equals(instrument.Meter.Name, meterName, StringComparison.Ordinal) &&
instrumentSet.Contains(instrument.Name))
{
meterListener.EnableMeasurementEvents(instrument);
}
}
};
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
if (instrumentSet.Contains(instrument.Name))
{
measurements.Add(new MeasurementRecord(instrument.Name, measurement, CopyTags(tags)));
}
});
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
{
if (instrumentSet.Contains(instrument.Name))
{
measurements.Add(new MeasurementRecord(instrument.Name, measurement, CopyTags(tags)));
}
});
listener.Start();
return (listener, measurements);
}
private static IReadOnlyList<KeyValuePair<string, object?>> CopyTags(ReadOnlySpan<KeyValuePair<string, object?>> tags)
{
var list = new List<KeyValuePair<string, object?>>(tags.Length);
foreach (var tag in tags)
{
list.Add(tag);
}
return list;
}
private sealed record MeasurementRecord(string Instrument, double Value, IReadOnlyList<KeyValuePair<string, object?>> Tags);
private sealed class TestObservationLookup : IAdvisoryObservationLookup
{
private readonly IReadOnlyList<AdvisoryObservation> _observations;
public TestObservationLookup(IReadOnlyList<AdvisoryObservation> observations)
{
_observations = observations;
}
public ValueTask<IReadOnlyList<AdvisoryObservation>> ListByTenantAsync(string tenant, CancellationToken cancellationToken)
{
var matches = _observations
.Where(o => string.Equals(o.Tenant, tenant, StringComparison.OrdinalIgnoreCase))
.ToList();
return ValueTask.FromResult<IReadOnlyList<AdvisoryObservation>>(matches);
}
public ValueTask<IReadOnlyList<AdvisoryObservation>> FindByFiltersAsync(
string tenant,
IReadOnlyCollection<string> observationIds,
IReadOnlyCollection<string> aliases,
IReadOnlyCollection<string> purls,
IReadOnlyCollection<string> cpes,
AdvisoryObservationCursor? cursor,
int limit,
CancellationToken cancellationToken)
{
var matches = _observations
.Where(o => string.Equals(o.Tenant, tenant, StringComparison.OrdinalIgnoreCase))
.Take(limit)
.ToList();
return ValueTask.FromResult<IReadOnlyList<AdvisoryObservation>>(matches);
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Text.Json;
using StellaOps.Concelier.Core.Diagnostics;
using StellaOps.Concelier.Core.Linksets;
using Xunit;
namespace StellaOps.Concelier.WebService.Tests;
public sealed class VulnExplorerTelemetryTests : IDisposable
{
private readonly MeterListener _listener;
private readonly List<(string Name, double Value, KeyValuePair<string, object?>[] Tags)> _histogramMeasurements = new();
private readonly List<(string Name, long Value, KeyValuePair<string, object?>[] Tags)> _counterMeasurements = new();
public VulnExplorerTelemetryTests()
{
_listener = new MeterListener
{
InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name == VulnExplorerTelemetry.MeterName)
{
listener.EnableMeasurementEvents(instrument);
}
}
};
_listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
if (instrument.Meter.Name == VulnExplorerTelemetry.MeterName)
{
_counterMeasurements.Add((instrument.Name, measurement, tags.ToArray()));
}
});
_listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
{
if (instrument.Meter.Name == VulnExplorerTelemetry.MeterName)
{
_histogramMeasurements.Add((instrument.Name, measurement, tags.ToArray()));
}
});
_listener.Start();
}
[Fact]
public void CountAliasCollisions_FiltersAliasConflicts()
{
var conflicts = new List<AdvisoryLinksetConflict>
{
new("aliases", "alias-inconsistency", Array.Empty<string>()),
new("ranges", "range-divergence", Array.Empty<string>()),
new("alias-field", "ALIAS-INCONSISTENCY", Array.Empty<string>())
};
var count = VulnExplorerTelemetry.CountAliasCollisions(conflicts);
Assert.Equal(2, count);
}
[Fact]
public void IsWithdrawn_DetectsWithdrawnFlagsAndTimestamps()
{
using var json = JsonDocument.Parse("{\"withdrawn\":true,\"withdrawn_at\":\"2024-10-10T00:00:00Z\"}");
Assert.True(VulnExplorerTelemetry.IsWithdrawn(json.RootElement));
}
[Fact]
public void RecordChunkLatency_EmitsHistogramMeasurement()
{
VulnExplorerTelemetry.RecordChunkLatency("tenant-a", "vendor-a", TimeSpan.FromMilliseconds(42));
var measurement = Assert.Single(_histogramMeasurements);
Assert.Equal("vuln.chunk_latency_ms", measurement.Name);
Assert.Equal(42, measurement.Value);
}
[Fact]
public void RecordWithdrawnStatement_EmitsCounter()
{
VulnExplorerTelemetry.RecordWithdrawnStatement("tenant-b", "vendor-b");
var measurement = Assert.Single(_counterMeasurements);
Assert.Equal("vuln.withdrawn_statements_total", measurement.Name);
Assert.Equal(1, measurement.Value);
}
public void Dispose()
{
_listener.Dispose();
}
}

View File

@@ -75,16 +75,7 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
public Task InitializeAsync()
{
PrepareMongoEnvironment();
if (TryStartExternalMongo(out var externalConnectionString) && !string.IsNullOrWhiteSpace(externalConnectionString))
{
_factory = new ConcelierApplicationFactory(externalConnectionString);
}
else
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
_factory = new ConcelierApplicationFactory(_runner.ConnectionString);
}
_factory = new ConcelierApplicationFactory(string.Empty);
WarmupFactory(_factory);
return Task.CompletedTask;
}
@@ -92,30 +83,6 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
public Task DisposeAsync()
{
_factory.Dispose();
if (_externalMongo is not null)
{
try
{
if (!_externalMongo.HasExited)
{
_externalMongo.Kill(true);
_externalMongo.WaitForExit(2000);
}
}
catch
{
// ignore cleanup errors in tests
}
if (!string.IsNullOrEmpty(_externalMongoDataPath) && Directory.Exists(_externalMongoDataPath))
{
try { Directory.Delete(_externalMongoDataPath, recursive: true); } catch { /* ignore */ }
}
}
else
{
_runner.Dispose();
}
return Task.CompletedTask;
}
@@ -141,12 +108,12 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
var healthPayload = await healthResponse.Content.ReadFromJsonAsync<HealthPayload>();
Assert.NotNull(healthPayload);
Assert.Equal("healthy", healthPayload!.Status);
Assert.Equal("mongo", healthPayload.Storage.Driver);
Assert.Equal("postgres", healthPayload.Storage.Backend);
var readyPayload = await readyResponse.Content.ReadFromJsonAsync<ReadyPayload>();
Assert.NotNull(readyPayload);
Assert.Equal("ready", readyPayload!.Status);
Assert.Equal("ready", readyPayload.Mongo.Status);
Assert.True(readyPayload!.Status is "ready" or "degraded");
Assert.Equal("postgres", readyPayload.Storage.Backend);
}
[Fact]
@@ -2019,9 +1986,10 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
private sealed class ConcelierApplicationFactory : WebApplicationFactory<Program>
{
private readonly string _connectionString;
private readonly string? _previousDsn;
private readonly string? _previousDriver;
private readonly string? _previousTimeout;
private readonly string? _previousPgDsn;
private readonly string? _previousPgEnabled;
private readonly string? _previousPgTimeout;
private readonly string? _previousPgSchema;
private readonly string? _previousTelemetryEnabled;
private readonly string? _previousTelemetryLogging;
private readonly string? _previousTelemetryTracing;
@@ -2035,11 +2003,15 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Action<ConcelierOptions.AuthorityOptions>? authorityConfigure = null,
IDictionary<string, string?>? environmentOverrides = null)
{
_connectionString = connectionString;
var defaultPostgresDsn = "Host=localhost;Port=5432;Database=concelier_test;Username=postgres;Password=postgres";
_connectionString = string.IsNullOrWhiteSpace(connectionString) || connectionString.StartsWith("mongodb://", StringComparison.OrdinalIgnoreCase)
? defaultPostgresDsn
: connectionString;
_authorityConfigure = authorityConfigure;
_previousDsn = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__DSN");
_previousDriver = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__DRIVER");
_previousTimeout = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__COMMANDTIMEOUTSECONDS");
_previousPgDsn = Environment.GetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__CONNECTIONSTRING");
_previousPgEnabled = Environment.GetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__ENABLED");
_previousPgTimeout = Environment.GetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__COMMANDTIMEOUTSECONDS");
_previousPgSchema = Environment.GetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__SCHEMANAME");
_previousTelemetryEnabled = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLED");
_previousTelemetryLogging = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING");
_previousTelemetryTracing = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING");
@@ -2055,13 +2027,15 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", merged);
}
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DSN", connectionString);
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DRIVER", "mongo");
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__COMMANDTIMEOUTSECONDS", "30");
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__CONNECTIONSTRING", _connectionString);
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__ENABLED", "true");
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__COMMANDTIMEOUTSECONDS", "30");
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__SCHEMANAME", "vuln");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLED", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLEMETRICS", "false");
Environment.SetEnvironmentVariable("CONCELIER_SKIP_OPTIONS_VALIDATION", "1");
const string EvidenceRootKey = "CONCELIER_EVIDENCE__ROOT";
var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "..", ".."));
_additionalPreviousEnvironment[EvidenceRootKey] = Environment.GetEnvironmentVariable(EvidenceRootKey);
@@ -2176,9 +2150,11 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DSN", _previousDsn);
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DRIVER", _previousDriver);
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__COMMANDTIMEOUTSECONDS", _previousTimeout);
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__CONNECTIONSTRING", _previousPgDsn);
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__ENABLED", _previousPgEnabled);
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__COMMANDTIMEOUTSECONDS", _previousPgTimeout);
Environment.SetEnvironmentVariable("CONCELIER_POSTGRESSTORAGE__SCHEMANAME", _previousPgSchema);
Environment.SetEnvironmentVariable("CONCELIER_SKIP_OPTIONS_VALIDATION", null);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLED", _previousTelemetryEnabled);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING", _previousTelemetryLogging);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING", _previousTelemetryTracing);
@@ -2470,13 +2446,11 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
private sealed record HealthPayload(string Status, DateTimeOffset StartedAt, double UptimeSeconds, StoragePayload Storage, TelemetryPayload Telemetry);
private sealed record StoragePayload(string Driver, bool Completed, DateTimeOffset? CompletedAt, double? DurationMs);
private sealed record StoragePayload(string Backend, bool Ready, DateTimeOffset? CheckedAt, double? LatencyMs, string? Error);
private sealed record TelemetryPayload(bool Enabled, bool Tracing, bool Metrics, bool Logging);
private sealed record ReadyPayload(string Status, DateTimeOffset StartedAt, double UptimeSeconds, ReadyMongoPayload Mongo);
private sealed record ReadyMongoPayload(string Status, double? LatencyMs, DateTimeOffset? CheckedAt, string? Error);
private sealed record ReadyPayload(string Status, DateTimeOffset StartedAt, double UptimeSeconds, StoragePayload Storage);
private sealed record JobDefinitionPayload(string Kind, bool Enabled, string? CronExpression, TimeSpan Timeout, TimeSpan LeaseDuration, JobRunPayload? LastRun);