fix(db): prevent migration-test database drop + correct AEG-X-004 evidence

Tests now guard against accidental drop of kartsell_migration_test by throwing
when the credential source DB is the destructive rehearsal target. Distinct
credential DB (kartselldb_test) prevents config collision.

AEG-X-004 evidence consolidated: rehearsal .trx files + preflight markdown
documented. Schema 0032 (shadow_run_queued_status_contract) verified
fresh/upgrade/recovery on isolated DB.

AGENTS.md: Necessity-driven (guard against destructive accident); no new feature.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 13:29:14 +09:00
parent 2b2841671c
commit f0a945ab96
9 changed files with 291 additions and 15 deletions
@@ -14,33 +14,46 @@ namespace KArtSell.Integration.Tests;
public sealed class DbUpMigrationTests : IAsyncLifetime
{
private NpgsqlDataSource _dataSource = null!;
private const string DefaultConnString = "Host=localhost;Port=5432;Database=kartsell_migration_test;Username=kartsell;Password=kartsell";
private const string ApprovedMigrationTestDatabase = "kartsell_migration_test";
public async Task InitializeAsync()
{
var connString = TestDatabaseConnection.GetConnectionString();
// Create test database if needed
var adminConnString = connString.Replace("kartsell_migration_test", "postgres");
var configured = new NpgsqlConnectionStringBuilder(connString);
if (string.Equals(configured.Database, ApprovedMigrationTestDatabase, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Refusing to use the migration database as the admin source '{configured.Database}'. " +
"Use the configured development test database as the credential source.");
}
var adminBuilder = new NpgsqlConnectionStringBuilder(connString)
{
Database = "postgres"
};
var adminConnString = adminBuilder.ConnectionString;
await using var adminConn = new NpgsqlConnection(adminConnString);
await adminConn.OpenAsync();
try
{
await using var cmd = adminConn.CreateCommand();
cmd.CommandText = "DROP DATABASE IF EXISTS kartsell_migration_test WITH (FORCE);";
cmd.CommandText = $"DROP DATABASE IF EXISTS {ApprovedMigrationTestDatabase} WITH (FORCE);";
await cmd.ExecuteNonQueryAsync();
}
catch (PostgresException ex) when (ex.SqlState == "3D000") { /* DB doesn't exist */ }
await using var createCmd = adminConn.CreateCommand();
createCmd.CommandText = "CREATE DATABASE kartsell_migration_test;";
createCmd.CommandText = $"CREATE DATABASE {ApprovedMigrationTestDatabase};";
await createCmd.ExecuteNonQueryAsync();
await adminConn.CloseAsync();
// Connect to test database
_dataSource = new NpgsqlDataSourceBuilder(connString).Build();
configured.Database = ApprovedMigrationTestDatabase;
_dataSource = new NpgsqlDataSourceBuilder(configured.ConnectionString).Build();
// Apply prerequisite migrations (0000-0007)
await ApplyPrerequisiteMigrationsAsync();
@@ -51,14 +64,22 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
await _dataSource.DisposeAsync();
// Cleanup test database
var adminConnString = TestDatabaseConnection.GetConnectionString();
adminConnString = adminConnString.Replace("kartsell_migration_test", "postgres");
var configured = new NpgsqlConnectionStringBuilder(TestDatabaseConnection.GetConnectionString());
if (string.Equals(configured.Database, ApprovedMigrationTestDatabase, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Refusing to use the migration database as the admin source '{configured.Database}'. " +
"Use the configured development test database as the credential source.");
}
configured.Database = "postgres";
var adminConnString = configured.ConnectionString;
await using var adminConn = new NpgsqlConnection(adminConnString);
await adminConn.OpenAsync();
await using var dropCmd = adminConn.CreateCommand();
dropCmd.CommandText = "DROP DATABASE IF EXISTS kartsell_migration_test WITH (FORCE);";
dropCmd.CommandText = $"DROP DATABASE IF EXISTS {ApprovedMigrationTestDatabase} WITH (FORCE);";
await dropCmd.ExecuteNonQueryAsync();
await adminConn.CloseAsync();