테스트 DB 계약과 실행 안전성 정렬
ci / backend (push) Failing after 0s
ci / static (push) Failing after 6s
ci / backend (pull_request) Failing after 1s
ci / static (pull_request) Failing after 7s
Build & Test with Secrets / build (pull_request) Failing after 1s
ci / frontend (push) Failing after 48s
Build & Test with Secrets / security-scan (pull_request) Successful in 5s
Build & Test with Secrets / frontend (pull_request) Failing after 1m23s
ci / frontend (pull_request) Failing after 1m32s
Build & Test with Secrets / notification (pull_request) Failing after 2s

This commit is contained in:
2026-08-02 17:37:12 +09:00
parent cc7d963755
commit 74ddd95a05
16 changed files with 258 additions and 66 deletions
@@ -18,7 +18,7 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
public async Task InitializeAsync()
{
var connString = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES") ?? DefaultConnString;
var connString = TestDatabaseConnection.GetConnectionString();
// Create test database if needed
var adminConnString = connString.Replace("kartsell_migration_test", "postgres");
@@ -51,7 +51,7 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
await _dataSource.DisposeAsync();
// Cleanup test database
var adminConnString = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES") ?? DefaultConnString;
var adminConnString = TestDatabaseConnection.GetConnectionString();
adminConnString = adminConnString.Replace("kartsell_migration_test", "postgres");
await using var adminConn = new NpgsqlConnection(adminConnString);
@@ -149,13 +149,10 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
// Assert: All three tables exist
Assert.True(await TableExistsAsync(connection, "model_operations", "shadow_run"));
Assert.True(await TableExistsAsync(connection, "outbox", "inbox"));
Assert.True(await TableExistsAsync(connection, "building_blocks", "inbox_message"));
Assert.True(await TableExistsAsync(connection, "model_operations", "approval_queue"));
// Assert: Foreign keys exist
var fkCount = await CountForeignKeysAsync(connection, "outbox", "inbox");
Assert.True(fkCount > 0, "inbox should have FK to outbox");
var aqFkCount = await CountForeignKeysAsync(connection, "model_operations", "approval_queue");
Assert.True(aqFkCount > 0, "approval_queue should have FK to shadow_run");
}
@@ -191,7 +188,8 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
// Assert: Data survived, table is unchanged
await using var selectCmd = connection.CreateCommand();
selectCmd.CommandText = "SELECT COUNT(*) FROM model_operations.shadow_run;";
selectCmd.CommandText = "SELECT COUNT(*) FROM model_operations.shadow_run WHERE run_id = @runId;";
selectCmd.Parameters.AddWithValue("@runId", runId);
var count = (long?)await selectCmd.ExecuteScalarAsync();
Assert.Equal(1, count);
@@ -268,8 +266,9 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
var outboxId = Guid.NewGuid();
await using var outboxCmd = connection.CreateCommand();
outboxCmd.CommandText = """
INSERT INTO outbox.outbox (id, event_type, payload)
VALUES (@id, 'TestEvent', '{"test":"data"}'::jsonb);
INSERT INTO building_blocks.outbox_message
(message_id, event_type, schema_version, payload_json, correlation_id, occurred_at, payload_hash)
VALUES (@id, 'TestEvent', 1, '{"test":"data"}'::jsonb, 'test-correlation', CURRENT_TIMESTAMP, 'test-hash');
""";
outboxCmd.Parameters.AddWithValue("@id", outboxId);
await outboxCmd.ExecuteNonQueryAsync();
@@ -278,11 +277,10 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
var inboxId = Guid.NewGuid();
await using var invalidCmd = connection.CreateCommand();
invalidCmd.CommandText = """
INSERT INTO outbox.inbox (id, outbox_id, consumer_id, event_type, payload, status)
VALUES (@id, @outboxId, 'TestConsumer', 'TestEvent', '{"test":"data"}'::jsonb, 'Processed');
INSERT INTO building_blocks.inbox_message (message_id, consumer, status)
VALUES (@id, 'TestConsumer', 'Processed');
""";
invalidCmd.Parameters.AddWithValue("@id", inboxId);
invalidCmd.Parameters.AddWithValue("@outboxId", outboxId);
// Assert: Trigger violation
await Assert.ThrowsAsync<PostgresException>(
@@ -304,8 +302,9 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
var outboxId = Guid.NewGuid();
await using var outboxCmd = connection.CreateCommand();
outboxCmd.CommandText = """
INSERT INTO outbox.outbox (id, event_type, payload)
VALUES (@id, 'TestEvent', '{"test":"data"}'::jsonb);
INSERT INTO building_blocks.outbox_message
(message_id, event_type, schema_version, payload_json, correlation_id, occurred_at, payload_hash)
VALUES (@id, 'TestEvent', 1, '{"test":"data"}'::jsonb, 'test-correlation', CURRENT_TIMESTAMP, 'test-hash');
""";
outboxCmd.Parameters.AddWithValue("@id", outboxId);
await outboxCmd.ExecuteNonQueryAsync();
@@ -313,8 +312,8 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
// Insert first inbox record
await using var insertCmd = connection.CreateCommand();
insertCmd.CommandText = """
INSERT INTO outbox.inbox (outbox_id, consumer_id, event_type, payload, status)
VALUES (@outboxId, 'Consumer1', 'TestEvent', '{"test":"data"}'::jsonb, 'Pending');
INSERT INTO building_blocks.inbox_message (message_id, consumer, status)
VALUES (@outboxId, 'Consumer1', 'Pending');
""";
insertCmd.Parameters.AddWithValue("@outboxId", outboxId);
await insertCmd.ExecuteNonQueryAsync();
@@ -322,8 +321,8 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
// Act: Try to insert duplicate (same outbox_id + consumer_id)
await using var duplicateCmd = connection.CreateCommand();
duplicateCmd.CommandText = """
INSERT INTO outbox.inbox (outbox_id, consumer_id, event_type, payload, status)
VALUES (@outboxId, 'Consumer1', 'TestEvent', '{"test":"data"}'::jsonb, 'Pending');
INSERT INTO building_blocks.inbox_message (message_id, consumer, status)
VALUES (@outboxId, 'Consumer1', 'Pending');
""";
duplicateCmd.Parameters.AddWithValue("@outboxId", outboxId);
@@ -479,7 +478,7 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
{
await using var connection = await _dataSource.OpenConnectionAsync();
await using var cmd = connection.CreateCommand();
cmd.CommandText = File.ReadAllText("src/KArtSell.DbMigrator/0008_CreateShadowRunTable.sql");
cmd.CommandText = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "migrations", "0022_model_operations_execution_schema.sql"));
await cmd.ExecuteNonQueryAsync();
}
@@ -487,7 +486,7 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
{
await using var connection = await _dataSource.OpenConnectionAsync();
await using var cmd = connection.CreateCommand();
cmd.CommandText = File.ReadAllText("src/KArtSell.DbMigrator/0009_CreateInboxTable.sql");
cmd.CommandText = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "migrations", "0023_inbox_status_contract.sql"));
await cmd.ExecuteNonQueryAsync();
}
@@ -495,7 +494,7 @@ public sealed class DbUpMigrationTests : IAsyncLifetime
{
await using var connection = await _dataSource.OpenConnectionAsync();
await using var cmd = connection.CreateCommand();
cmd.CommandText = File.ReadAllText("src/KArtSell.DbMigrator/0010_CreateApprovalQueueTable.sql");
cmd.CommandText = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "migrations", "0022_model_operations_execution_schema.sql"));
await cmd.ExecuteNonQueryAsync();
}