using StellaOps.Auth.Abstractions; using StellaOps.EvidenceLocker.Api; using StellaOps.TestKit; using System.Net; using System.Net.Http.Json; using System.Net.Http.Headers; namespace StellaOps.EvidenceLocker.Tests; [Collection(EvidenceLockerTestCollection.Name)] public sealed class EvidenceAuditEndpointsTests : IDisposable { private readonly EvidenceLockerWebApplicationFactory _factory; private readonly HttpClient _client; public EvidenceAuditEndpointsTests(EvidenceLockerWebApplicationFactory factory) { _factory = factory; _factory.ResetTestState(); _client = factory.CreateClient(); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task EvidenceHomeAndPacks_AreDeterministic() { ConfigureAuthHeaders(_client, Guid.NewGuid().ToString("D"), StellaOpsScopes.EvidenceRead); var homeResponse = await _client.GetAsync("/api/v1/evidence", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, homeResponse.StatusCode); var home = await homeResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(home); Assert.True(home!.QuickStats.LatestPacks24h > 0); var firstPacksResponse = await _client.GetAsync("/api/v1/evidence/packs", TestContext.Current.CancellationToken); var secondPacksResponse = await _client.GetAsync("/api/v1/evidence/packs", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, firstPacksResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, secondPacksResponse.StatusCode); var first = await firstPacksResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); var second = await secondPacksResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); Assert.Equal(first, second); var payload = await firstPacksResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(payload); Assert.NotEmpty(payload!.Items); Assert.Equal("pack-9001", payload.Items[0].PackId); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task EvidenceAuditRoutes_ReturnExpectedPayloads() { ConfigureAuthHeaders(_client, Guid.NewGuid().ToString("D"), StellaOpsScopes.EvidenceRead); var packDetail = await _client.GetFromJsonAsync( "/api/v1/evidence/packs/pack-9001", TestContext.Current.CancellationToken); Assert.NotNull(packDetail); Assert.Equal("chain-9912", packDetail!.ProofChainId); var proof = await _client.GetFromJsonAsync( "/api/v1/evidence/proofs/sha256:beef000000000000000000000000000000000000000000000000000000000003", TestContext.Current.CancellationToken); Assert.NotNull(proof); Assert.Equal("valid", proof!.Status); var audit = await _client.GetFromJsonAsync( "/api/v1/evidence/audit", TestContext.Current.CancellationToken); Assert.NotNull(audit); Assert.True(audit!.Total >= 1); var receipt = await _client.GetFromJsonAsync( "/api/v1/evidence/receipts/cvss/CVE-2026-1234", TestContext.Current.CancellationToken); Assert.NotNull(receipt); Assert.Equal(9.8m, receipt!.BaseScore); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task EvidenceAuditRoutes_UnknownResources_ReturnNotFound() { ConfigureAuthHeaders(_client, Guid.NewGuid().ToString("D"), StellaOpsScopes.EvidenceRead); var packResponse = await _client.GetAsync("/api/v1/evidence/packs/missing-pack", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, packResponse.StatusCode); var proofResponse = await _client.GetAsync("/api/v1/evidence/proofs/sha256:missing", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, proofResponse.StatusCode); var receiptResponse = await _client.GetAsync("/api/v1/evidence/receipts/cvss/CVE-0000-0000", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, receiptResponse.StatusCode); } private static void ConfigureAuthHeaders(HttpClient client, string tenantId, string scopes) { client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(EvidenceLockerTestAuthHandler.SchemeName); client.DefaultRequestHeaders.Add("X-Test-Tenant", tenantId); client.DefaultRequestHeaders.Add("X-Test-Scopes", scopes); } public void Dispose() { _client.Dispose(); } }