T8: Fix PostgresExceptionApplicationRepositoryTests to use NpgsqlDataSource

This commit is contained in:
StellaOps Bot
2025-12-21 09:47:30 +02:00
parent 8a4edee665
commit a216d7eea4

View File

@@ -1,95 +1,168 @@
// <copyright file="PostgresExceptionApplicationRepositoryTests.cs" company="StellaOps">
// Copyright (c) StellaOps. All rights reserved.
// Licensed under the AGPL-3.0-or-later license.
// </copyright>
using System.Collections.Immutable; using System.Collections.Immutable;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions; using Npgsql;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Exceptions.Models; using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Repositories; using StellaOps.Policy.Exceptions.Repositories;
using Xunit; using Xunit;
namespace StellaOps.Policy.Storage.Postgres.Tests; namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
/// Integration tests for PostgresExceptionApplicationRepository.
/// Tests exception application audit trail persistence.
/// </summary>
[Collection(PolicyPostgresCollection.Name)] [Collection(PolicyPostgresCollection.Name)]
public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
{ {
private readonly PolicyPostgresFixture _fixture; private readonly PolicyPostgresFixture _fixture;
private readonly PostgresExceptionApplicationRepository _repository; private readonly PostgresExceptionApplicationRepository _repository;
private readonly NpgsqlDataSource _dataSource;
private readonly Guid _tenantId = Guid.NewGuid(); private readonly Guid _tenantId = Guid.NewGuid();
public PostgresExceptionApplicationRepositoryTests(PolicyPostgresFixture fixture) public PostgresExceptionApplicationRepositoryTests(PolicyPostgresFixture fixture)
{ {
_fixture = fixture; _fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName; // Create NpgsqlDataSource from connection string
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance); var builder = new NpgsqlDataSourceBuilder(fixture.ConnectionString);
_repository = new PostgresExceptionApplicationRepository(dataSource.DataSource); _dataSource = builder.Build();
_repository = new PostgresExceptionApplicationRepository(_dataSource);
} }
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync(); public async Task InitializeAsync()
public Task DisposeAsync() => Task.CompletedTask; {
await _fixture.TruncateAllTablesAsync();
// Set search path to include the test schema
await using var conn = await _dataSource.OpenConnectionAsync();
await using var cmd = new NpgsqlCommand($"SET search_path TO {_fixture.SchemaName}, public;", conn);
await cmd.ExecuteNonQueryAsync();
}
public async Task DisposeAsync()
{
await _dataSource.DisposeAsync();
}
[Fact] [Fact]
public async Task RecordAsync_ShouldPersist() public async Task RecordAsync_ShouldPersist()
{ {
// Arrange
var app = CreateApp("EXC-001", "FIND-001"); var app = CreateApp("EXC-001", "FIND-001");
// Act
var result = await _repository.RecordAsync(app); var result = await _repository.RecordAsync(app);
// Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
result.ExceptionId.Should().Be("EXC-001"); result.ExceptionId.Should().Be("EXC-001");
result.FindingId.Should().Be("FIND-001");
} }
[Fact] [Fact]
public async Task RecordBatchAsync_ShouldPersistMultiple() public async Task RecordBatchAsync_ShouldPersistMultiple()
{ {
var apps = new[] { CreateApp("EXC-B1", "F1"), CreateApp("EXC-B1", "F2"), CreateApp("EXC-B2", "F3") }; // Arrange
var apps = new[]
{
CreateApp("EXC-B1", "F1"),
CreateApp("EXC-B1", "F2"),
CreateApp("EXC-B2", "F3"),
};
// Act
var result = await _repository.RecordBatchAsync(apps); var result = await _repository.RecordBatchAsync(apps);
// Assert
result.Should().HaveCount(3); result.Should().HaveCount(3);
} }
[Fact] [Fact]
public async Task RecordBatchAsync_EmptyReturnsEmpty() public async Task RecordBatchAsync_EmptyReturnsEmpty()
{ {
// Act
var result = await _repository.RecordBatchAsync(Array.Empty<ExceptionApplication>()); var result = await _repository.RecordBatchAsync(Array.Empty<ExceptionApplication>());
// Assert
result.Should().BeEmpty(); result.Should().BeEmpty();
} }
[Fact] [Fact]
public async Task GetByExceptionIdAsync_ReturnsMatches() public async Task GetByExceptionIdAsync_ReturnsMatches()
{ {
// Arrange
await _repository.RecordAsync(CreateApp("EXC-G1", "F1")); await _repository.RecordAsync(CreateApp("EXC-G1", "F1"));
await _repository.RecordAsync(CreateApp("EXC-G1", "F2")); await _repository.RecordAsync(CreateApp("EXC-G1", "F2"));
await _repository.RecordAsync(CreateApp("EXC-OTH", "F3")); await _repository.RecordAsync(CreateApp("EXC-OTH", "F3"));
// Act
var results = await _repository.GetByExceptionIdAsync(_tenantId, "EXC-G1"); var results = await _repository.GetByExceptionIdAsync(_tenantId, "EXC-G1");
// Assert
results.Should().HaveCount(2); results.Should().HaveCount(2);
results.Should().AllSatisfy(r => r.ExceptionId.Should().Be("EXC-G1"));
} }
[Fact] [Fact]
public async Task GetByFindingIdAsync_ReturnsMatches() public async Task GetByFindingIdAsync_ReturnsMatches()
{ {
// Arrange
await _repository.RecordAsync(CreateApp("E1", "FIND-G1")); await _repository.RecordAsync(CreateApp("E1", "FIND-G1"));
await _repository.RecordAsync(CreateApp("E2", "FIND-G1")); await _repository.RecordAsync(CreateApp("E2", "FIND-G1"));
await _repository.RecordAsync(CreateApp("E3", "FIND-OTH")); await _repository.RecordAsync(CreateApp("E3", "FIND-OTH"));
// Act
var results = await _repository.GetByFindingIdAsync(_tenantId, "FIND-G1"); var results = await _repository.GetByFindingIdAsync(_tenantId, "FIND-G1");
// Assert
results.Should().HaveCount(2); results.Should().HaveCount(2);
results.Should().AllSatisfy(r => r.FindingId.Should().Be("FIND-G1"));
} }
[Fact] [Fact]
public async Task GetByVulnerabilityIdAsync_ReturnsMatches() public async Task GetByVulnerabilityIdAsync_ReturnsMatches()
{ {
// Arrange
await _repository.RecordAsync(CreateApp("E1", "F1", "CVE-2024-1234")); await _repository.RecordAsync(CreateApp("E1", "F1", "CVE-2024-1234"));
await _repository.RecordAsync(CreateApp("E2", "F2", "CVE-2024-1234")); await _repository.RecordAsync(CreateApp("E2", "F2", "CVE-2024-1234"));
await _repository.RecordAsync(CreateApp("E3", "F3", "CVE-2024-5678")); await _repository.RecordAsync(CreateApp("E3", "F3", "CVE-2024-5678"));
// Act
var results = await _repository.GetByVulnerabilityIdAsync(_tenantId, "CVE-2024-1234"); var results = await _repository.GetByVulnerabilityIdAsync(_tenantId, "CVE-2024-1234");
// Assert
results.Should().HaveCount(2); results.Should().HaveCount(2);
results.Should().AllSatisfy(r => r.VulnerabilityId.Should().Be("CVE-2024-1234"));
} }
[Fact] [Fact]
public async Task CountAsync_WithFilter_ReturnsFiltered() public async Task CountAsync_WithFilter_ReturnsFiltered()
{ {
await _repository.RecordBatchAsync(new[] { CreateApp("E1", "F1", eff: "suppress"), CreateApp("E2", "F2", eff: "suppress"), CreateApp("E3", "F3", eff: "modify") }); // Arrange
await _repository.RecordBatchAsync(new[]
{
CreateApp("E1", "F1", eff: "suppress"),
CreateApp("E2", "F2", eff: "suppress"),
CreateApp("E3", "F3", eff: "modify"),
});
var filter = new ExceptionApplicationFilter(EffectType: "suppress"); var filter = new ExceptionApplicationFilter(EffectType: "suppress");
// Act
var count = await _repository.CountAsync(_tenantId, filter); var count = await _repository.CountAsync(_tenantId, filter);
// Assert
count.Should().Be(2); count.Should().Be(2);
} }
private ExceptionApplication CreateApp(string excId, string findId, string? vulnId = null, string eff = "suppress") => private ExceptionApplication CreateApp(
string excId,
string findId,
string? vulnId = null,
string eff = "suppress") =>
ExceptionApplication.Create(_tenantId, excId, findId, "affected", "not_affected", "test", eff, vulnId); ExceptionApplication.Create(_tenantId, excId, findId, "affected", "not_affected", "test", eff, vulnId);
} }