save checkpoint

This commit is contained in:
master
2026-02-12 21:02:43 +02:00
parent 5bca406787
commit 9911b7d73c
593 changed files with 174390 additions and 1376 deletions

View File

@@ -1,83 +1,166 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Scanner.Storage.ObjectStore;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class SbomUploadEndpointsTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_validates_cyclonedx_format()
public async Task Upload_accepts_cyclonedx_inline_and_persists_record()
{
// This test validates that CycloneDX format detection works
// Full integration with upload service is tested separately
using var secrets = new TestSurfaceSecretsScope();
await using var factory = await CreateFactoryAsync();
using var client = factory.CreateClient();
var sampleCycloneDx = """
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"metadata": { "timestamp": "2025-01-15T10:00:00Z" },
"components": []
}
""";
var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sampleCycloneDx));
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"components": [
{
"type": "library",
"name": "left-pad",
"version": "1.3.0",
"purl": "pkg:npm/left-pad@1.3.0",
"licenses": [{ "license": { "id": "MIT" } }]
},
{
"type": "library",
"name": "chalk",
"version": "5.0.0",
"purl": "pkg:npm/chalk@5.0.0"
}
]
}
""";
var request = new SbomUploadRequestDto
{
ArtifactRef = "example.com/app:1.0",
SbomBase64 = base64,
ArtifactRef = "example.com/app:1.0.0",
ArtifactDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Sbom = JsonDocument.Parse(sampleCycloneDx).RootElement.Clone(),
Source = new SbomUploadSourceDto
{
Tool = "syft",
Version = "1.0.0"
Version = "1.0.0",
CiContext = new SbomUploadCiContextDto
{
BuildId = "build-42",
Repository = "example/repo"
}
}
};
// Verify the request is valid and can be serialized
Assert.NotNull(request.ArtifactRef);
Assert.NotEmpty(request.SbomBase64);
Assert.NotNull(request.Source);
Assert.Equal("syft", request.Source.Tool);
var uploadResponse = await client.PostAsJsonAsync("/api/v1/sbom/upload", request);
Assert.Equal(HttpStatusCode.Accepted, uploadResponse.StatusCode);
var payload = await uploadResponse.Content.ReadFromJsonAsync<SbomUploadResponseDto>();
Assert.NotNull(payload);
Assert.False(string.IsNullOrWhiteSpace(payload!.SbomId));
Assert.Equal("cyclonedx", payload.Format);
Assert.Equal("1.6", payload.FormatVersion);
Assert.True(payload.ValidationResult.Valid);
Assert.Equal(2, payload.ValidationResult.ComponentCount);
Assert.Equal(0.85d, payload.ValidationResult.QualityScore);
Assert.StartsWith("sha256:", payload.Digest, StringComparison.Ordinal);
Assert.False(string.IsNullOrWhiteSpace(payload.AnalysisJobId));
var getResponse = await client.GetAsync($"/api/v1/sbom/uploads/{payload.SbomId}");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var record = await getResponse.Content.ReadFromJsonAsync<SbomUploadRecordDto>();
Assert.NotNull(record);
Assert.Equal(payload.SbomId, record!.SbomId);
Assert.Equal("example.com/app:1.0.0", record.ArtifactRef);
Assert.Equal("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", record.ArtifactDigest);
Assert.Equal("cyclonedx", record.Format);
Assert.Equal("1.6", record.FormatVersion);
Assert.Equal("build-42", record.Source?.CiContext?.BuildId);
Assert.Equal("example/repo", record.Source?.CiContext?.Repository);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_validates_spdx_format()
public async Task Upload_accepts_spdx_base64_and_tracks_ci_context()
{
// This test validates that SPDX format detection works
using var secrets = new TestSurfaceSecretsScope();
await using var factory = await CreateFactoryAsync();
using var client = factory.CreateClient();
var sampleSpdx = """
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-sbom",
"documentNamespace": "https://example.com/test",
"packages": []
}
""";
var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sampleSpdx));
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-sbom",
"documentNamespace": "https://example.com/test",
"packages": [
{
"name": "openssl",
"versionInfo": "3.0.0",
"licenseDeclared": "Apache-2.0",
"externalRefs": [
{
"referenceType": "purl",
"referenceLocator": "pkg:generic/openssl@3.0.0"
}
]
}
]
}
""";
var request = new SbomUploadRequestDto
{
ArtifactRef = "example.com/service:2.0",
SbomBase64 = base64
ArtifactRef = "example.com/service:2.0.0",
SbomBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(sampleSpdx)),
Source = new SbomUploadSourceDto
{
Tool = "trivy",
Version = "0.50.0",
CiContext = new SbomUploadCiContextDto
{
BuildId = "build-77",
Repository = "example/service-repo"
}
}
};
// Verify the request is valid
Assert.NotNull(request.ArtifactRef);
Assert.NotEmpty(request.SbomBase64);
var uploadResponse = await client.PostAsJsonAsync("/api/v1/sbom/upload", request);
Assert.Equal(HttpStatusCode.Accepted, uploadResponse.StatusCode);
var payload = await uploadResponse.Content.ReadFromJsonAsync<SbomUploadResponseDto>();
Assert.NotNull(payload);
Assert.Equal("spdx", payload!.Format);
Assert.Equal("2.3", payload.FormatVersion);
Assert.True(payload.ValidationResult.Valid);
Assert.Equal(1, payload.ValidationResult.ComponentCount);
Assert.Equal(1.0d, payload.ValidationResult.QualityScore);
Assert.False(string.IsNullOrWhiteSpace(payload.AnalysisJobId));
Assert.StartsWith("sha256:", payload.Digest, StringComparison.Ordinal);
var getResponse = await client.GetAsync($"/api/v1/sbom/uploads/{payload.SbomId}");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var record = await getResponse.Content.ReadFromJsonAsync<SbomUploadRecordDto>();
Assert.NotNull(record);
Assert.Equal("build-77", record!.Source?.CiContext?.BuildId);
Assert.Equal("example/service-repo", record.Source?.CiContext?.Repository);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task Upload_rejects_unknown_format()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -87,7 +170,7 @@ public sealed class SbomUploadEndpointsTests
var invalid = new SbomUploadRequestDto
{
ArtifactRef = "example.com/invalid:1.0",
SbomBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{\"name\":\"oops\"}"))
SbomBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"name\":\"oops\"}"))
};
var response = await client.PostAsJsonAsync("/api/v1/sbom/upload", invalid);
@@ -109,38 +192,6 @@ public sealed class SbomUploadEndpointsTests
return factory;
}
private static string LoadFixtureBase64(string fileName)
{
var repoRoot = ResolveRepoRoot();
var path = Path.Combine(
repoRoot,
"src",
"AirGap",
"__Tests",
"StellaOps.AirGap.Importer.Tests",
"Reconciliation",
"Fixtures",
fileName);
Assert.True(File.Exists(path), $"Fixture not found at {path}.");
var bytes = File.ReadAllBytes(path);
return Convert.ToBase64String(bytes);
}
private static string ResolveRepoRoot()
{
var baseDirectory = AppContext.BaseDirectory;
return Path.GetFullPath(Path.Combine(
baseDirectory,
"..",
"..",
"..",
"..",
"..",
"..",
".."));
}
private sealed class InMemoryArtifactObjectStore : IArtifactObjectStore
{
private readonly ConcurrentDictionary<string, byte[]> _objects = new(StringComparer.Ordinal);

View File

@@ -153,7 +153,28 @@ public sealed partial class ScansEndpointsTests
ImmutableArray<EntryTraceEdge>.Empty,
ImmutableArray<EntryTraceDiagnostic>.Empty,
ImmutableArray.Create(plan),
ImmutableArray.Create(terminal));
ImmutableArray.Create(terminal),
new EntryTraceBinaryIntelligence(
ImmutableArray.Create(new EntryTraceBinaryTarget(
"/usr/local/bin/app",
"sha256:abc",
"X64",
"ELF",
3,
2,
1,
1,
ImmutableArray.Create(new EntryTraceBinaryVulnerability(
"CVE-2024-1234",
"SSL_read",
"pkg:generic/openssl",
"SSL_read",
0.98f,
"Critical")))),
1,
1,
1,
generatedAt));
var ndjson = EntryTraceNdjsonWriter.Serialize(graph, new EntryTraceNdjsonMetadata(scanId, "sha256:test", generatedAt));
var storedResult = new EntryTraceResult(scanId, "sha256:test", generatedAt, graph, ndjson);
@@ -173,6 +194,8 @@ public sealed partial class ScansEndpointsTests
Assert.Equal(storedResult.ScanId, payload!.ScanId);
Assert.Equal(storedResult.ImageDigest, payload.ImageDigest);
Assert.Equal(storedResult.Graph.Plans.Length, payload.Graph.Plans.Length);
Assert.NotNull(payload.Graph.BinaryIntelligence);
Assert.Equal(1, payload.Graph.BinaryIntelligence!.TotalVulnerableMatches);
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| QA-SCANNER-VERIFY-008 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: extended entry-trace endpoint contract test assertions for `graph.binaryIntelligence`; verified in run-002 Tier 2 (2026-02-12). |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/StellaOps.Scanner.WebService.Tests.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| SPRINT-20260208-062-VEXREACH-001 | DONE | Added deterministic unit coverage for VEX+reachability filter matrix and controller endpoint (`6` tests passed on filtered run, 2026-02-08). |