185 lines
6.4 KiB
C#
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();
|
|
}
|
|
}
|