using Eis.Infrastructure.Data; using Microsoft.Data.Sqlite; namespace Eis.Infrastructure.Tests.Data; public sealed class DatabaseMaintenanceServiceTests { [Fact] public async Task SeedAndReset_UseValidatedReplacementAndCreateBackups() { var root = Path.Combine(Path.GetTempPath(), $"eis-tools-{Guid.NewGuid():N}"); var path = Path.Combine(root, "eis.sqlite"); Directory.CreateDirectory(root); try { var service = new DatabaseMaintenanceService(DatabaseOptions.CreateSqlite(path)); await service.ExecuteAsync( DatabaseMaintenanceMode.Init, null, force: false, dryRun: false, CancellationToken.None); var seed = await service.ExecuteAsync( DatabaseMaintenanceMode.Seed, path, force: false, dryRun: false, CancellationToken.None); Assert.NotNull(seed.BackupPath); Assert.True(File.Exists(seed.BackupPath)); Assert.Equal(1200, seed.Summary?.Candidates); Assert.Equal(10800, seed.Summary?.Results); Assert.Equal(1200, await ScalarAsync(path, "SELECT COUNT(*) FROM candidate_profiles")); Assert.Equal(10800, await ScalarAsync(path, "SELECT COUNT(*) FROM results")); var reset = await service.ExecuteAsync( DatabaseMaintenanceMode.Reset, path, force: false, dryRun: false, CancellationToken.None); Assert.NotNull(reset.BackupPath); Assert.True(File.Exists(reset.BackupPath)); Assert.Equal(0, await ScalarAsync(path, "SELECT COUNT(*) FROM candidate_profiles")); Assert.Equal(1, await ScalarAsync(path, "SELECT COUNT(*) FROM users")); Assert.Equal(20, await ScalarAsync(path, "SELECT schema_version FROM schema_metadata WHERE id = 1")); } finally { SqliteConnection.ClearAllPools(); if (Directory.Exists(root)) { Directory.Delete(root, recursive: true); } } } [Fact] public async Task DestructiveCommand_RequiresExactTargetConfirmation() { var root = Path.Combine(Path.GetTempPath(), $"eis-tools-confirm-{Guid.NewGuid():N}"); var path = Path.Combine(root, "eis.sqlite"); try { var service = new DatabaseMaintenanceService(DatabaseOptions.CreateSqlite(path)); var exception = await Assert.ThrowsAsync(() => service.ExecuteAsync( DatabaseMaintenanceMode.Seed, path + ".wrong", force: false, dryRun: false, CancellationToken.None)); Assert.Contains("--confirm-target", exception.Message); Assert.False(File.Exists(path)); } finally { SqliteConnection.ClearAllPools(); if (Directory.Exists(root)) { Directory.Delete(root, recursive: true); } } } [Fact] public async Task DryRun_ReportsSeedWithoutWritingDatabase() { var root = Path.Combine(Path.GetTempPath(), $"eis-tools-dry-{Guid.NewGuid():N}"); var path = Path.Combine(root, "eis.sqlite"); var service = new DatabaseMaintenanceService(DatabaseOptions.CreateSqlite(path)); var result = await service.ExecuteAsync( DatabaseMaintenanceMode.Seed, null, force: false, dryRun: true, CancellationToken.None); Assert.True(result.DryRun); Assert.Equal(1200, result.Summary?.Candidates); Assert.False(File.Exists(path)); } [Fact] public async Task MySqlSystemDatabase_IsRejectedBeforeConnecting() { var options = DatabaseOptions.CreateMySql( "Server=127.0.0.1;User ID=test;Database=mysql;Connection Timeout=1"); var service = new DatabaseMaintenanceService(options); var exception = await Assert.ThrowsAsync(() => service.ExecuteAsync( DatabaseMaintenanceMode.Init, null, force: false, dryRun: true, CancellationToken.None)); Assert.Contains("系统数据库", exception.Message); } private static async Task ScalarAsync(string path, string sql) { await using var connection = new SqliteConnection($"Data Source={path};Pooling=False"); await connection.OpenAsync(); await using var command = connection.CreateCommand(); command.CommandText = sql; return Convert.ToInt64(await command.ExecuteScalarAsync()); } }