Files
git.stella-ops.org/src/Concelier/__Tests/StellaOps.Concelier.WebService.Tests/WebServiceEndpointsTests.cs
master b059bc7675
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat(metrics): Add new histograms for chunk latency, results, and sources in AdvisoryAiMetrics
feat(telemetry): Record chunk latency, result count, and source count in AdvisoryAiTelemetry

fix(endpoint): Include telemetry source count in advisory chunks endpoint response

test(metrics): Enhance WebServiceEndpointsTests to validate new metrics for chunk latency, results, and sources

refactor(tests): Update test utilities for Deno language analyzer tests

chore(tests): Add performance tests for AdvisoryGuardrail with scenarios and blocked phrases

docs: Archive Sprint 137 design document for scanner and surface enhancements
2025-11-10 22:26:43 +02:00

2333 lines
103 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Mongo2Go;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Driver;
using StellaOps.Concelier.Core.Events;
using StellaOps.Concelier.Core.Jobs;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Merge.Services;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Observations;
using StellaOps.Concelier.Core.Raw;
using StellaOps.Concelier.WebService.Jobs;
using StellaOps.Concelier.WebService.Options;
using StellaOps.Concelier.WebService.Contracts;
using Xunit.Sdk;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.Client;
using Xunit;
using Xunit.Abstractions;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using StellaOps.Concelier.WebService.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.WebService.Tests;
public sealed class WebServiceEndpointsTests : IAsyncLifetime
{
private const string TestAuthorityIssuer = "https://authority.example";
private const string TestAuthorityAudience = "api://concelier";
private const string TestSigningSecret = "0123456789ABCDEF0123456789ABCDEF";
private static readonly SymmetricSecurityKey TestSigningKey = new(Encoding.UTF8.GetBytes(TestSigningSecret));
private readonly ITestOutputHelper _output;
private MongoDbRunner _runner = null!;
private ConcelierApplicationFactory _factory = null!;
public WebServiceEndpointsTests(ITestOutputHelper output)
{
_output = output;
}
public Task InitializeAsync()
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
_factory = new ConcelierApplicationFactory(_runner.ConnectionString);
WarmupFactory(_factory);
return Task.CompletedTask;
}
public Task DisposeAsync()
{
_factory.Dispose();
_runner.Dispose();
return Task.CompletedTask;
}
[Fact]
public async Task HealthAndReadyEndpointsRespond()
{
using var client = _factory.CreateClient();
var healthResponse = await client.GetAsync("/health");
if (!healthResponse.IsSuccessStatusCode)
{
var body = await healthResponse.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"/health failed: {(int)healthResponse.StatusCode} {body}");
}
var readyResponse = await client.GetAsync("/ready");
if (!readyResponse.IsSuccessStatusCode)
{
var body = await readyResponse.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"/ready failed: {(int)readyResponse.StatusCode} {body}");
}
var healthPayload = await healthResponse.Content.ReadFromJsonAsync<HealthPayload>();
Assert.NotNull(healthPayload);
Assert.Equal("healthy", healthPayload!.Status);
Assert.Equal("mongo", healthPayload.Storage.Driver);
var readyPayload = await readyResponse.Content.ReadFromJsonAsync<ReadyPayload>();
Assert.NotNull(readyPayload);
Assert.Equal("ready", readyPayload!.Status);
Assert.Equal("ready", readyPayload.Mongo.Status);
}
[Fact]
public async Task ObservationsEndpoint_ReturnsTenantScopedResults()
{
await SeedObservationDocumentsAsync(BuildSampleObservationDocuments());
using var client = _factory.CreateClient();
var response = await client.GetAsync("/concelier/observations?tenant=tenant-a&alias=CVE-2025-0001");
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new XunitException($"/concelier/observations failed: {(int)response.StatusCode} {body}");
}
using var document = await response.Content.ReadFromJsonAsync<JsonDocument>();
Assert.NotNull(document);
var root = document!.RootElement;
var observations = root.GetProperty("observations").EnumerateArray().ToArray();
Assert.Equal(2, observations.Length);
Assert.Equal("tenant-a:ghsa:beta:1", observations[0].GetProperty("observationId").GetString());
Assert.Equal("tenant-a:nvd:alpha:1", observations[1].GetProperty("observationId").GetString());
var linkset = root.GetProperty("linkset");
Assert.Equal(new[] { "cve-2025-0001", "ghsa-2025-xyz" }, linkset.GetProperty("aliases").EnumerateArray().Select(x => x.GetString()).ToArray());
Assert.Equal(new[] { "pkg:npm/demo@1.0.0", "pkg:npm/demo@1.1.0" }, linkset.GetProperty("purls").EnumerateArray().Select(x => x.GetString()).ToArray());
Assert.Equal(new[] { "cpe:/a:vendor:product:1.0", "cpe:/a:vendor:product:1.1" }, linkset.GetProperty("cpes").EnumerateArray().Select(x => x.GetString()).ToArray());
var references = linkset.GetProperty("references").EnumerateArray().ToArray();
Assert.Equal(2, references.Length);
Assert.Equal("advisory", references[0].GetProperty("type").GetString());
Assert.Equal("https://example.test/advisory-1", references[0].GetProperty("url").GetString());
Assert.Equal("patch", references[1].GetProperty("type").GetString());
Assert.False(root.GetProperty("hasMore").GetBoolean());
Assert.True(root.GetProperty("nextCursor").ValueKind == JsonValueKind.Null);
}
[Fact]
public async Task ObservationsEndpoint_AppliesObservationIdFilter()
{
await SeedObservationDocumentsAsync(BuildSampleObservationDocuments());
using var client = _factory.CreateClient();
var observationId = Uri.EscapeDataString("tenant-a:ghsa:beta:1");
var response = await client.GetAsync($"/concelier/observations?tenant=tenant-a&observationId={observationId}&cpe=cpe:/a:vendor:product:1.1");
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new XunitException($"/concelier/observations filter failed: {(int)response.StatusCode} {body}");
}
using var document = await response.Content.ReadFromJsonAsync<JsonDocument>();
Assert.NotNull(document);
var root = document!.RootElement;
var observations = root.GetProperty("observations").EnumerateArray().ToArray();
Assert.Single(observations);
Assert.Equal("tenant-a:ghsa:beta:1", observations[0].GetProperty("observationId").GetString());
Assert.Equal(new[] { "pkg:npm/demo@1.1.0" }, observations[0].GetProperty("linkset").GetProperty("purls").EnumerateArray().Select(x => x.GetString()).ToArray());
Assert.Equal(new[] { "cpe:/a:vendor:product:1.1" }, observations[0].GetProperty("linkset").GetProperty("cpes").EnumerateArray().Select(x => x.GetString()).ToArray());
Assert.False(root.GetProperty("hasMore").GetBoolean());
Assert.True(root.GetProperty("nextCursor").ValueKind == JsonValueKind.Null);
}
[Fact]
public async Task ObservationsEndpoint_SupportsPagination()
{
await SeedObservationDocumentsAsync(BuildSampleObservationDocuments());
using var client = _factory.CreateClient();
var firstResponse = await client.GetAsync("/concelier/observations?tenant=tenant-a&limit=1");
firstResponse.EnsureSuccessStatusCode();
using var firstDocument = await firstResponse.Content.ReadFromJsonAsync<JsonDocument>();
Assert.NotNull(firstDocument);
var firstRoot = firstDocument!.RootElement;
var firstObservations = firstRoot.GetProperty("observations").EnumerateArray().ToArray();
Assert.Single(firstObservations);
var nextCursor = firstRoot.GetProperty("nextCursor").GetString();
Assert.True(firstRoot.GetProperty("hasMore").GetBoolean());
Assert.False(string.IsNullOrWhiteSpace(nextCursor));
var secondResponse = await client.GetAsync($"/concelier/observations?tenant=tenant-a&limit=2&cursor={Uri.EscapeDataString(nextCursor!)}");
secondResponse.EnsureSuccessStatusCode();
using var secondDocument = await secondResponse.Content.ReadFromJsonAsync<JsonDocument>();
Assert.NotNull(secondDocument);
var secondRoot = secondDocument!.RootElement;
var secondObservations = secondRoot.GetProperty("observations").EnumerateArray().ToArray();
Assert.Single(secondObservations);
Assert.False(secondRoot.GetProperty("hasMore").GetBoolean());
Assert.True(secondRoot.GetProperty("nextCursor").ValueKind == JsonValueKind.Null);
Assert.Equal("tenant-a:nvd:alpha:1", secondObservations[0].GetProperty("observationId").GetString());
}
[Fact]
public async Task ObservationsEndpoint_ReturnsBadRequestWhenTenantMissing()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync("/concelier/observations");
var body = await response.Content.ReadAsStringAsync();
Assert.True(response.StatusCode == HttpStatusCode.BadRequest, $"Expected 400 but got {(int)response.StatusCode}: {body}");
}
[Fact]
public async Task AdvisoryChunksEndpoint_ReturnsParagraphAnchors()
{
var newestRaw = BsonDocument.Parse(
"""
{
"summary": {
"intro": "This is a deterministic summary paragraph describing CVE-2025-0001 with remediation context for Advisory AI consumers."
},
"details": [
"Long-form remediation guidance that exceeds the minimum length threshold and mentions affected packages.",
{
"body": "Nested context that Advisory AI can cite when rendering downstream explanations."
}
]
}
""");
var olderRaw = BsonDocument.Parse(
"""
{
"summary": {
"intro": "Older paragraph that should be visible when no section filter applies."
}
}
""");
var newerCreatedAt = new DateTime(2025, 1, 7, 0, 0, 0, DateTimeKind.Utc);
var olderCreatedAt = new DateTime(2025, 1, 5, 0, 0, 0, DateTimeKind.Utc);
var newerHash = ComputeContentHash(newestRaw);
var olderHash = ComputeContentHash(olderRaw);
var documents = new[]
{
CreateChunkObservationDocument(
id: "tenant-a:chunk:newest",
tenant: "tenant-a",
createdAt: newerCreatedAt,
alias: "cve-2025-0001",
rawDocument: newestRaw),
CreateChunkObservationDocument(
id: "tenant-a:chunk:older",
tenant: "tenant-a",
createdAt: olderCreatedAt,
alias: "cve-2025-0001",
rawDocument: olderRaw)
};
await SeedObservationDocumentsAsync(documents);
await SeedAdvisoryRawDocumentsAsync(
CreateAdvisoryRawDocument("tenant-a", "nvd", "tenant-a:chunk:newest", newerHash, newestRaw.DeepClone().AsBsonDocument),
CreateAdvisoryRawDocument("tenant-a", "nvd", "tenant-a:chunk:older", olderHash, olderRaw.DeepClone().AsBsonDocument));
using var client = _factory.CreateClient();
var response = await client.GetAsync("/advisories/cve-2025-0001/chunks?tenant=tenant-a&section=summary&format=csaf");
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadAsStringAsync();
using var document = JsonDocument.Parse(payload);
var root = document.RootElement;
Assert.Equal("cve-2025-0001", root.GetProperty("advisoryKey").GetString());
Assert.Equal(1, root.GetProperty("total").GetInt32());
Assert.False(root.GetProperty("truncated").GetBoolean());
var chunk = Assert.Single(root.GetProperty("chunks").EnumerateArray());
Assert.Equal("summary", chunk.GetProperty("section").GetString());
Assert.Equal("summary.intro", chunk.GetProperty("paragraphId").GetString());
var text = chunk.GetProperty("text").GetString();
Assert.False(string.IsNullOrWhiteSpace(text));
Assert.Contains("deterministic summary paragraph", text, StringComparison.OrdinalIgnoreCase);
var metadata = chunk.GetProperty("metadata");
Assert.Equal("summary.intro", metadata.GetProperty("path").GetString());
Assert.Equal("csaf", metadata.GetProperty("format").GetString());
var sources = root.GetProperty("sources").EnumerateArray().ToArray();
Assert.Equal(2, sources.Length);
Assert.Equal("tenant-a:chunk:newest", sources[0].GetProperty("observationId").GetString());
Assert.Equal("tenant-a:chunk:older", sources[1].GetProperty("observationId").GetString());
Assert.All(
sources,
source => Assert.True(string.Equals("csaf", source.GetProperty("format").GetString(), StringComparison.OrdinalIgnoreCase)));
}
[Fact]
public async Task AdvisoryChunksEndpoint_ReturnsNotFoundWhenAdvisoryMissing()
{
await SeedObservationDocumentsAsync(BuildSampleObservationDocuments());
using var client = _factory.CreateClient();
var response = await client.GetAsync("/advisories/cve-2099-9999/chunks?tenant=tenant-a");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
var payload = await response.Content.ReadAsStringAsync();
using var document = JsonDocument.Parse(payload);
var root = document.RootElement;
Assert.Equal("https://stellaops.org/problems/not-found", root.GetProperty("type").GetString());
Assert.Equal("Advisory not found", root.GetProperty("title").GetString());
Assert.Contains("cve-2099-9999", root.GetProperty("detail").GetString(), StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task AdvisoryIngestEndpoint_PersistsDocumentAndSupportsReadback()
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-ingest");
const string upstreamId = "GHSA-INGEST-0001";
var ingestRequest = BuildAdvisoryIngestRequest(
contentHash: null,
upstreamId: upstreamId);
var ingestResponse = await client.PostAsJsonAsync("/ingest/advisory", ingestRequest);
if (ingestResponse.StatusCode != HttpStatusCode.Created)
{
WriteProgramLogs();
}
Assert.Equal(HttpStatusCode.Created, ingestResponse.StatusCode);
var ingestPayload = await ingestResponse.Content.ReadFromJsonAsync<AdvisoryIngestResponse>();
Assert.NotNull(ingestPayload);
Assert.True(ingestPayload!.Inserted);
Assert.False(string.IsNullOrWhiteSpace(ingestPayload.Id));
Assert.Equal("tenant-ingest", ingestPayload.Tenant);
Assert.Equal(ComputeDeterministicContentHash(upstreamId), ingestPayload.ContentHash);
Assert.NotNull(ingestResponse.Headers.Location);
var locationValue = ingestResponse.Headers.Location!.ToString();
Assert.False(string.IsNullOrWhiteSpace(locationValue));
var lastSlashIndex = locationValue.LastIndexOf('/');
var idSegment = lastSlashIndex >= 0
? locationValue[(lastSlashIndex + 1)..]
: locationValue;
var decodedSegment = Uri.UnescapeDataString(idSegment);
Assert.Equal(ingestPayload.Id, decodedSegment);
var duplicateResponse = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest(
contentHash: null,
upstreamId: upstreamId));
Assert.Equal(HttpStatusCode.OK, duplicateResponse.StatusCode);
var duplicatePayload = await duplicateResponse.Content.ReadFromJsonAsync<AdvisoryIngestResponse>();
Assert.NotNull(duplicatePayload);
Assert.False(duplicatePayload!.Inserted);
using (var getRequest = new HttpRequestMessage(HttpMethod.Get, $"/advisories/raw/{ingestPayload.Id}"))
{
getRequest.Headers.Add("X-Stella-Tenant", "tenant-ingest");
var getResponse = await client.SendAsync(getRequest);
getResponse.EnsureSuccessStatusCode();
var record = await getResponse.Content.ReadFromJsonAsync<AdvisoryRawRecordResponse>();
Assert.NotNull(record);
Assert.Equal(ingestPayload.Id, record!.Id);
Assert.Equal("tenant-ingest", record.Tenant);
Assert.Equal(ComputeDeterministicContentHash(upstreamId), record.Document.Upstream.ContentHash);
}
using (var listRequest = new HttpRequestMessage(HttpMethod.Get, "/advisories/raw?limit=10"))
{
listRequest.Headers.Add("X-Stella-Tenant", "tenant-ingest");
var listResponse = await client.SendAsync(listRequest);
listResponse.EnsureSuccessStatusCode();
var listPayload = await listResponse.Content.ReadFromJsonAsync<AdvisoryRawListResponse>();
Assert.NotNull(listPayload);
var record = Assert.Single(listPayload!.Records);
Assert.Equal(ingestPayload.Id, record.Id);
}
}
[Fact]
public async Task AocVerifyEndpoint_ReturnsSummaryForTenant()
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-verify");
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest(
contentHash: "sha256:verify-1",
upstreamId: "GHSA-VERIFY-001"));
var verifyResponse = await client.PostAsJsonAsync("/aoc/verify", new AocVerifyRequest(null, null, null, null, null));
verifyResponse.EnsureSuccessStatusCode();
var verifyPayload = await verifyResponse.Content.ReadFromJsonAsync<AocVerifyResponse>();
Assert.NotNull(verifyPayload);
Assert.Equal("tenant-verify", verifyPayload!.Tenant);
Assert.True(verifyPayload.Checked.Advisories >= 1);
Assert.Equal(0, verifyPayload.Checked.Vex);
Assert.True(verifyPayload.Metrics.IngestionWriteTotal >= verifyPayload.Checked.Advisories);
Assert.Empty(verifyPayload.Violations);
Assert.False(verifyPayload.Truncated);
}
[Fact]
public async Task AocVerifyEndpoint_ReturnsViolationsForGuardFailures()
{
await SeedAdvisoryRawDocumentsAsync(
CreateAdvisoryRawDocument(
tenant: "tenant-verify-violations",
vendor: "osv",
upstreamId: "GHSA-VERIFY-ERR",
contentHash: "sha256:verify-err",
raw: new BsonDocument
{
{ "id", "GHSA-VERIFY-ERR" },
{ "severity", "critical" }
}));
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-verify-violations");
var verifyResponse = await client.PostAsJsonAsync("/aoc/verify", new AocVerifyRequest(null, null, null, null, null));
verifyResponse.EnsureSuccessStatusCode();
var verifyPayload = await verifyResponse.Content.ReadFromJsonAsync<AocVerifyResponse>();
Assert.NotNull(verifyPayload);
Assert.Equal("tenant-verify-violations", verifyPayload!.Tenant);
Assert.True(verifyPayload.Checked.Advisories >= 1);
var violation = Assert.Single(verifyPayload.Violations);
Assert.Equal("ERR_AOC_001", violation.Code);
Assert.True(violation.Count >= 1);
Assert.NotEmpty(violation.Examples);
}
[Fact]
public async Task AdvisoryRawListEndpoint_SupportsCursorPagination()
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-list");
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:list-1", "GHSA-LIST-001"));
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:list-2", "GHSA-LIST-002"));
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:list-3", "GHSA-LIST-003"));
using var firstRequest = new HttpRequestMessage(HttpMethod.Get, "/advisories/raw?limit=2");
firstRequest.Headers.Add("X-Stella-Tenant", "tenant-list");
var firstResponse = await client.SendAsync(firstRequest);
firstResponse.EnsureSuccessStatusCode();
var firstPage = await firstResponse.Content.ReadFromJsonAsync<AdvisoryRawListResponse>();
Assert.NotNull(firstPage);
Assert.Equal(2, firstPage!.Records.Count);
Assert.True(firstPage.HasMore);
Assert.False(string.IsNullOrWhiteSpace(firstPage.NextCursor));
using var secondRequest = new HttpRequestMessage(HttpMethod.Get, $"/advisories/raw?cursor={Uri.EscapeDataString(firstPage.NextCursor!)}");
secondRequest.Headers.Add("X-Stella-Tenant", "tenant-list");
var secondResponse = await client.SendAsync(secondRequest);
secondResponse.EnsureSuccessStatusCode();
var secondPage = await secondResponse.Content.ReadFromJsonAsync<AdvisoryRawListResponse>();
Assert.NotNull(secondPage);
Assert.Single(secondPage!.Records);
Assert.False(secondPage.HasMore);
Assert.Null(secondPage.NextCursor);
var firstIds = firstPage.Records.Select(record => record.Id).ToArray();
var secondIds = secondPage.Records.Select(record => record.Id).ToArray();
Assert.Empty(firstIds.Intersect(secondIds));
}
[Fact]
public async Task AdvisoryEvidenceEndpoint_ReturnsDocumentsForCanonicalKey()
{
await SeedAdvisoryRawDocumentsAsync(
CreateAdvisoryRawDocument("tenant-a", "vendor-x", "GHSA-2025-0001", "sha256:001", new BsonDocument("id", "GHSA-2025-0001:1")),
CreateAdvisoryRawDocument("tenant-a", "vendor-y", "GHSA-2025-0001", "sha256:002", new BsonDocument("id", "GHSA-2025-0001:2")),
CreateAdvisoryRawDocument("tenant-b", "vendor-x", "GHSA-2025-0001", "sha256:003", new BsonDocument("id", "GHSA-2025-0001:3")));
using var client = _factory.CreateClient();
var response = await client.GetAsync("/vuln/evidence/advisories/ghsa-2025-0001?tenant=tenant-a");
response.EnsureSuccessStatusCode();
var evidence = await response.Content.ReadFromJsonAsync<AdvisoryEvidenceResponse>();
Assert.NotNull(evidence);
Assert.Equal("GHSA-2025-0001", evidence!.AdvisoryKey);
Assert.Equal(2, evidence.Records.Count);
Assert.All(evidence.Records, record => Assert.Equal("tenant-a", record.Tenant));
}
[Fact]
public async Task AdvisoryEvidenceEndpoint_FiltersByVendor()
{
await SeedAdvisoryRawDocumentsAsync(
CreateAdvisoryRawDocument("tenant-a", "vendor-x", "GHSA-2025-0002", "sha256:101", new BsonDocument("id", "GHSA-2025-0002:1")),
CreateAdvisoryRawDocument("tenant-a", "vendor-y", "GHSA-2025-0002", "sha256:102", new BsonDocument("id", "GHSA-2025-0002:2")));
using var client = _factory.CreateClient();
var response = await client.GetAsync("/vuln/evidence/advisories/GHSA-2025-0002?tenant=tenant-a&vendor=vendor-y");
response.EnsureSuccessStatusCode();
var evidence = await response.Content.ReadFromJsonAsync<AdvisoryEvidenceResponse>();
Assert.NotNull(evidence);
var record = Assert.Single(evidence!.Records);
Assert.Equal("vendor-y", record.Document.Source.Vendor);
}
[Fact]
public async Task AdvisoryEvidenceEndpoint_ReturnsNotFoundWhenMissing()
{
await SeedAdvisoryRawDocumentsAsync();
using var client = _factory.CreateClient();
var response = await client.GetAsync("/vuln/evidence/advisories/CVE-2099-9999?tenant=tenant-a");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task AdvisoryChunksEndpoint_EmitsRequestAndCacheMetrics()
{
await SeedObservationDocumentsAsync(BuildSampleObservationDocuments());
using var client = _factory.CreateClient();
var metrics = await CaptureMetricsAsync(
AdvisoryAiMetrics.MeterName,
new[]
{
"advisory_ai_chunk_requests_total",
"advisory_ai_chunk_cache_hits_total",
"advisory_ai_chunk_latency_milliseconds",
"advisory_ai_chunk_segments",
"advisory_ai_chunk_sources"
},
async () =>
{
const string url = "/advisories/CVE-2025-0001/chunks?tenant=tenant-a";
var first = await client.GetAsync(url);
first.EnsureSuccessStatusCode();
var second = await client.GetAsync(url);
second.EnsureSuccessStatusCode();
});
Assert.True(metrics.TryGetValue("advisory_ai_chunk_requests_total", out var requests));
Assert.NotNull(requests);
Assert.Equal(2, requests!.Count);
Assert.Contains(requests!, measurement =>
string.Equals(GetTagValue(measurement, "cache"), "miss", StringComparison.Ordinal));
Assert.Contains(requests!, measurement =>
string.Equals(GetTagValue(measurement, "cache"), "hit", StringComparison.Ordinal));
Assert.True(metrics.TryGetValue("advisory_ai_chunk_cache_hits_total", out var cacheHitMeasurements));
var cacheHit = Assert.Single(cacheHitMeasurements!);
Assert.Equal(1, cacheHit.Value);
Assert.Equal("hit", GetTagValue(cacheHit, "result"));
Assert.True(metrics.TryGetValue("advisory_ai_chunk_latency_milliseconds", out var latencyMeasurements));
Assert.Equal(2, latencyMeasurements!.Count);
Assert.All(latencyMeasurements!, measurement => Assert.True(measurement.Value > 0));
Assert.True(metrics.TryGetValue("advisory_ai_chunk_segments", out var segmentMeasurements));
Assert.Equal(2, segmentMeasurements!.Count);
Assert.Contains(segmentMeasurements!, measurement => GetTagValue(measurement, "truncated") == "false");
Assert.True(metrics.TryGetValue("advisory_ai_chunk_sources", out var sourceMeasurements));
Assert.Equal(2, sourceMeasurements!.Count);
}
[Fact]
public async Task AdvisoryChunksEndpoint_EmitsGuardrailMetrics()
{
var raw = BsonDocument.Parse("{\"details\":\"tiny\"}");
var document = CreateChunkObservationDocument(
"tenant-a:chunk:1",
"tenant-a",
new DateTime(2025, 2, 1, 0, 0, 0, DateTimeKind.Utc),
"CVE-2025-GUARD",
raw);
await SeedObservationDocumentsAsync(new[] { document });
using var client = _factory.CreateClient();
var guardrailMetrics = await CaptureMetricsAsync(
AdvisoryAiMetrics.MeterName,
"advisory_ai_guardrail_blocks_total",
async () =>
{
var response = await client.GetAsync("/advisories/CVE-2025-GUARD/chunks?tenant=tenant-a");
response.EnsureSuccessStatusCode();
});
var measurement = Assert.Single(guardrailMetrics);
Assert.True(measurement.Value >= 1);
Assert.Equal("below_minimum_length", GetTagValue(measurement, "reason"));
}
[Fact]
public async Task AdvisoryIngestEndpoint_EmitsMetricsWithExpectedTags()
{
var measurements = await CaptureMetricsAsync(
IngestionMetrics.MeterName,
"ingestion_write_total",
async () =>
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-metrics");
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:metric-1", "GHSA-METRIC-001"));
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:metric-1", "GHSA-METRIC-001"));
});
Assert.Equal(2, measurements.Count);
var inserted = measurements.FirstOrDefault(measurement =>
string.Equals(GetTagValue(measurement, "tenant"), "tenant-metrics", StringComparison.Ordinal) &&
string.Equals(GetTagValue(measurement, "result"), "inserted", StringComparison.Ordinal));
Assert.NotNull(inserted);
Assert.Equal(1, inserted!.Value);
Assert.Equal("osv", GetTagValue(inserted, "source"));
var duplicate = measurements.FirstOrDefault(measurement =>
string.Equals(GetTagValue(measurement, "tenant"), "tenant-metrics", StringComparison.Ordinal) &&
string.Equals(GetTagValue(measurement, "result"), "duplicate", StringComparison.Ordinal));
Assert.NotNull(duplicate);
Assert.Equal(1, duplicate!.Value);
Assert.Equal("osv", GetTagValue(duplicate, "source"));
}
[Fact]
public async Task AocVerifyEndpoint_EmitsVerificationMetric()
{
var measurements = await CaptureMetricsAsync(
IngestionMetrics.MeterName,
"verify_runs_total",
async () =>
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-verify-metrics");
await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:verify-metric", "GHSA-VERIFY-METRIC"));
var verifyResponse = await client.PostAsJsonAsync("/aoc/verify", new AocVerifyRequest(null, null, null, null, null));
verifyResponse.EnsureSuccessStatusCode();
});
var measurement = Assert.Single(measurements);
Assert.Equal("tenant-verify-metrics", GetTagValue(measurement, "tenant"));
Assert.Equal("ok", GetTagValue(measurement, "result"));
Assert.Equal(1, measurement.Value);
}
[Fact]
public async Task AdvisoryIngestEndpoint_RejectsCrossTenantWhenAuthenticated()
{
IdentityModelEventSource.ShowPII = true;
var environment = new Dictionary<string, string?>
{
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",
["CONCELIER_AUTHORITY__ISSUER"] = TestAuthorityIssuer,
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = TestAuthorityAudience,
["CONCELIER_AUTHORITY__CLIENTID"] = "webservice-tests",
["CONCELIER_AUTHORITY__CLIENTSECRET"] = "unused",
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.AllowAnonymousFallback = false;
authority.Issuer = TestAuthorityIssuer;
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add(TestAuthorityAudience);
authority.ClientId = "webservice-tests";
authority.ClientSecret = "unused";
},
environment);
using var client = factory.CreateClient();
var schemes = await factory.Services.GetRequiredService<IAuthenticationSchemeProvider>().GetAllSchemesAsync();
_output.WriteLine("Schemes => " + string.Join(',', schemes.Select(s => s.Name)));
var token = CreateTestToken("tenant-auth", StellaOpsScopes.AdvisoryIngest);
_output.WriteLine("token => " + token);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-auth");
var ingestResponse = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:auth-1", "GHSA-AUTH-001"));
if (ingestResponse.StatusCode != HttpStatusCode.Created)
{
var body = await ingestResponse.Content.ReadAsStringAsync();
_output.WriteLine($"ingestResponse => {(int)ingestResponse.StatusCode} {ingestResponse.StatusCode}: {body}");
var authLogs = factory.LoggerProvider.Snapshot("Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler");
foreach (var entry in authLogs)
{
_output.WriteLine($"authLog => {entry.Level}: {entry.Message} ({entry.Exception?.Message})");
}
var programLogs = factory.LoggerProvider.Snapshot("StellaOps.Concelier.WebService.Program");
foreach (var entry in programLogs)
{
_output.WriteLine($"programLog => {entry.Level}: {entry.Message}");
}
}
Assert.Equal(HttpStatusCode.Created, ingestResponse.StatusCode);
client.DefaultRequestHeaders.Remove("X-Stella-Tenant");
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-other");
var crossTenantResponse = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:auth-2", "GHSA-AUTH-002"));
Assert.Equal(HttpStatusCode.Forbidden, crossTenantResponse.StatusCode);
}
[Fact]
public async Task AdvisoryIngestEndpoint_RejectsTenantOutsideAllowlist()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",
["CONCELIER_AUTHORITY__ISSUER"] = TestAuthorityIssuer,
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = TestAuthorityAudience,
["CONCELIER_AUTHORITY__CLIENTID"] = "webservice-tests",
["CONCELIER_AUTHORITY__CLIENTSECRET"] = "unused",
["CONCELIER_AUTHORITY__REQUIREDTENANTS__0"] = "tenant-auth"
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.AllowAnonymousFallback = false;
authority.Issuer = TestAuthorityIssuer;
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add(TestAuthorityAudience);
authority.ClientId = "webservice-tests";
authority.ClientSecret = "unused";
authority.RequiredTenants.Clear();
authority.RequiredTenants.Add("tenant-auth");
},
environment);
using var client = factory.CreateClient();
var allowedToken = CreateTestToken("tenant-auth", StellaOpsScopes.AdvisoryIngest);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", allowedToken);
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-auth");
var allowedResponse = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:allow-1", "GHSA-ALLOW-001"));
Assert.Equal(HttpStatusCode.Created, allowedResponse.StatusCode);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", CreateTestToken("tenant-blocked", StellaOpsScopes.AdvisoryIngest));
client.DefaultRequestHeaders.Remove("X-Stella-Tenant");
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-blocked");
var forbiddenResponse = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:allow-2", "GHSA-ALLOW-002"));
Assert.Equal(HttpStatusCode.Forbidden, forbiddenResponse.StatusCode);
}
[Fact]
public async Task AdvisoryIngestEndpoint_ReturnsGuardViolationWhenContentHashMissing()
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-violation");
var invalidRequest = BuildAdvisoryIngestRequest(
contentHash: string.Empty,
upstreamId: "GHSA-INVALID-1",
enforceContentHash: false);
var response = await client.PostAsJsonAsync("/ingest/advisory", invalidRequest);
Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
var problemJson = await response.Content.ReadAsStringAsync();
using var document = JsonDocument.Parse(problemJson);
var root = document.RootElement;
Assert.Equal("Aggregation-Only Contract violation", root.GetProperty("title").GetString());
Assert.Equal(422, root.GetProperty("status").GetInt32());
Assert.True(root.TryGetProperty("violations", out var violations), "Problem response missing violations payload.");
Assert.True(root.TryGetProperty("code", out var codeElement), "Problem response missing code payload.");
Assert.Equal("ERR_AOC_004", codeElement.GetString());
var violation = Assert.Single(violations.EnumerateArray());
Assert.Equal("ERR_AOC_004", violation.GetProperty("code").GetString());
}
[Fact]
public async Task JobsEndpointsReturnExpectedStatuses()
{
using var client = _factory.CreateClient();
var definitions = await client.GetAsync("/jobs/definitions");
if (!definitions.IsSuccessStatusCode)
{
var body = await definitions.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"/jobs/definitions failed: {(int)definitions.StatusCode} {body}");
}
var trigger = await client.PostAsync("/jobs/unknown", new StringContent("{}", System.Text.Encoding.UTF8, "application/json"));
if (trigger.StatusCode != HttpStatusCode.NotFound)
{
var payload = await trigger.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"/jobs/unknown expected 404, got {(int)trigger.StatusCode}: {payload}");
}
var problem = await trigger.Content.ReadFromJsonAsync<ProblemDocument>();
Assert.NotNull(problem);
Assert.Equal("https://stellaops.org/problems/not-found", problem!.Type);
Assert.Equal(404, problem.Status);
}
[Fact]
public async Task JobRunEndpointReturnsProblemWhenNotFound()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync($"/jobs/{Guid.NewGuid()}");
if (response.StatusCode != HttpStatusCode.NotFound)
{
var body = await response.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"/jobs/{{id}} expected 404, got {(int)response.StatusCode}: {body}");
}
var problem = await response.Content.ReadFromJsonAsync<ProblemDocument>();
Assert.NotNull(problem);
Assert.Equal("https://stellaops.org/problems/not-found", problem!.Type);
}
[Fact]
public async Task JobTriggerMapsCoordinatorOutcomes()
{
var handler = _factory.Services.GetRequiredService<StubJobCoordinator>();
using var client = _factory.CreateClient();
handler.NextResult = JobTriggerResult.AlreadyRunning("busy");
var conflict = await client.PostAsync("/jobs/test", JsonContent.Create(new JobTriggerRequest()));
if (conflict.StatusCode != HttpStatusCode.Conflict)
{
var payload = await conflict.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"Conflict path expected 409, got {(int)conflict.StatusCode}: {payload}");
}
var conflictProblem = await conflict.Content.ReadFromJsonAsync<ProblemDocument>();
Assert.NotNull(conflictProblem);
Assert.Equal("https://stellaops.org/problems/conflict", conflictProblem!.Type);
handler.NextResult = JobTriggerResult.Accepted(new JobRunSnapshot(Guid.NewGuid(), "demo", JobRunStatus.Pending, DateTimeOffset.UtcNow, null, null, "api", null, null, null, null, new Dictionary<string, object?>()));
var accepted = await client.PostAsync("/jobs/test", JsonContent.Create(new JobTriggerRequest()));
if (accepted.StatusCode != HttpStatusCode.Accepted)
{
var payload = await accepted.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"Accepted path expected 202, got {(int)accepted.StatusCode}: {payload}");
}
Assert.NotNull(accepted.Headers.Location);
var acceptedPayload = await accepted.Content.ReadFromJsonAsync<JobRunPayload>();
Assert.NotNull(acceptedPayload);
handler.NextResult = JobTriggerResult.Failed(new JobRunSnapshot(Guid.NewGuid(), "demo", JobRunStatus.Failed, DateTimeOffset.UtcNow, null, DateTimeOffset.UtcNow, "api", null, "err", null, null, new Dictionary<string, object?>()), "boom");
var failed = await client.PostAsync("/jobs/test", JsonContent.Create(new JobTriggerRequest()));
if (failed.StatusCode != HttpStatusCode.InternalServerError)
{
var payload = await failed.Content.ReadAsStringAsync();
throw new Xunit.Sdk.XunitException($"Failed path expected 500, got {(int)failed.StatusCode}: {payload}");
}
var failureProblem = await failed.Content.ReadFromJsonAsync<ProblemDocument>();
Assert.NotNull(failureProblem);
Assert.Equal("https://stellaops.org/problems/job-failure", failureProblem!.Type);
}
[Fact]
public async Task JobsEndpointsExposeJobData()
{
var handler = _factory.Services.GetRequiredService<StubJobCoordinator>();
var now = DateTimeOffset.UtcNow;
var run = new JobRunSnapshot(
Guid.NewGuid(),
"demo",
JobRunStatus.Succeeded,
now,
now,
now.AddSeconds(2),
"api",
"hash",
null,
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(1),
new Dictionary<string, object?> { ["key"] = "value" });
handler.Definitions = new[]
{
new JobDefinition("demo", typeof(DemoJob), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(1), "*/5 * * * *", true)
};
handler.LastRuns["demo"] = run;
handler.RecentRuns = new[] { run };
handler.ActiveRuns = Array.Empty<JobRunSnapshot>();
handler.Runs[run.RunId] = run;
try
{
using var client = _factory.CreateClient();
var definitions = await client.GetFromJsonAsync<List<JobDefinitionPayload>>("/jobs/definitions");
Assert.NotNull(definitions);
Assert.Single(definitions!);
Assert.Equal("demo", definitions![0].Kind);
Assert.NotNull(definitions[0].LastRun);
Assert.Equal(run.RunId, definitions[0].LastRun!.RunId);
var runPayload = await client.GetFromJsonAsync<JobRunPayload>($"/jobs/{run.RunId}");
Assert.NotNull(runPayload);
Assert.Equal(run.RunId, runPayload!.RunId);
Assert.Equal("Succeeded", runPayload.Status);
var runs = await client.GetFromJsonAsync<List<JobRunPayload>>("/jobs?kind=demo&limit=5");
Assert.NotNull(runs);
Assert.Single(runs!);
Assert.Equal(run.RunId, runs![0].RunId);
var runsByDefinition = await client.GetFromJsonAsync<List<JobRunPayload>>("/jobs/definitions/demo/runs");
Assert.NotNull(runsByDefinition);
Assert.Single(runsByDefinition!);
var active = await client.GetFromJsonAsync<List<JobRunPayload>>("/jobs/active");
Assert.NotNull(active);
Assert.Empty(active!);
}
finally
{
handler.Definitions = Array.Empty<JobDefinition>();
handler.RecentRuns = Array.Empty<JobRunSnapshot>();
handler.ActiveRuns = Array.Empty<JobRunSnapshot>();
handler.Runs.Clear();
handler.LastRuns.Clear();
}
}
[Fact]
public async Task AdvisoryReplayEndpointReturnsLatestStatement()
{
var vulnerabilityKey = "CVE-2025-9000";
var advisory = new Advisory(
advisoryKey: vulnerabilityKey,
title: "Replay Test",
summary: "Example summary",
language: "en",
published: DateTimeOffset.Parse("2025-01-01T00:00:00Z", CultureInfo.InvariantCulture),
modified: DateTimeOffset.Parse("2025-01-02T00:00:00Z", CultureInfo.InvariantCulture),
severity: "medium",
exploitKnown: false,
aliases: new[] { vulnerabilityKey },
references: Array.Empty<AdvisoryReference>(),
affectedPackages: Array.Empty<AffectedPackage>(),
cvssMetrics: Array.Empty<CvssMetric>(),
provenance: Array.Empty<AdvisoryProvenance>());
var statementId = Guid.NewGuid();
using (var scope = _factory.Services.CreateScope())
{
var eventLog = scope.ServiceProvider.GetRequiredService<IAdvisoryEventLog>();
var appendRequest = new AdvisoryEventAppendRequest(new[]
{
new AdvisoryStatementInput(
vulnerabilityKey,
advisory,
advisory.Modified ?? advisory.Published ?? DateTimeOffset.UtcNow,
Array.Empty<Guid>(),
StatementId: statementId,
AdvisoryKey: advisory.AdvisoryKey)
});
await eventLog.AppendAsync(appendRequest, CancellationToken.None);
}
using var client = _factory.CreateClient();
var response = await client.GetAsync($"/concelier/advisories/{vulnerabilityKey}/replay");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<ReplayResponse>();
Assert.NotNull(payload);
Assert.Equal(vulnerabilityKey, payload!.VulnerabilityKey, ignoreCase: true);
var statement = Assert.Single(payload.Statements);
Assert.Equal(statementId, statement.StatementId);
Assert.Equal(advisory.AdvisoryKey, statement.Advisory.AdvisoryKey);
Assert.False(string.IsNullOrWhiteSpace(statement.StatementHash));
Assert.True(payload.Conflicts is null || payload.Conflicts!.Count == 0);
}
[Fact]
public async Task AdvisoryReplayEndpointReturnsConflictExplainer()
{
var vulnerabilityKey = "CVE-2025-9100";
var statementId = Guid.NewGuid();
var conflictId = Guid.NewGuid();
var recordedAt = DateTimeOffset.Parse("2025-02-01T00:00:00Z", CultureInfo.InvariantCulture);
using (var scope = _factory.Services.CreateScope())
{
var eventLog = scope.ServiceProvider.GetRequiredService<IAdvisoryEventLog>();
var advisory = new Advisory(
advisoryKey: vulnerabilityKey,
title: "Base advisory",
summary: "Baseline summary",
language: "en",
published: recordedAt.AddDays(-1),
modified: recordedAt,
severity: "critical",
exploitKnown: false,
aliases: new[] { vulnerabilityKey },
references: Array.Empty<AdvisoryReference>(),
affectedPackages: Array.Empty<AffectedPackage>(),
cvssMetrics: Array.Empty<CvssMetric>(),
provenance: Array.Empty<AdvisoryProvenance>());
var statementInput = new AdvisoryStatementInput(
vulnerabilityKey,
advisory,
recordedAt,
Array.Empty<Guid>(),
StatementId: statementId,
AdvisoryKey: advisory.AdvisoryKey);
await eventLog.AppendAsync(new AdvisoryEventAppendRequest(new[] { statementInput }), CancellationToken.None);
var explainer = new MergeConflictExplainerPayload(
Type: "severity",
Reason: "mismatch",
PrimarySources: new[] { "vendor" },
PrimaryRank: 1,
SuppressedSources: new[] { "nvd" },
SuppressedRank: 5,
PrimaryValue: "CRITICAL",
SuppressedValue: "MEDIUM");
using var conflictDoc = JsonDocument.Parse(explainer.ToCanonicalJson());
var conflictInput = new AdvisoryConflictInput(
vulnerabilityKey,
conflictDoc,
recordedAt,
new[] { statementId },
ConflictId: conflictId);
await eventLog.AppendAsync(new AdvisoryEventAppendRequest(Array.Empty<AdvisoryStatementInput>(), new[] { conflictInput }), CancellationToken.None);
}
using var client = _factory.CreateClient();
var response = await client.GetAsync($"/concelier/advisories/{vulnerabilityKey}/replay");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<ReplayResponse>();
Assert.NotNull(payload);
var conflict = Assert.Single(payload!.Conflicts);
Assert.Equal(conflictId, conflict.ConflictId);
Assert.Equal("severity", conflict.Explainer.Type);
Assert.Equal("mismatch", conflict.Explainer.Reason);
Assert.Equal("CRITICAL", conflict.Explainer.PrimaryValue);
Assert.Equal("MEDIUM", conflict.Explainer.SuppressedValue);
Assert.Equal(conflict.Explainer.ComputeHashHex(), conflict.ConflictHash);
}
[Fact]
public async Task MirrorEndpointsServeConfiguredArtifacts()
{
using var temp = new TempDirectory();
var exportId = "20251019T120000Z";
var exportRoot = Path.Combine(temp.Path, exportId);
var mirrorRoot = Path.Combine(exportRoot, "mirror");
var domainRoot = Path.Combine(mirrorRoot, "primary");
Directory.CreateDirectory(domainRoot);
await File.WriteAllTextAsync(
Path.Combine(mirrorRoot, "index.json"),
"""{"schemaVersion":1,"domains":[]}""");
await File.WriteAllTextAsync(
Path.Combine(domainRoot, "manifest.json"),
"""{"domainId":"primary"}""");
await File.WriteAllTextAsync(
Path.Combine(domainRoot, "bundle.json"),
"""{"advisories":[]}""");
await File.WriteAllTextAsync(
Path.Combine(domainRoot, "bundle.json.jws"),
"test-signature");
var environment = new Dictionary<string, string?>
{
["CONCELIER_MIRROR__ENABLED"] = "true",
["CONCELIER_MIRROR__EXPORTROOT"] = temp.Path,
["CONCELIER_MIRROR__ACTIVEEXPORTID"] = exportId,
["CONCELIER_MIRROR__DOMAINS__0__ID"] = "primary",
["CONCELIER_MIRROR__DOMAINS__0__DISPLAYNAME"] = "Primary",
["CONCELIER_MIRROR__DOMAINS__0__REQUIREAUTHENTICATION"] = "false",
["CONCELIER_MIRROR__DOMAINS__0__MAXDOWNLOADREQUESTSPERHOUR"] = "5",
["CONCELIER_MIRROR__MAXINDEXREQUESTSPERHOUR"] = "5"
};
using var factory = new ConcelierApplicationFactory(_runner.ConnectionString, environmentOverrides: environment);
using var client = factory.CreateClient();
var indexResponse = await client.GetAsync("/concelier/exports/index.json");
Assert.Equal(HttpStatusCode.OK, indexResponse.StatusCode);
var indexContent = await indexResponse.Content.ReadAsStringAsync();
Assert.Contains(@"""schemaVersion"":1", indexContent, StringComparison.Ordinal);
var manifestResponse = await client.GetAsync("/concelier/exports/mirror/primary/manifest.json");
Assert.Equal(HttpStatusCode.OK, manifestResponse.StatusCode);
var manifestContent = await manifestResponse.Content.ReadAsStringAsync();
Assert.Contains(@"""domainId"":""primary""", manifestContent, StringComparison.Ordinal);
var bundleResponse = await client.GetAsync("/concelier/exports/mirror/primary/bundle.json.jws");
Assert.Equal(HttpStatusCode.OK, bundleResponse.StatusCode);
var signatureContent = await bundleResponse.Content.ReadAsStringAsync();
Assert.Equal("test-signature", signatureContent);
}
[Fact]
public async Task MirrorEndpointsEnforceAuthenticationForProtectedDomains()
{
using var temp = new TempDirectory();
var exportId = "20251019T120000Z";
var secureRoot = Path.Combine(temp.Path, exportId, "mirror", "secure");
Directory.CreateDirectory(secureRoot);
await File.WriteAllTextAsync(
Path.Combine(temp.Path, exportId, "mirror", "index.json"),
"""{"schemaVersion":1,"domains":[]}""");
await File.WriteAllTextAsync(
Path.Combine(secureRoot, "manifest.json"),
"""{"domainId":"secure"}""");
var environment = new Dictionary<string, string?>
{
["CONCELIER_MIRROR__ENABLED"] = "true",
["CONCELIER_MIRROR__EXPORTROOT"] = temp.Path,
["CONCELIER_MIRROR__ACTIVEEXPORTID"] = exportId,
["CONCELIER_MIRROR__DOMAINS__0__ID"] = "secure",
["CONCELIER_MIRROR__DOMAINS__0__REQUIREAUTHENTICATION"] = "true",
["CONCELIER_MIRROR__DOMAINS__0__MAXDOWNLOADREQUESTSPERHOUR"] = "5",
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",
["CONCELIER_AUTHORITY__ISSUER"] = "https://authority.example",
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = "api://concelier",
["CONCELIER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
["CONCELIER_AUTHORITY__CLIENTID"] = "concelier-jobs",
["CONCELIER_AUTHORITY__CLIENTSECRET"] = "secret",
["CONCELIER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.AllowAnonymousFallback = false;
authority.Issuer = "https://authority.example";
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add("api://concelier");
authority.RequiredScopes.Clear();
authority.RequiredScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
authority.ClientId = "concelier-jobs";
authority.ClientSecret = "secret";
},
environment);
using var client = factory.CreateClient();
var response = await client.GetAsync("/concelier/exports/mirror/secure/manifest.json");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
var authHeader = Assert.Single(response.Headers.WwwAuthenticate);
Assert.Equal("Bearer", authHeader.Scheme);
}
[Fact]
public async Task MirrorEndpointsRespectRateLimits()
{
using var temp = new TempDirectory();
var exportId = "20251019T130000Z";
var exportRoot = Path.Combine(temp.Path, exportId);
var mirrorRoot = Path.Combine(exportRoot, "mirror");
Directory.CreateDirectory(mirrorRoot);
await File.WriteAllTextAsync(
Path.Combine(mirrorRoot, "index.json"),
"""{\"schemaVersion\":1,\"domains\":[]}"""
);
var environment = new Dictionary<string, string?>
{
["CONCELIER_MIRROR__ENABLED"] = "true",
["CONCELIER_MIRROR__EXPORTROOT"] = temp.Path,
["CONCELIER_MIRROR__ACTIVEEXPORTID"] = exportId,
["CONCELIER_MIRROR__MAXINDEXREQUESTSPERHOUR"] = "1",
["CONCELIER_MIRROR__DOMAINS__0__ID"] = "primary",
["CONCELIER_MIRROR__DOMAINS__0__REQUIREAUTHENTICATION"] = "false",
["CONCELIER_MIRROR__DOMAINS__0__MAXDOWNLOADREQUESTSPERHOUR"] = "1"
};
using var factory = new ConcelierApplicationFactory(_runner.ConnectionString, environmentOverrides: environment);
using var client = factory.CreateClient();
var okResponse = await client.GetAsync("/concelier/exports/index.json");
Assert.Equal(HttpStatusCode.OK, okResponse.StatusCode);
var limitedResponse = await client.GetAsync("/concelier/exports/index.json");
Assert.Equal((HttpStatusCode)429, limitedResponse.StatusCode);
Assert.NotNull(limitedResponse.Headers.RetryAfter);
Assert.True(limitedResponse.Headers.RetryAfter!.Delta.HasValue);
Assert.True(limitedResponse.Headers.RetryAfter!.Delta!.Value.TotalSeconds > 0);
}
[Fact]
public void MergeModuleDisabledByDefault()
{
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authorityConfigure: null,
environmentOverrides: null);
using var scope = factory.Services.CreateScope();
var provider = scope.ServiceProvider;
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
Assert.Null(provider.GetService<AdvisoryMergeService>());
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
Assert.DoesNotContain("merge:reconcile", schedulerOptions.Definitions.Keys);
}
[Fact]
public void MergeModuleReenabledWhenFeatureFlagCleared()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_FEATURES__NOMERGEENABLED"] = "false"
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authorityConfigure: null,
environmentOverrides: environment);
using var scope = factory.Services.CreateScope();
var provider = scope.ServiceProvider;
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
Assert.NotNull(provider.GetService<AdvisoryMergeService>());
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
Assert.Contains("merge:reconcile", schedulerOptions.Definitions.Keys);
}
[Fact]
public void MergeJobRemovedWhenAllowlistExcludes()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_FEATURES__NOMERGEENABLED"] = "false",
["CONCELIER_FEATURES__MERGEJOBALLOWLIST__0"] = "export:json"
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authorityConfigure: null,
environmentOverrides: environment);
using var scope = factory.Services.CreateScope();
var provider = scope.ServiceProvider;
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
Assert.NotNull(provider.GetService<AdvisoryMergeService>());
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
Assert.DoesNotContain("merge:reconcile", schedulerOptions.Definitions.Keys);
}
[Fact]
public void MergeJobRemainsWhenAllowlisted()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_FEATURES__NOMERGEENABLED"] = "false",
["CONCELIER_FEATURES__MERGEJOBALLOWLIST__0"] = "merge:reconcile"
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authorityConfigure: null,
environmentOverrides: environment);
using var scope = factory.Services.CreateScope();
var provider = scope.ServiceProvider;
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
Assert.NotNull(provider.GetService<AdvisoryMergeService>());
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
Assert.Contains("merge:reconcile", schedulerOptions.Definitions.Keys);
}
[Fact]
public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",
["CONCELIER_AUTHORITY__ISSUER"] = "https://authority.example",
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = "api://concelier",
["CONCELIER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
["CONCELIER_AUTHORITY__BYPASSNETWORKS__0"] = "127.0.0.1/32",
["CONCELIER_AUTHORITY__BYPASSNETWORKS__1"] = "::1/128",
["CONCELIER_AUTHORITY__CLIENTID"] = "concelier-jobs",
["CONCELIER_AUTHORITY__CLIENTSECRET"] = "test-secret",
["CONCELIER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.AllowAnonymousFallback = false;
authority.Issuer = "https://authority.example";
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add("api://concelier");
authority.RequiredScopes.Clear();
authority.RequiredScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
authority.BypassNetworks.Clear();
authority.BypassNetworks.Add("127.0.0.1/32");
authority.BypassNetworks.Add("::1/128");
authority.ClientId = "concelier-jobs";
authority.ClientSecret = "test-secret";
},
environment);
var handler = factory.Services.GetRequiredService<StubJobCoordinator>();
handler.Definitions = new[] { new JobDefinition("demo", typeof(DemoJob), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(1), null, true) };
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Test-RemoteAddr", "127.0.0.1");
var response = await client.GetAsync("/jobs/definitions");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var auditLogs = factory.LoggerProvider.Snapshot("Concelier.Authorization.Audit");
var bypassLog = Assert.Single(auditLogs, entry => entry.TryGetState("Bypass", out var state) && state is bool flag && flag);
Assert.True(bypassLog.TryGetState("RemoteAddress", out var remoteObj) && string.Equals(remoteObj?.ToString(), "127.0.0.1", StringComparison.Ordinal));
Assert.True(bypassLog.TryGetState("StatusCode", out var statusObj) && Convert.ToInt32(statusObj) == (int)HttpStatusCode.OK);
}
[Fact]
public async Task JobsEndpointsRequireAuthWhenFallbackDisabled()
{
var enforcementEnvironment = new Dictionary<string, string?>
{
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",
["CONCELIER_AUTHORITY__ISSUER"] = "https://authority.example",
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = "api://concelier",
["CONCELIER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
["CONCELIER_AUTHORITY__CLIENTID"] = "concelier-jobs",
["CONCELIER_AUTHORITY__CLIENTSECRET"] = "test-secret",
["CONCELIER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.AllowAnonymousFallback = false;
authority.Issuer = "https://authority.example";
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add("api://concelier");
authority.RequiredScopes.Clear();
authority.RequiredScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
authority.BypassNetworks.Clear();
authority.ClientId = "concelier-jobs";
authority.ClientSecret = "test-secret";
},
enforcementEnvironment);
var resolved = factory.Services.GetRequiredService<IOptions<ConcelierOptions>>().Value;
Assert.False(resolved.Authority.AllowAnonymousFallback);
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Test-RemoteAddr", "127.0.0.1");
var response = await client.GetAsync("/jobs/definitions");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
var auditLogs = factory.LoggerProvider.Snapshot("Concelier.Authorization.Audit");
var enforcementLog = Assert.Single(auditLogs);
Assert.True(enforcementLog.TryGetState("BypassAllowed", out var bypassAllowedObj) && bypassAllowedObj is bool bypassAllowed && bypassAllowed == false);
Assert.True(enforcementLog.TryGetState("HasPrincipal", out var principalObj) && principalObj is bool hasPrincipal && hasPrincipal == false);
}
[Fact]
public void AuthorityClientResilienceOptionsAreBound()
{
var environment = new Dictionary<string, string?>
{
["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ISSUER"] = "https://authority.example",
["CONCELIER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false",
["CONCELIER_AUTHORITY__AUDIENCES__0"] = "api://concelier",
["CONCELIER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
["CONCELIER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.ConcelierJobsTrigger,
["CONCELIER_AUTHORITY__BACKCHANNELTIMEOUTSECONDS"] = "45",
["CONCELIER_AUTHORITY__RESILIENCE__ENABLERETRIES"] = "true",
["CONCELIER_AUTHORITY__RESILIENCE__RETRYDELAYS__0"] = "00:00:02",
["CONCELIER_AUTHORITY__RESILIENCE__RETRYDELAYS__1"] = "00:00:04",
["CONCELIER_AUTHORITY__RESILIENCE__ALLOWOFFLINECACHEFALLBACK"] = "false",
["CONCELIER_AUTHORITY__RESILIENCE__OFFLINECACHETOLERANCE"] = "00:02:30"
};
using var factory = new ConcelierApplicationFactory(
_runner.ConnectionString,
authority =>
{
authority.Enabled = true;
authority.Issuer = "https://authority.example";
authority.RequireHttpsMetadata = false;
authority.Audiences.Clear();
authority.Audiences.Add("api://concelier");
authority.RequiredScopes.Clear();
authority.RequiredScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
authority.ClientScopes.Clear();
authority.ClientScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
authority.BackchannelTimeoutSeconds = 45;
},
environment);
var monitor = factory.Services.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>();
var options = monitor.CurrentValue;
Assert.Equal("https://authority.example", options.Authority);
Assert.Equal(TimeSpan.FromSeconds(45), options.HttpTimeout);
Assert.Equal(new[] { StellaOpsScopes.ConcelierJobsTrigger }, options.NormalizedScopes);
Assert.Equal(new[] { TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4) }, options.NormalizedRetryDelays);
Assert.False(options.AllowOfflineCacheFallback);
Assert.Equal(TimeSpan.FromSeconds(150), options.OfflineCacheTolerance);
}
private async Task SeedObservationDocumentsAsync(IEnumerable<AdvisoryObservationDocument> documents)
{
var client = new MongoClient(_runner.ConnectionString);
var database = client.GetDatabase(MongoStorageDefaults.DefaultDatabaseName);
var collection = database.GetCollection<AdvisoryObservationDocument>(MongoStorageDefaults.Collections.AdvisoryObservations);
try
{
await database.DropCollectionAsync(MongoStorageDefaults.Collections.AdvisoryObservations);
}
catch (MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase))
{
// Collection does not exist yet; ignore.
}
var snapshot = documents?.ToArray() ?? Array.Empty<AdvisoryObservationDocument>();
if (snapshot.Length == 0)
{
await collection.InsertManyAsync(snapshot);
return;
}
await collection.InsertManyAsync(snapshot);
var rawDocuments = snapshot
.Select(doc => CreateAdvisoryRawDocument(
doc.Tenant,
doc.Source.Vendor,
doc.Id,
doc.Upstream.ContentHash,
doc.Content.Raw.DeepClone().AsBsonDocument))
.ToArray();
await SeedAdvisoryRawDocumentsAsync(rawDocuments);
}
private static AdvisoryObservationDocument[] BuildSampleObservationDocuments()
{
return new[]
{
CreateObservationDocument(
id: "tenant-a:nvd:alpha:1",
tenant: "tenant-a",
createdAt: new DateTime(2025, 1, 5, 0, 0, 0, DateTimeKind.Utc),
aliases: new[] { "cve-2025-0001" },
purls: new[] { "pkg:npm/demo@1.0.0" },
cpes: new[] { "cpe:/a:vendor:product:1.0" },
references: new[] { ("advisory", "https://example.test/advisory-1") }),
CreateObservationDocument(
id: "tenant-a:ghsa:beta:1",
tenant: "tenant-a",
createdAt: new DateTime(2025, 1, 6, 0, 0, 0, DateTimeKind.Utc),
aliases: new[] { "ghsa-2025-xyz", "cve-2025-0001" },
purls: new[] { "pkg:npm/demo@1.1.0" },
cpes: new[] { "cpe:/a:vendor:product:1.1" },
references: new[] { ("patch", "https://example.test/patch-1") }),
CreateObservationDocument(
id: "tenant-b:nvd:alpha:1",
tenant: "tenant-b",
createdAt: new DateTime(2025, 1, 7, 0, 0, 0, DateTimeKind.Utc),
aliases: new[] { "cve-2025-0001" },
purls: new[] { "pkg:npm/demo@2.0.0" },
cpes: new[] { "cpe:/a:vendor:product:2.0" },
references: new[] { ("advisory", "https://example.test/advisory-2") })
};
}
private static AdvisoryObservationDocument CreateObservationDocument(
string id,
string tenant,
DateTime createdAt,
IEnumerable<string>? aliases = null,
IEnumerable<string>? purls = null,
IEnumerable<string>? cpes = null,
IEnumerable<(string Type, string Url)>? references = null)
{
return new AdvisoryObservationDocument
{
Id = id,
Tenant = tenant.ToLowerInvariant(),
CreatedAt = createdAt,
Source = new AdvisoryObservationSourceDocument
{
Vendor = "nvd",
Stream = "feed",
Api = "https://example.test/api"
},
Upstream = new AdvisoryObservationUpstreamDocument
{
UpstreamId = id,
DocumentVersion = null,
FetchedAt = createdAt,
ReceivedAt = createdAt,
ContentHash = $"sha256:{id}",
Signature = new AdvisoryObservationSignatureDocument
{
Present = false
},
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
},
Content = new AdvisoryObservationContentDocument
{
Format = "csaf",
SpecVersion = "2.0",
Raw = BsonDocument.Parse("""{"observation":"%ID%"}""".Replace("%ID%", id)),
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
},
Linkset = new AdvisoryObservationLinksetDocument
{
Aliases = aliases?.Where(value => value is not null).ToList(),
Purls = purls?.Where(value => value is not null).ToList(),
Cpes = cpes?.Where(value => value is not null).ToList(),
References = references is null
? new List<AdvisoryObservationReferenceDocument>()
: references
.Select(reference => new AdvisoryObservationReferenceDocument
{
Type = reference.Type,
Url = reference.Url
})
.ToList()
},
Attributes = new Dictionary<string, string>(StringComparer.Ordinal)
};
}
private static AdvisoryObservationDocument CreateChunkObservationDocument(
string id,
string tenant,
DateTime createdAt,
string alias,
BsonDocument rawDocument)
{
var document = CreateObservationDocument(
id,
tenant,
createdAt,
aliases: new[] { alias });
var clone = rawDocument.DeepClone().AsBsonDocument;
document.Content.Raw = clone;
document.Upstream.ContentHash = ComputeContentHash(clone);
return document;
}
private static readonly DateTimeOffset DefaultIngestTimestamp = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
private static string ComputeContentHash(BsonDocument rawDocument)
{
var canonical = rawDocument.ToJson(new JsonWriterSettings
{
OutputMode = JsonOutputMode.RelaxedExtendedJson
});
var digest = Hash.ComputeHashHex(Encoding.UTF8.GetBytes(canonical), HashAlgorithms.Sha256);
return $"sha256:{digest}";
}
private static string ComputeDeterministicContentHash(string upstreamId)
{
var raw = CreateJsonElement($@"{{""id"":""{upstreamId}"",""modified"":""{DefaultIngestTimestamp:O}""}}");
return NormalizeContentHash(null, raw, enforceContentHash: true);
}
private static string NormalizeContentHash(string? value, JsonElement raw, bool enforceContentHash)
{
if (!enforceContentHash)
{
return value ?? string.Empty;
}
if (!string.IsNullOrWhiteSpace(value))
{
return value.Trim();
}
var digest = Hash.ComputeHashHex(Encoding.UTF8.GetBytes(raw.GetRawText()), HashAlgorithms.Sha256);
return $"sha256:{digest}";
}
private sealed record ReplayResponse(
string VulnerabilityKey,
DateTimeOffset? AsOf,
List<ReplayStatement> Statements,
List<ReplayConflict>? Conflicts);
private sealed record ReplayStatement(
Guid StatementId,
string VulnerabilityKey,
string AdvisoryKey,
Advisory Advisory,
string StatementHash,
DateTimeOffset AsOf,
DateTimeOffset RecordedAt,
IReadOnlyList<Guid> InputDocumentIds);
private sealed record ReplayConflict(
Guid ConflictId,
string VulnerabilityKey,
IReadOnlyList<Guid> StatementIds,
string ConflictHash,
DateTimeOffset AsOf,
DateTimeOffset RecordedAt,
string Details,
MergeConflictExplainerPayload Explainer);
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? _previousTelemetryEnabled;
private readonly string? _previousTelemetryLogging;
private readonly string? _previousTelemetryTracing;
private readonly string? _previousTelemetryMetrics;
private readonly Action<ConcelierOptions.AuthorityOptions>? _authorityConfigure;
private readonly IDictionary<string, string?> _additionalPreviousEnvironment = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
public CollectingLoggerProvider LoggerProvider { get; } = new();
public ConcelierApplicationFactory(
string connectionString,
Action<ConcelierOptions.AuthorityOptions>? authorityConfigure = null,
IDictionary<string, string?>? environmentOverrides = null)
{
_connectionString = connectionString;
_authorityConfigure = authorityConfigure;
_previousDsn = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__DSN");
_previousDriver = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__DRIVER");
_previousTimeout = Environment.GetEnvironmentVariable("CONCELIER_STORAGE__COMMANDTIMEOUTSECONDS");
_previousTelemetryEnabled = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLED");
_previousTelemetryLogging = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING");
_previousTelemetryTracing = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING");
_previousTelemetryMetrics = Environment.GetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLEMETRICS");
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DSN", connectionString);
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__DRIVER", "mongo");
Environment.SetEnvironmentVariable("CONCELIER_STORAGE__COMMANDTIMEOUTSECONDS", "30");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLED", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING", "false");
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLEMETRICS", "false");
const string TestSecretKey = "CONCELIER_AUTHORITY__TESTSIGNINGSECRET";
if (environmentOverrides is null || !environmentOverrides.ContainsKey(TestSecretKey))
{
var previousSecret = Environment.GetEnvironmentVariable(TestSecretKey);
_additionalPreviousEnvironment[TestSecretKey] = previousSecret;
Environment.SetEnvironmentVariable(TestSecretKey, TestSigningSecret);
}
if (environmentOverrides is not null)
{
foreach (var kvp in environmentOverrides)
{
var previous = Environment.GetEnvironmentVariable(kvp.Key);
_additionalPreviousEnvironment[kvp.Key] = previous;
Environment.SetEnvironmentVariable(kvp.Key, kvp.Value);
}
}
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
var settings = new Dictionary<string, string?>
{
["Plugins:Directory"] = Path.Combine(context.HostingEnvironment.ContentRootPath, "StellaOps.Concelier.PluginBinaries"),
};
configurationBuilder.AddInMemoryCollection(settings!);
});
builder.ConfigureLogging(logging =>
{
logging.AddProvider(LoggerProvider);
});
builder.ConfigureServices(services =>
{
services.AddSingleton<StubJobCoordinator>();
services.AddSingleton<IJobCoordinator>(sp => sp.GetRequiredService<StubJobCoordinator>());
services.PostConfigure<ConcelierOptions>(options =>
{
options.Storage.Driver = "mongo";
options.Storage.Dsn = _connectionString;
options.Storage.CommandTimeoutSeconds = 30;
options.Plugins.Directory ??= Path.Combine(AppContext.BaseDirectory, "StellaOps.Concelier.PluginBinaries");
options.Telemetry.Enabled = false;
options.Telemetry.EnableLogging = false;
options.Telemetry.EnableTracing = false;
options.Telemetry.EnableMetrics = false;
options.Authority ??= new ConcelierOptions.AuthorityOptions();
_authorityConfigure?.Invoke(options.Authority);
});
});
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IStartupFilter, RemoteIpStartupFilter>();
services.PostConfigure<JwtBearerOptions>(StellaOpsAuthenticationDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = TestSigningKey,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
NameClaimType = ClaimTypes.Name,
RoleClaimType = ClaimTypes.Role,
ClockSkew = TimeSpan.Zero
};
var issuer = string.IsNullOrWhiteSpace(options.Authority) ? TestAuthorityIssuer : options.Authority;
options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(new OpenIdConnectConfiguration
{
Issuer = issuer
});
});
});
}
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_TELEMETRY__ENABLED", _previousTelemetryEnabled);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLELOGGING", _previousTelemetryLogging);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING", _previousTelemetryTracing);
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLEMETRICS", _previousTelemetryMetrics);
foreach (var kvp in _additionalPreviousEnvironment)
{
Environment.SetEnvironmentVariable(kvp.Key, kvp.Value);
}
LoggerProvider.Dispose();
}
private sealed class RemoteIpStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.Use(async (context, nextMiddleware) =>
{
if (context.Request.Headers.TryGetValue("X-Test-RemoteAddr", out var values)
&& values.Count > 0
&& IPAddress.TryParse(values[0], out var remote))
{
context.Connection.RemoteIpAddress = remote;
}
await nextMiddleware();
});
next(app);
};
}
}
public sealed record LogEntry(
string LoggerName,
LogLevel Level,
EventId EventId,
string? Message,
Exception? Exception,
IReadOnlyList<KeyValuePair<string, object?>> State)
{
public bool TryGetState(string name, out object? value)
{
foreach (var kvp in State)
{
if (string.Equals(kvp.Key, name, StringComparison.Ordinal))
{
value = kvp.Value;
return true;
}
}
value = null;
return false;
}
}
public sealed class CollectingLoggerProvider : ILoggerProvider
{
private readonly object syncRoot = new();
private readonly List<LogEntry> entries = new();
private bool disposed;
public ILogger CreateLogger(string categoryName) => new CollectingLogger(categoryName, this);
public IReadOnlyList<LogEntry> Snapshot(string loggerName)
{
lock (syncRoot)
{
return entries
.Where(entry => string.Equals(entry.LoggerName, loggerName, StringComparison.Ordinal))
.ToArray();
}
}
public void Dispose()
{
disposed = true;
lock (syncRoot)
{
entries.Clear();
}
}
private void Append(LogEntry entry)
{
if (disposed)
{
return;
}
lock (syncRoot)
{
entries.Add(entry);
}
}
private sealed class CollectingLogger : ILogger
{
private readonly string categoryName;
private readonly CollectingLoggerProvider provider;
public CollectingLogger(string categoryName, CollectingLoggerProvider provider)
{
this.categoryName = categoryName;
this.provider = provider;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (formatter is null)
{
throw new ArgumentNullException(nameof(formatter));
}
var message = formatter(state, exception);
var kvps = ExtractState(state);
var entry = new LogEntry(categoryName, logLevel, eventId, message, exception, kvps);
provider.Append(entry);
}
private static IReadOnlyList<KeyValuePair<string, object?>> ExtractState<TState>(TState state)
{
if (state is IReadOnlyList<KeyValuePair<string, object?>> list)
{
return list;
}
if (state is IEnumerable<KeyValuePair<string, object?>> enumerable)
{
return enumerable.ToArray();
}
if (state is null)
{
return Array.Empty<KeyValuePair<string, object?>>();
}
return new[] { new KeyValuePair<string, object?>("State", state) };
}
}
private sealed class NullScope : IDisposable
{
public static readonly NullScope Instance = new();
public void Dispose()
{
}
}
}
}
private sealed class TempDirectory : IDisposable
{
public string Path { get; }
public TempDirectory()
{
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "concelier-mirror-" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
Directory.CreateDirectory(Path);
}
public void Dispose()
{
try
{
if (Directory.Exists(Path))
{
Directory.Delete(Path, recursive: true);
}
}
catch
{
// best effort cleanup
}
}
}
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 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 JobDefinitionPayload(string Kind, bool Enabled, string? CronExpression, TimeSpan Timeout, TimeSpan LeaseDuration, JobRunPayload? LastRun);
private sealed record JobRunPayload(Guid RunId, string Kind, string Status, string Trigger, DateTimeOffset CreatedAt, DateTimeOffset? StartedAt, DateTimeOffset? CompletedAt, string? Error, TimeSpan? Duration, Dictionary<string, object?> Parameters);
private sealed record ProblemDocument(string? Type, string? Title, int? Status, string? Detail, string? Instance);
private async Task SeedAdvisoryRawDocumentsAsync(params BsonDocument[] documents)
{
var client = new MongoClient(_runner.ConnectionString);
var database = client.GetDatabase(MongoStorageDefaults.DefaultDatabaseName);
var collection = database.GetCollection<BsonDocument>(MongoStorageDefaults.Collections.AdvisoryRaw);
await collection.DeleteManyAsync(FilterDefinition<BsonDocument>.Empty);
if (documents.Length > 0)
{
await collection.InsertManyAsync(documents);
}
}
private static BsonDocument CreateAdvisoryRawDocument(
string tenant,
string vendor,
string upstreamId,
string contentHash,
BsonDocument? raw = null,
string? supersedes = null)
{
var now = DateTime.UtcNow;
return new BsonDocument
{
{ "_id", BuildRawDocumentId(vendor, upstreamId, contentHash) },
{ "tenant", tenant },
{
"source",
new BsonDocument
{
{ "vendor", vendor },
{ "connector", "test-connector" },
{ "version", "1.0.0" }
}
},
{
"upstream",
new BsonDocument
{
{ "upstream_id", upstreamId },
{ "document_version", "1" },
{ "retrieved_at", now },
{ "content_hash", contentHash },
{ "signature", new BsonDocument { { "present", false } } },
{ "provenance", new BsonDocument { { "api", "https://example.test" } } }
}
},
{
"content",
new BsonDocument
{
{ "format", "osv" },
{ "raw", raw ?? new BsonDocument("id", upstreamId) }
}
},
{
"identifiers",
new BsonDocument
{
{ "aliases", new BsonArray(new[] { upstreamId }) },
{ "primary", upstreamId }
}
},
{
"linkset",
new BsonDocument
{
{ "aliases", new BsonArray() },
{ "purls", new BsonArray() },
{ "cpes", new BsonArray() },
{ "references", new BsonArray() },
{ "reconciled_from", new BsonArray() },
{ "notes", new BsonDocument() }
}
},
{ "advisory_key", upstreamId.ToUpperInvariant() },
{
"links",
new BsonArray
{
new BsonDocument
{
{ "scheme", "PRIMARY" },
{ "value", upstreamId.ToUpperInvariant() }
}
}
},
{ "supersedes", supersedes is null ? BsonNull.Value : supersedes },
{ "ingested_at", now },
{ "created_at", now }
};
}
private static string BuildRawDocumentId(string vendor, string upstreamId, string contentHash)
{
static string Sanitize(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "unknown";
}
var buffer = new char[value.Length];
var index = 0;
foreach (var ch in value.Trim().ToLowerInvariant())
{
buffer[index++] = char.IsLetterOrDigit(ch) ? ch : '-';
}
var sanitized = new string(buffer, 0, index).Trim('-');
return string.IsNullOrEmpty(sanitized) ? "unknown" : sanitized;
}
var vendorSegment = Sanitize(vendor);
var upstreamSegment = Sanitize(upstreamId);
var hashSegment = Sanitize(contentHash.Replace(":", "-"));
return $"advisory_raw:{vendorSegment}:{upstreamSegment}:{hashSegment}";
}
private void WriteProgramLogs()
{
var entries = _factory.LoggerProvider.Snapshot("StellaOps.Concelier.WebService.Program");
foreach (var entry in entries)
{
_output.WriteLine($"[PROGRAM LOG] {entry.Level}: {entry.Message}");
}
}
private static void WarmupFactory(WebApplicationFactory<Program> factory)
{
using var client = factory.CreateClient();
}
private static AdvisoryIngestRequest BuildAdvisoryIngestRequest(
string? contentHash,
string upstreamId,
bool enforceContentHash = true)
{
var raw = CreateJsonElement($@"{{""id"":""{upstreamId}"",""modified"":""{DefaultIngestTimestamp:O}""}}");
var normalizedContentHash = NormalizeContentHash(contentHash, raw, enforceContentHash);
var references = new[]
{
new AdvisoryLinksetReferenceRequest("advisory", $"https://example.test/advisories/{upstreamId}", null)
};
return new AdvisoryIngestRequest(
new AdvisorySourceRequest("osv", "osv-connector", "1.0.0", "feed"),
new AdvisoryUpstreamRequest(
upstreamId,
"2025-01-01T00:00:00Z",
DateTimeOffset.UtcNow,
normalizedContentHash,
new AdvisorySignatureRequest(false, null, null, null, null, null),
new Dictionary<string, string> { ["http.method"] = "GET" }),
new AdvisoryContentRequest("osv", "1.3.0", raw, null),
new AdvisoryIdentifiersRequest(
upstreamId,
new[] { upstreamId, $"{upstreamId}-ALIAS" }),
new AdvisoryLinksetRequest(
new[] { upstreamId },
new[] { "pkg:npm/demo@1.0.0" },
Array.Empty<string>(),
references,
Array.Empty<string>(),
new Dictionary<string, string> { ["note"] = "ingest-test" }));
}
private static JsonElement CreateJsonElement(string json)
{
using var document = JsonDocument.Parse(json);
return document.RootElement.Clone();
}
private static async Task<IReadOnlyList<MetricMeasurement>> CaptureMetricsAsync(string meterName, string instrumentName, Func<Task> action)
{
var map = await CaptureMetricsAsync(meterName, new[] { instrumentName }, action).ConfigureAwait(false);
return map.TryGetValue(instrumentName, out var measurements)
? measurements
: Array.Empty<MetricMeasurement>();
}
private static async Task<Dictionary<string, IReadOnlyList<MetricMeasurement>>> CaptureMetricsAsync(
string meterName,
IReadOnlyCollection<string> instrumentNames,
Func<Task> action)
{
var measurementMap = instrumentNames.ToDictionary(
name => name,
_ => new List<MetricMeasurement>(),
StringComparer.Ordinal);
var instrumentSet = new HashSet<string>(instrumentNames, StringComparer.Ordinal);
var listener = new MeterListener();
listener.InstrumentPublished += (instrument, currentListener) =>
{
if (string.Equals(instrument.Meter.Name, meterName, StringComparison.Ordinal) &&
instrumentSet.Contains(instrument.Name))
{
currentListener.EnableMeasurementEvents(instrument);
}
};
void RecordMeasurement(Instrument instrument, double measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags)
{
if (!measurementMap.TryGetValue(instrument.Name, out var list))
{
return;
}
var tagDictionary = new Dictionary<string, object?>(StringComparer.Ordinal);
foreach (var tag in tags)
{
tagDictionary[tag.Key] = tag.Value;
}
list.Add(new MetricMeasurement(instrument.Name, measurement, tagDictionary));
}
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state)
=> RecordMeasurement(instrument, measurement, tags));
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state)
=> RecordMeasurement(instrument, measurement, tags));
listener.Start();
try
{
await action().ConfigureAwait(false);
}
finally
{
listener.Dispose();
}
return measurementMap.ToDictionary(
kvp => kvp.Key,
kvp => (IReadOnlyList<MetricMeasurement>)kvp.Value);
}
private static string? GetTagValue(MetricMeasurement measurement, string tag)
{
if (measurement.Tags.TryGetValue(tag, out var value))
{
return value?.ToString();
}
return null;
}
private static string CreateTestToken(string tenant, params string[] scopes)
{
var normalizedTenant = string.IsNullOrWhiteSpace(tenant) ? "default" : tenant.Trim().ToLowerInvariant();
var scopeSet = scopes is { Length: > 0 }
? scopes
.Select(StellaOpsScopes.Normalize)
.Where(static scope => !string.IsNullOrEmpty(scope))
.Select(static scope => scope!)
.Distinct(StringComparer.Ordinal)
.ToArray()
: Array.Empty<string>();
var claims = new List<Claim>
{
new Claim(StellaOpsClaimTypes.Subject, "test-user"),
new Claim(StellaOpsClaimTypes.Tenant, normalizedTenant),
new Claim(StellaOpsClaimTypes.Scope, string.Join(' ', scopeSet))
};
foreach (var scope in scopeSet)
{
claims.Add(new Claim(StellaOpsClaimTypes.ScopeItem, scope));
}
var credentials = new SigningCredentials(TestSigningKey, SecurityAlgorithms.HmacSha256);
var now = DateTime.UtcNow;
var token = new JwtSecurityToken(
issuer: TestAuthorityIssuer,
audience: TestAuthorityAudience,
claims: claims,
notBefore: now.AddMinutes(-5),
expires: now.AddMinutes(30),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private sealed record MetricMeasurement(string Instrument, double Value, IReadOnlyDictionary<string, object?> Tags);
private sealed class DemoJob : IJob
{
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken) => Task.CompletedTask;
}
private sealed class StubJobCoordinator : IJobCoordinator
{
public JobTriggerResult NextResult { get; set; } = JobTriggerResult.NotFound("not set");
public IReadOnlyList<JobDefinition> Definitions { get; set; } = Array.Empty<JobDefinition>();
public IReadOnlyList<JobRunSnapshot> RecentRuns { get; set; } = Array.Empty<JobRunSnapshot>();
public IReadOnlyList<JobRunSnapshot> ActiveRuns { get; set; } = Array.Empty<JobRunSnapshot>();
public Dictionary<Guid, JobRunSnapshot> Runs { get; } = new();
public Dictionary<string, JobRunSnapshot?> LastRuns { get; } = new(StringComparer.Ordinal);
public Task<JobTriggerResult> TriggerAsync(string kind, IReadOnlyDictionary<string, object?>? parameters, string trigger, CancellationToken cancellationToken)
=> Task.FromResult(NextResult);
public Task<IReadOnlyList<JobDefinition>> GetDefinitionsAsync(CancellationToken cancellationToken)
=> Task.FromResult(Definitions);
public Task<IReadOnlyList<JobRunSnapshot>> GetRecentRunsAsync(string? kind, int limit, CancellationToken cancellationToken)
{
IEnumerable<JobRunSnapshot> query = RecentRuns;
if (!string.IsNullOrWhiteSpace(kind))
{
query = query.Where(run => string.Equals(run.Kind, kind, StringComparison.Ordinal));
}
return Task.FromResult<IReadOnlyList<JobRunSnapshot>>(query.Take(limit).ToArray());
}
public Task<IReadOnlyList<JobRunSnapshot>> GetActiveRunsAsync(CancellationToken cancellationToken)
=> Task.FromResult(ActiveRuns);
public Task<JobRunSnapshot?> GetRunAsync(Guid runId, CancellationToken cancellationToken)
=> Task.FromResult(Runs.TryGetValue(runId, out var run) ? run : null);
public Task<JobRunSnapshot?> GetLastRunAsync(string kind, CancellationToken cancellationToken)
=> Task.FromResult(LastRuns.TryGetValue(kind, out var run) ? run : null);
public Task<IReadOnlyDictionary<string, JobRunSnapshot>> GetLastRunsAsync(IEnumerable<string> kinds, CancellationToken cancellationToken)
{
var map = new Dictionary<string, JobRunSnapshot>(StringComparer.Ordinal);
foreach (var kind in kinds)
{
if (kind is null)
{
continue;
}
if (LastRuns.TryGetValue(kind, out var run) && run is not null)
{
map[kind] = run;
}
}
return Task.FromResult<IReadOnlyDictionary<string, JobRunSnapshot>>(map);
}
}
}