Files
git.stella-ops.org/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/DatabaseMigrationTests.cs

113 lines
5.1 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
using Npgsql;
using StellaOps.EvidenceLocker.Core.Configuration;
using StellaOps.EvidenceLocker.Core.Domain;
using StellaOps.EvidenceLocker.Infrastructure.Db;
using StellaOps.TestKit;
using System;
using System.Net.Http;
using Testcontainers.PostgreSql;
using Xunit;
namespace StellaOps.EvidenceLocker.Tests;
public sealed class DatabaseMigrationTests : IClassFixture<PostgreSqlFixture>
{
private readonly PostgreSqlFixture _fixture;
public DatabaseMigrationTests(PostgreSqlFixture fixture)
{
_fixture = fixture;
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task ApplyAsync_CreatesExpectedSchemaAndPolicies()
{
await _fixture.EnsureInitializedAsync();
if (_fixture.SkipReason is not null)
{
Assert.Skip(_fixture.SkipReason);
}
var cancellationToken = CancellationToken.None;
// Migrations already applied by the fixture; verify schema state.
await using var connection = await _fixture.DataSource!.OpenConnectionAsync(cancellationToken);
await using var tablesCommand = new NpgsqlCommand(
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'evidence_locker' ORDER BY table_name;",
connection);
var tables = new List<string>();
await using (var reader = await tablesCommand.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
tables.Add(reader.GetString(0));
}
}
Assert.Contains("evidence_artifacts", tables);
Assert.Contains("evidence_bundles", tables);
Assert.Contains("evidence_gate_artifacts", tables);
Assert.Contains("evidence_holds", tables);
Assert.Contains("evidence_schema_version", tables);
await using var versionCommand = new NpgsqlCommand(
"SELECT COUNT(*) FROM evidence_locker.evidence_schema_version WHERE version = 1;",
connection);
var applied = Convert.ToInt64(await versionCommand.ExecuteScalarAsync(cancellationToken) ?? 0L);
Assert.Equal(1, applied);
await using var versionFourCommand = new NpgsqlCommand(
"SELECT COUNT(*) FROM evidence_locker.evidence_schema_version WHERE version = 4;",
connection);
var appliedVersionFour = Convert.ToInt64(await versionFourCommand.ExecuteScalarAsync(cancellationToken) ?? 0L);
Assert.Equal(1, appliedVersionFour);
var tenant = TenantId.FromGuid(Guid.NewGuid());
await using var tenantConnection = await _fixture.DataSource.OpenConnectionAsync(tenant, cancellationToken);
await using var insertCommand = new NpgsqlCommand(@"
INSERT INTO evidence_locker.evidence_bundles
(bundle_id, tenant_id, kind, status, root_hash, storage_key)
VALUES
(@bundle, @tenant, 1, 3, @hash, @key);",
tenantConnection);
insertCommand.Parameters.AddWithValue("bundle", Guid.NewGuid());
insertCommand.Parameters.AddWithValue("tenant", tenant.Value);
insertCommand.Parameters.AddWithValue("hash", new string('a', 64));
insertCommand.Parameters.AddWithValue("key", $"tenants/{tenant.Value:N}/bundles/test/resource");
await insertCommand.ExecuteNonQueryAsync(cancellationToken);
await using var isolationConnection = await _fixture.DataSource.OpenConnectionAsync(tenant, cancellationToken);
await using var selectCommand = new NpgsqlCommand(
"SELECT COUNT(*) FROM evidence_locker.evidence_bundles;",
isolationConnection);
var visibleCount = Convert.ToInt64(await selectCommand.ExecuteScalarAsync(cancellationToken) ?? 0L);
Assert.Equal(1, visibleCount);
await using var otherTenantConnection = await _fixture.DataSource.OpenConnectionAsync(TenantId.FromGuid(Guid.NewGuid()), cancellationToken);
await using var otherSelectCommand = new NpgsqlCommand(
"SELECT COUNT(*) FROM evidence_locker.evidence_bundles;",
otherTenantConnection);
var otherVisible = Convert.ToInt64(await otherSelectCommand.ExecuteScalarAsync(cancellationToken) ?? 0L);
Assert.Equal(0, otherVisible);
await using var violationConnection = await _fixture.DataSource.OpenConnectionAsync(tenant, cancellationToken);
await using var violationCommand = new NpgsqlCommand(@"
INSERT INTO evidence_locker.evidence_bundles
(bundle_id, tenant_id, kind, status, root_hash, storage_key)
VALUES
(@bundle, @tenant, 1, 3, @hash, @key);",
violationConnection);
violationCommand.Parameters.AddWithValue("bundle", Guid.NewGuid());
violationCommand.Parameters.AddWithValue("tenant", Guid.NewGuid());
violationCommand.Parameters.AddWithValue("hash", new string('b', 64));
violationCommand.Parameters.AddWithValue("key", "tenants/other/bundles/resource");
await Assert.ThrowsAsync<PostgresException>(() => violationCommand.ExecuteNonQueryAsync(cancellationToken));
}
}