using Dapper; using KArtSell.Host.Observability; using Npgsql; using Xunit; namespace KArtSell.Integration.Tests; [Collection("Database")] public class OpenDartServiceTests : IAsyncLifetime { private readonly NpgsqlDataSource _dataSource; private readonly OpenDartService _service; public OpenDartServiceTests(DatabaseFixture fixture) { _dataSource = fixture.DataSource; // Set test API key to stub/test mode (AGENTS.md ยง9: reproducibility, external dependency isolation) // Tests must NOT call real APIs; configure for local testing only if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENDART_API"))) Environment.SetEnvironmentVariable("OPENDART_API", "test-stub-key-no-real-calls"); // Use HttpClient without network to prevent real API calls in tests // Real API calls belong in shadow run, not unit/integration tests _service = new OpenDartService(_dataSource, new HttpClient(), fixture.Clock(), fixture.Logger()); } public async Task InitializeAsync() { // Create opendata schema if needed await using var conn = await _dataSource.OpenConnectionAsync(); await conn.ExecuteAsync(""" CREATE SCHEMA IF NOT EXISTS opendata; CREATE TABLE IF NOT EXISTS opendata.opendart_cache ( id BIGSERIAL PRIMARY KEY, ticker VARCHAR(10) NOT NULL, quarter VARCHAR(6) NOT NULL, data_json JSONB NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, published_at TIMESTAMP WITH TIME ZONE NOT NULL, UNIQUE(ticker, quarter) ); TRUNCATE opendata.opendart_cache; """); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task OpenDartCache_SchemaExists() { // Verify opendata schema and tables exist await using var conn = await _dataSource.OpenConnectionAsync(); var schemaExists = await conn.QueryFirstOrDefaultAsync( "SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = 'opendata')"); Assert.True(schemaExists, "opendata schema should exist"); var cacheTableExists = await conn.QueryFirstOrDefaultAsync( "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = 'opendata' AND table_name = 'opendart_cache')"); Assert.True(cacheTableExists, "opendata.opendart_cache table should exist"); } [Fact] public async Task OpenDartCache_HasRequiredColumns() { await using var conn = await _dataSource.OpenConnectionAsync(); var columnNames = await conn.QueryAsync(""" SELECT column_name FROM information_schema.columns WHERE table_schema = 'opendata' AND table_name = 'opendart_cache' ORDER BY ordinal_position """); var cols = columnNames.ToList(); Assert.Contains("ticker", cols); Assert.Contains("quarter", cols); Assert.Contains("data_json", cols); Assert.Contains("expires_at", cols); } [Fact] public async Task OpenDartBatchLog_SchemaExists() { await using var conn = await _dataSource.OpenConnectionAsync(); var batchLogExists = await conn.QueryFirstOrDefaultAsync( "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = 'opendata' AND table_name = 'opendart_batch_log')"); Assert.True(batchLogExists, "opendata.opendart_batch_log table should exist"); } }