Implement Advisory Canonicalization and Backfill Migration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added AdvisoryCanonicalizer for canonicalizing advisory identifiers. - Created EnsureAdvisoryCanonicalKeyBackfillMigration to populate advisory_key and links in advisory_raw documents. - Introduced FileSurfaceManifestStore for managing surface manifests with file system backing. - Developed ISurfaceManifestReader and ISurfaceManifestWriter interfaces for reading and writing manifests. - Implemented SurfaceManifestPathBuilder for constructing paths and URIs for surface manifests. - Added tests for FileSurfaceManifestStore to ensure correct functionality and deterministic behavior. - Updated documentation for new features and migration steps.
This commit is contained in:
@@ -8,11 +8,11 @@ namespace StellaOps.Concelier.WebService.Tests;
|
||||
public sealed class ConcelierOptionsPostConfigureTests
|
||||
{
|
||||
[Fact]
|
||||
public void Apply_LoadsClientSecretFromRelativeFile()
|
||||
{
|
||||
var tempDirectory = Directory.CreateTempSubdirectory();
|
||||
try
|
||||
{
|
||||
public void Apply_LoadsClientSecretFromRelativeFile()
|
||||
{
|
||||
var tempDirectory = Directory.CreateTempSubdirectory();
|
||||
try
|
||||
{
|
||||
var secretPath = Path.Combine(tempDirectory.FullName, "authority.secret");
|
||||
File.WriteAllText(secretPath, " concelier-secret ");
|
||||
|
||||
@@ -34,14 +34,22 @@ public sealed class ConcelierOptionsPostConfigureTests
|
||||
{
|
||||
Directory.Delete(tempDirectory.FullName, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_ThrowsWhenSecretFileMissing()
|
||||
{
|
||||
var options = new ConcelierOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Features_NoMergeEnabled_DefaultsToTrue()
|
||||
{
|
||||
var options = new ConcelierOptions();
|
||||
|
||||
Assert.True(options.Features.NoMergeEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_ThrowsWhenSecretFileMissing()
|
||||
{
|
||||
var options = new ConcelierOptions
|
||||
{
|
||||
Authority = new ConcelierOptions.AuthorityOptions
|
||||
{
|
||||
ClientSecretFile = "missing.secret"
|
||||
|
||||
@@ -469,6 +469,55 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
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 AdvisoryIngestEndpoint_EmitsMetricsWithExpectedTags()
|
||||
{
|
||||
@@ -1871,6 +1920,18 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
{ "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 }
|
||||
|
||||
Reference in New Issue
Block a user