up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 07:52:25 +02:00
parent 5970f0d9bd
commit 150b3730ef
215 changed files with 8119 additions and 740 deletions

View File

@@ -1,14 +1,16 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.SbomService.Models;
namespace StellaOps.SbomService.Tests;
public class EntrypointEndpointsTests : IClassFixture<SbomServiceWebApplicationFactory>
public class EntrypointEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly SbomServiceWebApplicationFactory _factory;
private readonly WebApplicationFactory<Program> _factory;
public EntrypointEndpointsTests(SbomServiceWebApplicationFactory factory)
public EntrypointEndpointsTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}

View File

@@ -0,0 +1,56 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.SbomService.Models;
using System.Text.Json;
using Xunit;
namespace StellaOps.SbomService.Tests;
public class OrchestratorEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public OrchestratorEndpointsTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task List_sources_requires_tenant()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/internal/orchestrator/sources");
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task List_and_register_sources_are_deterministic()
{
var client = _factory.CreateClient();
var seeded = await client.GetFromJsonAsync<JsonElement>("/internal/orchestrator/sources?tenant=tenant-a");
seeded.TryGetProperty("items", out var items).Should().BeTrue();
items.GetArrayLength().Should().BeGreaterOrEqualTo(1);
var request = new RegisterOrchestratorSourceRequest(
TenantId: "tenant-a",
ArtifactDigest: "sha256:new123",
SourceType: "scanner-index",
Metadata: "seeded:test");
var post = await client.PostAsJsonAsync("/internal/orchestrator/sources", request);
post.EnsureSuccessStatusCode();
var created = await post.Content.ReadFromJsonAsync<OrchestratorSource>();
created.Should().NotBeNull();
created!.ArtifactDigest.Should().Be("sha256:new123");
// Idempotent on digest+type
var postAgain = await client.PostAsJsonAsync("/internal/orchestrator/sources", request);
postAgain.EnsureSuccessStatusCode();
var again = await postAgain.Content.ReadFromJsonAsync<OrchestratorSource>();
again.Should().NotBeNull();
again!.SourceId.Should().Be(created.SourceId);
}
}

View File

@@ -74,6 +74,8 @@ public class ProjectionEndpointTests : IClassFixture<WebApplicationFactory<Progr
json.tenantId.Should().Be("tenant-a");
json.hash.Should().NotBeNullOrEmpty();
json.projection.GetProperty("purl").GetString().Should().Be("pkg:npm/lodash@4.17.21");
var metadata = json.projection.GetProperty("metadata");
metadata.GetProperty("asset").GetProperty("criticality").GetString().Should().Be("high");
}
private sealed record ProjectionResponse(string snapshotId, string tenantId, string schemaVersion, string hash, System.Text.Json.JsonElement projection);

View File

@@ -0,0 +1,45 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.SbomService.Models;
using Xunit;
namespace StellaOps.SbomService.Tests;
public class ResolverFeedExportTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ResolverFeedExportTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Export_returns_ndjson_in_deterministic_order()
{
var client = _factory.CreateClient();
// ensure feed populated
await client.PostAsync("/internal/sbom/resolver-feed/backfill", null);
var response = await client.GetAsync("/internal/sbom/resolver-feed/export");
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType!.MediaType.Should().Be("application/x-ndjson");
var body = await response.Content.ReadAsStringAsync();
var lines = body.Split('\n', StringSplitOptions.RemoveEmptyEntries);
lines.Length.Should().BeGreaterThan(0);
// verify deterministic ordering by first and last line comparison
var first = lines.First();
var last = lines.Last();
first.Should().BeLessOrEqualTo(last, Comparer<string>.Create(StringComparer.Ordinal.Compare));
// spot-check a known candidate
var candidates = await client.GetFromJsonAsync<List<ResolverCandidate>>("/internal/sbom/resolver-feed");
candidates.Should().NotBeNull();
candidates!.Any(c => c.Purl == "pkg:npm/lodash@4.17.21").Should().BeTrue();
}
}

View File

@@ -0,0 +1,47 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.SbomService.Models;
using Xunit;
namespace StellaOps.SbomService.Tests;
public class SbomAssetEventsTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public SbomAssetEventsTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Projection_emits_asset_event_once()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/sboms/snap-001/projection?tenant=tenant-a");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var assetEvents = await client.GetFromJsonAsync<List<SbomAssetUpdatedEvent>>("/internal/sbom/asset-events");
assetEvents.Should().NotBeNull();
var events = assetEvents!;
events.Should().HaveCount(1);
var evt = events[0];
evt.SnapshotId.Should().Be("snap-001");
evt.TenantId.Should().Be("tenant-a");
evt.Asset.Criticality.Should().Be("high");
evt.Asset.Exposure.Should().Contain("internet");
evt.Asset.Tags.Should().ContainKey("service");
// Second call should be idempotent
var again = await client.GetAsync("/sboms/snap-001/projection?tenant=tenant-a");
again.StatusCode.Should().Be(HttpStatusCode.OK);
var assetEventsAfter = await client.GetFromJsonAsync<List<SbomAssetUpdatedEvent>>("/internal/sbom/asset-events");
assetEventsAfter.Should().NotBeNull();
assetEventsAfter!.Should().HaveCount(1);
}
}

View File

@@ -0,0 +1,66 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.SbomService.Models;
using Xunit;
namespace StellaOps.SbomService.Tests;
public class SbomInventoryEventsTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public SbomInventoryEventsTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Inventory_events_emitted_on_projection()
{
var client = _factory.CreateClient();
var projection = await client.GetAsync("/sboms/snap-001/projection?tenant=tenant-a");
projection.StatusCode.Should().Be(HttpStatusCode.OK);
var inventory = await client.GetFromJsonAsync<List<SbomInventoryEvidence>>("/internal/sbom/inventory");
inventory.Should().NotBeNull();
var items = inventory!;
items.Should().NotBeEmpty();
items.Should().ContainSingle(i => i.Purl == "pkg:npm/lodash@4.17.21" && i.Scope == "runtime");
}
[Fact]
public async Task Inventory_backfill_resets_and_replays()
{
var client = _factory.CreateClient();
var pre = await client.GetFromJsonAsync<List<SbomInventoryEvidence>>("/internal/sbom/inventory");
pre.Should().NotBeNull();
var backfill = await client.PostAsync("/internal/sbom/inventory/backfill", null);
backfill.EnsureSuccessStatusCode();
var post = await client.GetFromJsonAsync<List<SbomInventoryEvidence>>("/internal/sbom/inventory");
post.Should().NotBeNull();
post!.Count.Should().BeGreaterOrEqualTo(pre!.Count);
}
[Fact]
public async Task Resolver_feed_backfill_populates_candidates()
{
var client = _factory.CreateClient();
var before = await client.GetFromJsonAsync<List<ResolverCandidate>>("/internal/sbom/resolver-feed");
before.Should().NotBeNull();
var resp = await client.PostAsync("/internal/sbom/resolver-feed/backfill", null);
resp.EnsureSuccessStatusCode();
var feed = await client.GetFromJsonAsync<List<ResolverCandidate>>("/internal/sbom/resolver-feed");
feed.Should().NotBeNull();
feed!.Should().NotBeEmpty();
feed.Should().Contain(c => c.Purl == "pkg:npm/lodash@4.17.21");
}
}