Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/FindingsEvidenceControllerTests.cs

185 lines
6.4 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Scanner.Triage;
using StellaOps.Scanner.Triage.Entities;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class FindingsEvidenceControllerTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsNotFound_WhenFindingMissing()
{
using var secrets = new TestSurfaceSecretsScope();
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
{
configuration["scanner:authority:enabled"] = "false";
});
await EnsureTriageSchemaAsync(factory);
using var client = factory.CreateClient();
var response = await client.GetAsync($"/api/v1/findings/{Guid.NewGuid()}/evidence");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsForbidden_WhenRawScopeMissing()
{
using var secrets = new TestSurfaceSecretsScope();
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
{
configuration["scanner:authority:enabled"] = "false";
});
await EnsureTriageSchemaAsync(factory);
using var client = factory.CreateClient();
var response = await client.GetAsync($"/api/v1/findings/{Guid.NewGuid()}/evidence?includeRaw=true");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsEvidence_WhenFindingExists()
{
using var secrets = new TestSurfaceSecretsScope();
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
{
configuration["scanner:authority:enabled"] = "false";
});
await EnsureTriageSchemaAsync(factory);
using var client = factory.CreateClient();
var findingId = await SeedFindingAsync(factory);
var response = await client.GetAsync($"/api/v1/findings/{findingId}/evidence");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<FindingEvidenceResponse>(SerializerOptions);
Assert.NotNull(result);
Assert.Equal(findingId.ToString(), result!.FindingId);
Assert.Equal("CVE-2024-12345", result.Cve);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsBadRequest_WhenTooMany()
{
using var secrets = new TestSurfaceSecretsScope();
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
{
configuration["scanner:authority:enabled"] = "false";
});
await EnsureTriageSchemaAsync(factory);
using var client = factory.CreateClient();
var request = new BatchEvidenceRequest
{
FindingIds = Enumerable.Range(0, 101).Select(_ => Guid.NewGuid().ToString()).ToList()
};
var response = await client.PostAsJsonAsync("/api/v1/findings/evidence/batch", request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsResults_ForExistingFindings()
{
using var secrets = new TestSurfaceSecretsScope();
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
{
configuration["scanner:authority:enabled"] = "false";
});
await EnsureTriageSchemaAsync(factory);
using var client = factory.CreateClient();
var findingId = await SeedFindingAsync(factory);
var request = new BatchEvidenceRequest
{
FindingIds = new[] { findingId.ToString(), Guid.NewGuid().ToString() }
};
var response = await client.PostAsJsonAsync("/api/v1/findings/evidence/batch", request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<BatchEvidenceResponse>(SerializerOptions);
Assert.NotNull(result);
Assert.Single(result!.Findings);
Assert.Equal(findingId.ToString(), result.Findings[0].FindingId);
}
private static async Task<Guid> SeedFindingAsync(ScannerApplicationFactory factory)
{
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<TriageDbContext>();
await db.Database.EnsureCreatedAsync();
var now = DateTimeOffset.UtcNow;
var findingId = Guid.NewGuid();
var finding = new TriageFinding
{
Id = findingId,
AssetId = Guid.NewGuid(),
AssetLabel = "prod/api-gateway:1.2.3",
Purl = "pkg:npm/lodash@4.17.20",
CveId = "CVE-2024-12345",
FirstSeenAt = now,
LastSeenAt = now,
UpdatedAt = now
};
db.Findings.Add(finding);
db.RiskResults.Add(new TriageRiskResult
{
Id = Guid.NewGuid(),
FindingId = findingId,
PolicyId = "policy-1",
PolicyVersion = "1.0.0",
InputsHash = "sha256:inputs",
Score = 72,
Verdict = TriageVerdict.Block,
Lane = TriageLane.Blocked,
Why = "High risk score",
ComputedAt = now
});
db.EvidenceArtifacts.Add(new TriageEvidenceArtifact
{
Id = Guid.NewGuid(),
FindingId = findingId,
Type = TriageEvidenceType.Provenance,
Title = "SBOM attestation",
ContentHash = "sha256:attestation",
Uri = "s3://evidence/attestation.json",
CreatedAt = now
});
await db.SaveChangesAsync();
return findingId;
}
private static async Task EnsureTriageSchemaAsync(ScannerApplicationFactory factory)
{
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<TriageDbContext>();
await db.Database.EnsureCreatedAsync();
}
}