consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -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>
|
||||
@@ -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. |
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user