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:
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user