using ClaudeMailbox.Data; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace ClaudeMailbox.Tests; public sealed class MigrationTests { [Fact] public async Task EnsureReady_Creates_Schema_And_Is_Idempotent() { var dbPath = Path.Combine(Path.GetTempPath(), $"claude-mailbox-migtest-{Guid.NewGuid():N}.db"); try { using (var ctx = NewCtx(dbPath)) MailboxDbContext.EnsureReady(ctx); // Second call must not throw. using (var ctx = NewCtx(dbPath)) MailboxDbContext.EnsureReady(ctx); // Verify tables exist. await using var conn = new SqliteConnection($"Data Source={dbPath}"); await conn.OpenAsync(); var tables = new List(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"; await using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) tables.Add(reader.GetString(0)); } Assert.Contains("mailboxes", tables); Assert.Contains("messages", tables); // Verify the expected index exists. string? index; using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='index' AND name='ix_messages_to_delivered';"; index = await cmd.ExecuteScalarAsync() as string; } Assert.Equal("ix_messages_to_delivered", index); } finally { SqliteConnection.ClearAllPools(); foreach (var ext in new[] { "", "-wal", "-shm" }) { var p = dbPath + ext; if (File.Exists(p)) { try { File.Delete(p); } catch { } } } } } private static MailboxDbContext NewCtx(string path) { var opts = new DbContextOptionsBuilder() .UseSqlite($"Data Source={path}") .Options; return new MailboxDbContext(opts); } }