using Microsoft.Data.Sqlite; using ZB.MOM.WW.Auth.ApiKeys.Sqlite; namespace ZB.MOM.WW.Auth.ApiKeys.Tests; public sealed class SqliteMigratorTests : IDisposable { private readonly string _dbPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".db"); private AuthSqliteConnectionFactory Factory => new(_dbPath); [Fact] public async Task MigrateAsync_CreatesAllThreeTables() { var migrator = new SqliteAuthStoreMigrator(Factory); await migrator.MigrateAsync(CancellationToken.None); Assert.True(await TableExistsAsync(SqliteAuthSchema.ApiKeysTable)); Assert.True(await TableExistsAsync(SqliteAuthSchema.ApiKeyAuditTable)); Assert.True(await TableExistsAsync(SqliteAuthSchema.SchemaVersionTable)); } [Fact] public async Task MigrateAsync_RunTwice_IsIdempotentAndRecordsCurrentVersion() { var migrator = new SqliteAuthStoreMigrator(Factory); await migrator.MigrateAsync(CancellationToken.None); await migrator.MigrateAsync(CancellationToken.None); Assert.Equal(SqliteAuthSchema.CurrentVersion, await ReadVersionAsync()); Assert.Equal(1, await CountSchemaVersionRowsAsync()); } [Fact] public async Task MigrateAsync_FutureSchemaVersion_Throws() { var migrator = new SqliteAuthStoreMigrator(Factory); await migrator.MigrateAsync(CancellationToken.None); await SetVersionAsync(99); await Assert.ThrowsAsync( () => migrator.MigrateAsync(CancellationToken.None)); } private async Task TableExistsAsync(string tableName) { await using SqliteConnection connection = await Factory.OpenConnectionAsync(CancellationToken.None); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = $name;"; command.Parameters.AddWithValue("$name", tableName); long count = (long)(await command.ExecuteScalarAsync(CancellationToken.None) ?? 0L); return count == 1; } private async Task ReadVersionAsync() { await using SqliteConnection connection = await Factory.OpenConnectionAsync(CancellationToken.None); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = "SELECT version FROM schema_version WHERE id = 1;"; object? value = await command.ExecuteScalarAsync(CancellationToken.None); return Convert.ToInt32(value, System.Globalization.CultureInfo.InvariantCulture); } private async Task CountSchemaVersionRowsAsync() { await using SqliteConnection connection = await Factory.OpenConnectionAsync(CancellationToken.None); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = "SELECT COUNT(*) FROM schema_version;"; long count = (long)(await command.ExecuteScalarAsync(CancellationToken.None) ?? 0L); return (int)count; } private async Task SetVersionAsync(int version) { await using SqliteConnection connection = await Factory.OpenConnectionAsync(CancellationToken.None); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = "UPDATE schema_version SET version = $version WHERE id = 1;"; command.Parameters.AddWithValue("$version", version); await command.ExecuteNonQueryAsync(CancellationToken.None); } public void Dispose() { SqliteConnection.ClearAllPools(); TryDelete(_dbPath); TryDelete(_dbPath + "-wal"); TryDelete(_dbPath + "-shm"); } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (IOException) { // Best-effort cleanup of the per-test temp database. } } }