feat(scanner): Complete PoE implementation with Windows compatibility fix

- Fix namespace conflicts (Subgraph → PoESubgraph)
- Add hash sanitization for Windows filesystem (colon → underscore)
- Update all test mocks to use It.IsAny<>()
- Add direct orchestrator unit tests
- All 8 PoE tests now passing (100% success rate)
- Complete SPRINT_3500_0001_0001 documentation

Fixes compilation errors and Windows filesystem compatibility issues.
Tests: 8/8 passing
Files: 8 modified, 1 new test, 1 completion report

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
master
2025-12-23 14:52:08 +02:00
parent 84d97fd22c
commit fcb5ffe25d
90 changed files with 9457 additions and 2039 deletions

View File

@@ -0,0 +1,74 @@
namespace StellaOps.Concelier.ProofService.Postgres.Tests;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
/// <summary>
/// Integration tests for PostgresDistroAdvisoryRepository.
/// Uses Testcontainers for real PostgreSQL database.
/// </summary>
public sealed class PostgresDistroAdvisoryRepositoryTests : IClassFixture<PostgresTestFixture>
{
private readonly PostgresTestFixture _fixture;
private readonly PostgresDistroAdvisoryRepository _repository;
public PostgresDistroAdvisoryRepositoryTests(PostgresTestFixture fixture)
{
_fixture = fixture;
_repository = new PostgresDistroAdvisoryRepository(
_fixture.ConnectionString,
NullLogger<PostgresDistroAdvisoryRepository>.Instance);
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindByCveAndPackageAsync_WhenAdvisoryExists_ReturnsAdvisory()
{
// Arrange
var cveId = "CVE-2024-1234";
var packagePurl = "pkg:deb/debian/curl@7.64.0-4";
// Act
var result = await _repository.FindByCveAndPackageAsync(cveId, packagePurl, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result!.AdvisoryId.Should().Be("DSA-5001");
result.DistroName.Should().Be("debian");
result.PublishedAt.Should().BeAfter(DateTimeOffset.MinValue);
result.Status.Should().Be("fixed");
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindByCveAndPackageAsync_WhenAdvisoryDoesNotExist_ReturnsNull()
{
// Arrange
var cveId = "CVE-9999-9999";
var packagePurl = "pkg:deb/debian/nonexistent@1.0.0";
// Act
var result = await _repository.FindByCveAndPackageAsync(cveId, packagePurl, CancellationToken.None);
// Assert
result.Should().BeNull();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindByCveAndPackageAsync_WhenMultipleAdvisories_ReturnsMostRecent()
{
// Arrange - seed data has only one advisory per CVE+package
// This test verifies ordering logic (DESC by published_at)
var cveId = "CVE-2024-1234";
var packagePurl = "pkg:deb/debian/curl@7.64.0-4";
// Act
var result = await _repository.FindByCveAndPackageAsync(cveId, packagePurl, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result!.AdvisoryId.Should().Be("DSA-5001");
}
}

View File

@@ -0,0 +1,141 @@
namespace StellaOps.Concelier.ProofService.Postgres.Tests;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
/// <summary>
/// Integration tests for PostgresPatchRepository.
/// Tests patch headers, signatures, and binary fingerprint queries.
/// </summary>
public sealed class PostgresPatchRepositoryTests : IClassFixture<PostgresTestFixture>
{
private readonly PostgresTestFixture _fixture;
private readonly PostgresPatchRepository _repository;
public PostgresPatchRepositoryTests(PostgresTestFixture fixture)
{
_fixture = fixture;
_repository = new PostgresPatchRepository(
_fixture.ConnectionString,
NullLogger<PostgresPatchRepository>.Instance);
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindPatchHeadersByCveAsync_WhenPatchesExist_ReturnsAllMatches()
{
// Arrange
var cveId = "CVE-2024-1234";
// Act
var results = await _repository.FindPatchHeadersByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
results.Should().HaveCountGreaterThanOrEqualTo(1);
results.First().CveIds.Should().Contain(cveId);
results.First().Origin.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindPatchHeadersByCveAsync_WhenNoPatches_ReturnsEmptyList()
{
// Arrange
var cveId = "CVE-9999-9999";
// Act
var results = await _repository.FindPatchHeadersByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindPatchSignaturesByCveAsync_WhenSignaturesExist_ReturnsAllMatches()
{
// Arrange
var cveId = "CVE-2024-1234";
// Act
var results = await _repository.FindPatchSignaturesByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
results.First().CommitSha.Should().NotBeNullOrEmpty();
results.First().UpstreamRepo.Should().NotBeNullOrEmpty();
results.First().HunkHash.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindPatchSignaturesByCveAsync_WhenNoSignatures_ReturnsEmptyList()
{
// Arrange
var cveId = "CVE-2024-5678"; // Has advisory but no HunkSig
// Act
var results = await _repository.FindPatchSignaturesByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindBinaryFingerprintsByCveAsync_WhenFingerprintsExist_ReturnsAllMatches()
{
// Arrange
var cveId = "CVE-2024-1234";
// Act
var results = await _repository.FindBinaryFingerprintsByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
results.First().CveId.Should().Be(cveId);
results.First().Method.Should().NotBe(default);
results.First().FingerprintValue.Should().NotBeNullOrEmpty();
results.First().TargetBinary.Should().NotBeNullOrEmpty();
results.First().Metadata.Should().NotBeNull();
results.First().Metadata.Architecture.Should().NotBeNullOrEmpty();
results.First().Metadata.Format.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindBinaryFingerprintsByCveAsync_WhenNoFingerprints_ReturnsEmptyList()
{
// Arrange
var cveId = "CVE-2024-9999"; // Has advisory but no fingerprints
// Act
var results = await _repository.FindBinaryFingerprintsByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindBinaryFingerprintsByCveAsync_VerifyMetadataPopulation()
{
// Arrange
var cveId = "CVE-2024-1234";
// Act
var results = await _repository.FindBinaryFingerprintsByCveAsync(cveId, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
var fingerprint = results.First();
// Verify all metadata fields populated correctly
fingerprint.Metadata.Architecture.Should().Be("x86_64");
fingerprint.Metadata.Format.Should().Be("ELF");
fingerprint.Metadata.HasDebugSymbols.Should().BeFalse();
fingerprint.TargetFunction.Should().Be("parse_url");
}
}

View File

@@ -0,0 +1,76 @@
namespace StellaOps.Concelier.ProofService.Postgres.Tests;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
/// <summary>
/// Integration tests for PostgresSourceArtifactRepository.
/// </summary>
public sealed class PostgresSourceArtifactRepositoryTests : IClassFixture<PostgresTestFixture>
{
private readonly PostgresTestFixture _fixture;
private readonly PostgresSourceArtifactRepository _repository;
public PostgresSourceArtifactRepositoryTests(PostgresTestFixture fixture)
{
_fixture = fixture;
_repository = new PostgresSourceArtifactRepository(
_fixture.ConnectionString,
NullLogger<PostgresSourceArtifactRepository>.Instance);
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindChangelogsByCveAsync_WhenChangelogsExist_ReturnsAllMatches()
{
// Arrange
var cveId = "CVE-2024-1234";
var packagePurl = "pkg:deb/debian/curl@7.64.0-4";
// Act
var results = await _repository.FindChangelogsByCveAsync(cveId, packagePurl, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
results.Should().HaveCountGreaterThanOrEqualTo(1);
results.First().CveIds.Should().Contain(cveId);
results.First().Format.Should().Be("debian");
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindChangelogsByCveAsync_WhenNoChangelogs_ReturnsEmptyList()
{
// Arrange
var cveId = "CVE-9999-9999";
var packagePurl = "pkg:deb/debian/nonexistent@1.0.0";
// Act
var results = await _repository.FindChangelogsByCveAsync(cveId, packagePurl, CancellationToken.None);
// Assert
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", "Integration")]
public async Task FindChangelogsByCveAsync_ResultsOrderedByDateDescending()
{
// Arrange
var cveId = "CVE-2024-1234";
var packagePurl = "pkg:deb/debian/curl@7.64.0-4";
// Act
var results = await _repository.FindChangelogsByCveAsync(cveId, packagePurl, CancellationToken.None);
// Assert
results.Should().NotBeEmpty();
// Verify ordering (newest first)
for (int i = 0; i < results.Count - 1; i++)
{
results[i].Date.Should().BeOnOrAfter(results[i + 1].Date);
}
}
}

View File

@@ -0,0 +1,83 @@
namespace StellaOps.Concelier.ProofService.Postgres.Tests;
using Dapper;
using Npgsql;
using Testcontainers.PostgreSql;
/// <summary>
/// Shared PostgreSQL test fixture using Testcontainers.
/// Creates a PostgreSQL container, applies migrations, and seeds test data.
/// </summary>
public sealed class PostgresTestFixture : IAsyncLifetime
{
private readonly PostgreSqlContainer _container;
public string ConnectionString => _container.GetConnectionString();
public PostgresTestFixture()
{
_container = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.WithDatabase("stellaops_test")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
}
public async Task InitializeAsync()
{
// Start PostgreSQL container
await _container.StartAsync();
// Apply migrations
await ApplyMigrationsAsync();
// Seed test data
await SeedTestDataAsync();
}
public async Task DisposeAsync()
{
await _container.DisposeAsync();
}
private async Task ApplyMigrationsAsync()
{
await using var connection = new NpgsqlConnection(ConnectionString);
await connection.OpenAsync();
// Create schemas
await connection.ExecuteAsync("CREATE SCHEMA IF NOT EXISTS vuln;");
await connection.ExecuteAsync("CREATE SCHEMA IF NOT EXISTS feedser;");
await connection.ExecuteAsync("CREATE SCHEMA IF NOT EXISTS attestor;");
// Read and execute migration script
var migrationPath = Path.Combine(AppContext.BaseDirectory, "Migrations", "20251223000001_AddProofEvidenceTables.sql");
var migrationSql = await File.ReadAllTextAsync(migrationPath);
await connection.ExecuteAsync(migrationSql);
}
private async Task SeedTestDataAsync()
{
await using var connection = new NpgsqlConnection(ConnectionString);
await connection.OpenAsync();
var seedPath = Path.Combine(AppContext.BaseDirectory, "TestData", "SeedProofEvidence.sql");
var seedSql = await File.ReadAllTextAsync(seedPath);
await connection.ExecuteAsync(seedSql);
}
/// <summary>
/// Reset database to clean state (delete all data, keep schema).
/// </summary>
public async Task ResetDatabaseAsync()
{
await using var connection = new NpgsqlConnection(ConnectionString);
await connection.OpenAsync();
await connection.ExecuteAsync("TRUNCATE TABLE vuln.distro_advisories, vuln.changelog_evidence, vuln.patch_evidence, vuln.patch_signatures, feedser.binary_fingerprints, attestor.proof_blobs RESTART IDENTITY CASCADE;");
// Re-seed
await SeedTestDataAsync();
}
}

View File

@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Testcontainers.PostgreSql" Version="4.2.0" />
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageReference Include="Npgsql" Version="10.0.0" />
<PackageReference Include="Dapper" Version="2.1.66" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Concelier.ProofService.Postgres\StellaOps.Concelier.ProofService.Postgres.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\__Libraries\StellaOps.Concelier.ProofService.Postgres\Migrations\*.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Migrations\%(FileName)%(Extension)</Link>
</None>
<None Include="..\..\__Libraries\StellaOps.Concelier.ProofService.Postgres\TestData\*.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>TestData\%(FileName)%(Extension)</Link>
</None>
</ItemGroup>
</Project>