Add unit tests and implementations for MongoDB index models and OpenAPI metadata

- Implemented `MongoIndexModelTests` to verify index models for various stores.
- Created `OpenApiMetadataFactory` with methods to generate OpenAPI metadata.
- Added tests for `OpenApiMetadataFactory` to ensure expected defaults and URL overrides.
- Introduced `ObserverSurfaceSecrets` and `WebhookSurfaceSecrets` for managing secrets.
- Developed `RuntimeSurfaceFsClient` and `WebhookSurfaceFsClient` for manifest retrieval.
- Added dependency injection tests for `SurfaceEnvironmentRegistration` in both Observer and Webhook contexts.
- Implemented tests for secret resolution in `ObserverSurfaceSecretsTests` and `WebhookSurfaceSecretsTests`.
- Created `EnsureLinkNotMergeCollectionsMigrationTests` to validate MongoDB migration logic.
- Added project files for MongoDB tests and NuGet package mirroring.
This commit is contained in:
master
2025-11-17 21:21:56 +02:00
parent d3128aec24
commit 9075bad2d9
146 changed files with 152183 additions and 82 deletions

View File

@@ -205,6 +205,104 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Assert.Equal("tenant-a:nvd:alpha:1", secondObservations[0].GetProperty("observationId").GetString());
}
[Fact]
public async Task LinksetsEndpoint_ReturnsNormalizedLinksetsFromIngestion()
{
var tenant = "tenant-linkset-ingest";
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", tenant);
var firstIngest = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:linkset-1", "GHSA-LINK-001", purls: new[] { "pkg:npm/demo@1.0.0" }));
firstIngest.EnsureSuccessStatusCode();
var secondIngest = await client.PostAsJsonAsync("/ingest/advisory", BuildAdvisoryIngestRequest("sha256:linkset-2", "GHSA-LINK-002", purls: new[] { "pkg:npm/demo@2.0.0" }));
secondIngest.EnsureSuccessStatusCode();
var response = await client.GetAsync("/linksets?tenant=tenant-linkset-ingest&limit=10");
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<AdvisoryLinksetQueryResponse>();
Assert.NotNull(payload);
Assert.Equal(2, payload!.Linksets.Length);
var linksetAdvisoryIds = payload.Linksets.Select(ls => ls.AdvisoryId).OrderBy(id => id, StringComparer.Ordinal).ToArray();
Assert.Equal(new[] { "GHSA-LINK-001", "GHSA-LINK-002" }, linksetAdvisoryIds);
var allPurls = payload.Linksets.SelectMany(ls => ls.Purls).OrderBy(p => p, StringComparer.Ordinal).ToArray();
Assert.Contains("pkg:npm/demo@1.0.0", allPurls);
Assert.Contains("pkg:npm/demo@2.0.0", allPurls);
var versions = payload.Linksets
.SelectMany(ls => ls.Versions)
.Distinct(StringComparer.Ordinal)
.OrderBy(v => v, StringComparer.Ordinal)
.ToArray();
Assert.Contains("1.0.0", versions);
Assert.Contains("2.0.0", versions);
Assert.False(payload.HasMore);
Assert.True(string.IsNullOrEmpty(payload.NextCursor));
}
[Fact]
public async Task LinksetsEndpoint_SupportsCursorPagination()
{
var tenant = "tenant-linkset-page";
var documents = new[]
{
CreateLinksetDocument(
tenant,
"nvd",
"ADV-002",
new[] { "obs-2" },
new[] { "pkg:npm/demo@2.0.0" },
new[] { "2.0.0" },
new DateTime(2025, 1, 6, 0, 0, 0, DateTimeKind.Utc)),
CreateLinksetDocument(
tenant,
"osv",
"ADV-001",
new[] { "obs-1" },
new[] { "pkg:npm/demo@1.0.0" },
new[] { "1.0.0" },
new DateTime(2025, 1, 5, 0, 0, 0, DateTimeKind.Utc)),
CreateLinksetDocument(
"tenant-other",
"osv",
"ADV-999",
new[] { "obs-x" },
new[] { "pkg:npm/other@1.0.0" },
new[] { "1.0.0" },
new DateTime(2025, 1, 4, 0, 0, 0, DateTimeKind.Utc))
};
await SeedLinksetDocumentsAsync(documents);
using var client = _factory.CreateClient();
var firstResponse = await client.GetAsync($"/linksets?tenant={tenant}&limit=1");
firstResponse.EnsureSuccessStatusCode();
var firstPayload = await firstResponse.Content.ReadFromJsonAsync<AdvisoryLinksetQueryResponse>();
Assert.NotNull(firstPayload);
var first = Assert.Single(firstPayload!.Linksets);
Assert.Equal("ADV-002", first.AdvisoryId);
Assert.Equal(new[] { "pkg:npm/demo@2.0.0" }, first.Purls.ToArray());
Assert.Equal(new[] { "2.0.0" }, first.Versions.ToArray());
Assert.True(firstPayload.HasMore);
Assert.False(string.IsNullOrWhiteSpace(firstPayload.NextCursor));
var secondResponse = await client.GetAsync($"/linksets?tenant={tenant}&limit=1&cursor={Uri.EscapeDataString(firstPayload.NextCursor!)}");
secondResponse.EnsureSuccessStatusCode();
var secondPayload = await secondResponse.Content.ReadFromJsonAsync<AdvisoryLinksetQueryResponse>();
Assert.NotNull(secondPayload);
var second = Assert.Single(secondPayload!.Linksets);
Assert.Equal("ADV-001", second.AdvisoryId);
Assert.Equal(new[] { "pkg:npm/demo@1.0.0" }, second.Purls.ToArray());
Assert.Equal(new[] { "1.0.0" }, second.Versions.ToArray());
Assert.False(secondPayload.HasMore);
Assert.True(string.IsNullOrEmpty(secondPayload.NextCursor));
}
[Fact]
public async Task ObservationsEndpoint_ReturnsBadRequestWhenTenantMissing()
{
@@ -1505,6 +1603,52 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
await SeedAdvisoryRawDocumentsAsync(rawDocuments);
}
private async Task SeedLinksetDocumentsAsync(IEnumerable<AdvisoryLinksetDocument> documents)
{
var client = new MongoClient(_runner.ConnectionString);
var database = client.GetDatabase(MongoStorageDefaults.DefaultDatabaseName);
var collection = database.GetCollection<AdvisoryLinksetDocument>(MongoStorageDefaults.Collections.AdvisoryLinksets);
try
{
await database.DropCollectionAsync(MongoStorageDefaults.Collections.AdvisoryLinksets);
}
catch (MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase))
{
// Collection not created yet; safe to ignore.
}
var snapshot = documents?.ToArray() ?? Array.Empty<AdvisoryLinksetDocument>();
if (snapshot.Length > 0)
{
await collection.InsertManyAsync(snapshot);
}
}
private static AdvisoryLinksetDocument CreateLinksetDocument(
string tenant,
string source,
string advisoryId,
IEnumerable<string> observationIds,
IEnumerable<string> purls,
IEnumerable<string> versions,
DateTime createdAtUtc)
{
return new AdvisoryLinksetDocument
{
TenantId = tenant,
Source = source,
AdvisoryId = advisoryId,
Observations = observationIds.ToList(),
CreatedAt = DateTime.SpecifyKind(createdAtUtc, DateTimeKind.Utc),
Normalized = new AdvisoryLinksetNormalizedDocument
{
Purls = purls.ToList(),
Versions = versions.ToList()
}
};
}
private static AdvisoryObservationDocument[] BuildSampleObservationDocuments()
{
return new[]