save checkpoint: save features

This commit is contained in:
master
2026-02-12 10:27:23 +02:00
parent dca86e1248
commit 5bca406787
8837 changed files with 1796879 additions and 5294 deletions

View File

@@ -0,0 +1,208 @@
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.VulnExplorer.Api.Tests;
public sealed class VulnExplorerTriageApiE2ETests : IClassFixture<WebApplicationFactory<Program>>
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
private readonly WebApplicationFactory<Program> factory;
public VulnExplorerTriageApiE2ETests(WebApplicationFactory<Program> factory)
{
this.factory = factory.WithWebHostBuilder(_ => { });
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task CreateAndGetVexDecision_WorksEndToEnd()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
var createPayload = CreateDecisionPayload("CVE-2025-E2E-001", "notAffected", withAttestation: false);
var createResponse = await client.PostAsJsonAsync("/v1/vex-decisions", createPayload, JsonOptions, cancellationToken);
Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode);
var created = await createResponse.Content.ReadFromJsonAsync<JsonObject>(cancellationToken);
var decisionId = created?["id"]?.GetValue<string>();
Assert.False(string.IsNullOrWhiteSpace(decisionId));
var getResponse = await client.GetAsync($"/v1/vex-decisions/{decisionId}", cancellationToken);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var fetched = await getResponse.Content.ReadFromJsonAsync<JsonObject>(cancellationToken);
Assert.Equal("CVE-2025-E2E-001", fetched?["vulnerabilityId"]?.GetValue<string>());
Assert.Equal("notAffected", fetched?["status"]?.GetValue<string>());
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task CreateWithAttestation_ReturnsSignedOverrideAndRekorReference()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
var payload = CreateDecisionPayload("CVE-2025-E2E-002", "affectedMitigated", withAttestation: true);
var response = await client.PostAsJsonAsync("/v1/vex-decisions", payload, JsonOptions, cancellationToken);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<JsonObject>(cancellationToken);
var signedOverride = body?["signedOverride"]?.AsObject();
Assert.NotNull(signedOverride);
Assert.False(string.IsNullOrWhiteSpace(signedOverride?["envelopeDigest"]?.GetValue<string>()));
Assert.NotNull(signedOverride?["rekorLogIndex"]);
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task EvidenceSubgraphEndpoint_ReturnsReachabilityAndProofReferences()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
var response = await client.GetAsync("/v1/evidence-subgraph/CVE-2025-0001", cancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<JsonObject>(cancellationToken);
Assert.NotNull(body?["root"]);
Assert.NotNull(body?["edges"]);
Assert.NotNull(body?["verdict"]);
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task FixVerificationWorkflow_TracksStateTransitions()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
var createResponse = await client.PostAsJsonAsync(
"/v1/fix-verifications",
new
{
cveId = "CVE-2025-E2E-003",
componentPurl = "pkg:maven/org.example/app@1.2.3",
artifactDigest = "sha256:abc123"
},
cancellationToken);
Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode);
var patchResponse = await client.PatchAsync(
"/v1/fix-verifications/CVE-2025-E2E-003",
JsonContent.Create(new { verdict = "verified_by_scanner" }),
cancellationToken);
Assert.Equal(HttpStatusCode.OK, patchResponse.StatusCode);
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task CreateAuditBundle_ReturnsBundleForDecisionSet()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
var createPayload = CreateDecisionPayload("CVE-2025-E2E-004", "notAffected", withAttestation: false);
var decisionResponse = await client.PostAsJsonAsync("/v1/vex-decisions", createPayload, JsonOptions, cancellationToken);
Assert.Equal(HttpStatusCode.Created, decisionResponse.StatusCode);
var decision = await decisionResponse.Content.ReadFromJsonAsync<JsonObject>(cancellationToken);
var decisionId = decision?["id"]?.GetValue<string>();
Assert.False(string.IsNullOrWhiteSpace(decisionId));
var bundleResponse = await client.PostAsJsonAsync(
"/v1/audit-bundles",
new
{
tenant = "tenant-a",
decisionIds = new[] { decisionId }
},
cancellationToken);
Assert.Equal(HttpStatusCode.Created, bundleResponse.StatusCode);
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task CreateDecision_WithInvalidStatus_ReturnsBadRequest()
{
var client = CreateClient();
var cancellationToken = TestContext.Current.CancellationToken;
const string invalidJson = """
{
"vulnerabilityId": "CVE-2025-E2E-005",
"subject": {
"type": "image",
"name": "registry.example/app:9.9.9",
"digest": {
"sha256": "zzz999"
}
},
"status": "invalidStatusLiteral",
"justificationType": "other"
}
""";
using var content = new StringContent(invalidJson, Encoding.UTF8, "application/json");
var response = await client.PostAsync("/v1/vex-decisions", content, cancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
private HttpClient CreateClient()
{
var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("x-stella-tenant", "tenant-a");
client.DefaultRequestHeaders.Add("x-stella-user-id", "e2e-analyst");
client.DefaultRequestHeaders.Add("x-stella-user-name", "E2E Analyst");
return client;
}
private static object CreateDecisionPayload(string vulnerabilityId, string status, bool withAttestation)
{
if (withAttestation)
{
return new
{
vulnerabilityId,
subject = new
{
type = "image",
name = "registry.example/app:2.0.0",
digest = new Dictionary<string, string> { ["sha256"] = "def456" }
},
status,
justificationType = "runtimeMitigationPresent",
justificationText = "Runtime guard active.",
attestationOptions = new
{
createAttestation = true,
anchorToRekor = true,
signingKeyId = "test-key"
}
};
}
return new
{
vulnerabilityId,
subject = new
{
type = "image",
name = "registry.example/app:1.2.3",
digest = new Dictionary<string, string> { ["sha256"] = "abc123" }
},
status,
justificationType = "codeNotReachable",
justificationText = "Guarded by deployment policy."
};
}
}