数据库维护、安全校验和事务逻辑位于 [DatabaseMaintenanceService.cs (line 1)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Data/DatabaseMaintenanceService.cs:1)。 内置完整演示数据:1200 名考生、10800 条成绩、2404 条招生记录。 删除旧 import-test-data.mjs 和 reset-dev-database.mjs 运行入口。 Docker 镜像同时包含 Web 和 /app/tools/Eis.Tools.dll。 [package.json (line 11)](C:/Users/BI/Documents/EIS-dotnet/package.json:11) 原数据库命令已改为调用 .NET。 新增统一发布脚本:[publish.ps1 (line 1)](C:/Users/BI/Documents/EIS-dotnet/scripts/publish.ps1:1)。
135 lines
4.8 KiB
C#
135 lines
4.8 KiB
C#
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<InvalidOperationException>(() =>
|
|
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<InvalidOperationException>(() =>
|
|
service.ExecuteAsync(
|
|
DatabaseMaintenanceMode.Init,
|
|
null,
|
|
force: false,
|
|
dryRun: true,
|
|
CancellationToken.None));
|
|
|
|
Assert.Contains("系统数据库", exception.Message);
|
|
}
|
|
|
|
private static async Task<long> 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());
|
|
}
|
|
}
|