consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<!-- Test packages provided by Directory.Build.props -->
<ItemGroup>
<ProjectReference Include="../../VulnExplorer/StellaOps.VulnExplorer.Api/StellaOps.VulnExplorer.Api.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,8 +0,0 @@
# StellaOps.VulnExplorer.Api.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_solid_review.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/__Tests/StellaOps.VulnExplorer.Api.Tests/StellaOps.VulnExplorer.Api.Tests.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -1,80 +0,0 @@
using System.Net;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using StellaOps.VulnExplorer.Api.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.VulnExplorer.Api.Tests;
public class VulnApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> factory;
public VulnApiTests(WebApplicationFactory<Program> factory)
{
this.factory = factory.WithWebHostBuilder(_ => { });
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsDeterministicOrder()
{
var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("x-stella-tenant", "tenant-a");
var cancellationToken = TestContext.Current.CancellationToken;
var response = await client.GetAsync("/v1/vulns", cancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<VulnListResponse>(cancellationToken);
Assert.NotNull(payload);
Assert.Equal(new[] { "vuln-0001", "vuln-0002" }, payload!.Items.Select(v => v.Id));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_FiltersByCve()
{
var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("x-stella-tenant", "tenant-a");
var cancellationToken = TestContext.Current.CancellationToken;
var response = await client.GetAsync("/v1/vulns?cve=CVE-2024-2222", cancellationToken);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<VulnListResponse>(cancellationToken);
Assert.Single(payload!.Items);
Assert.Equal("vuln-0002", payload.Items[0].Id);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Detail_ReturnsNotFoundWhenMissing()
{
var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("x-stella-tenant", "tenant-a");
var cancellationToken = TestContext.Current.CancellationToken;
var response = await client.GetAsync("/v1/vulns/missing", cancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Detail_ReturnsRationaleAndPaths()
{
var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("x-stella-tenant", "tenant-a");
var cancellationToken = TestContext.Current.CancellationToken;
var response = await client.GetAsync("/v1/vulns/vuln-0001", cancellationToken);
response.EnsureSuccessStatusCode();
var detail = await response.Content.ReadFromJsonAsync<VulnDetail>(cancellationToken);
Assert.NotNull(detail);
Assert.Equal("rat-0001", detail!.Rationale.Id);
Assert.Contains("/src/app/Program.cs", detail.Paths);
Assert.NotEmpty(detail.Evidence);
}
}

View File

@@ -1,208 +0,0 @@
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."
};
}
}

View File

@@ -15,7 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
<ProjectReference Include="..\..\..\Signer\__Libraries\StellaOps.Signer.KeyManagement\StellaOps.Signer.KeyManagement.csproj" />
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Signer.KeyManagement\StellaOps.Signer.KeyManagement.csproj" />
</ItemGroup>
</Project>

View File

@@ -21,7 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\BinaryIndex\__Libraries\StellaOps.BinaryIndex.GoldenSet\StellaOps.BinaryIndex.GoldenSet.csproj" />
<ProjectReference Include="..\..\..\BinaryIndex\__Libraries\StellaOps.BinaryIndex.Analysis\StellaOps.BinaryIndex.Analysis.csproj" />
<ProjectReference Include="..\..\..\RiskEngine\StellaOps.RiskEngine\StellaOps.RiskEngine.Core\StellaOps.RiskEngine.Core.csproj" />
<ProjectReference Include="..\..\..\Findings\StellaOps.RiskEngine.Core\StellaOps.RiskEngine.Core.csproj" />
</ItemGroup>
<ItemGroup>