using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData; using Microsoft.EntityFrameworkCore; namespace Gpulse.WCT.DataAnalyzer.Tests.Commands; /// /// RV-001:EF 迁移替代 EnsureCreated。验证两类场景: /// 1. 全新库经迁移建全部表(CliTestHost 已覆盖,此处断言关键新表存在)。 /// 2. 既有 EnsureCreated 库(无迁移历史但有表)经 MigrateOrUpgradeAsync 升级后补齐 /// 本变更新增的 3 张维度表 + test_scenario 的 3 列 FK。 /// [TestFixture] public class DatabaseMigrationInitializerTests { /// 全新库经迁移建表后,3 张新维度表存在。 [Test] public async Task MigrateOrUpgrade_FreshDb_CreatesNewDimensionTables() { var dbPath = Path.Combine(Path.GetTempPath(), $"wct_fresh_{Guid.NewGuid():N}.db"); try { await using var context = CreateContext(dbPath); await DatabaseMigrationInitializer.MigrateOrUpgradeAsync(context); Assert.Multiple(async () => { Assert.That(await TableExistsAsync(context, "manufacturer"), Is.True); Assert.That(await TableExistsAsync(context, "car_model"), Is.True); Assert.That(await TableExistsAsync(context, "foreign_object"), Is.True); }); } finally { Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); if (File.Exists(dbPath)) File.Delete(dbPath); } } /// 既有 EnsureCreated 库(旧 schema:无新表/FK 列)经升级补齐。 [Test] public async Task MigrateOrUpgrade_LegacyEnsureCreatedDb_AddsNewTablesAndFkColumns() { var dbPath = Path.Combine(Path.GetTempPath(), $"wct_legacy_{Guid.NewGuid():N}.db"); try { // 1. 构造一个"旧 schema"库:test_scenario 无 manufacturer_id/car_model_id/foreign_object_id, // 无 __EFMigrationsHistory(模拟 EnsureCreated 既有库) CreateLegacyEnsureCreatedDatabase(dbPath); // 2. 运行升级 await using var context = CreateContext(dbPath); await DatabaseMigrationInitializer.MigrateOrUpgradeAsync(context); // 3. 断言新维度表与 test_scenario 的新 FK 列已补齐 Assert.Multiple(async () => { Assert.That(await TableExistsAsync(context, "manufacturer"), Is.True); Assert.That(await TableExistsAsync(context, "car_model"), Is.True); Assert.That(await TableExistsAsync(context, "foreign_object"), Is.True); Assert.That(await ColumnExistsAsync(context, "test_scenario", "manufacturer_id"), Is.True); Assert.That(await ColumnExistsAsync(context, "test_scenario", "car_model_id"), Is.True); Assert.That(await ColumnExistsAsync(context, "test_scenario", "foreign_object_id"), Is.True); }); } finally { Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); if (File.Exists(dbPath)) File.Delete(dbPath); } } private static WctMinerDbContext CreateContext(string dbPath) { var dir = Path.GetDirectoryName(dbPath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); var options = new DbContextOptionsBuilder() .UseSqlite($"Data Source={dbPath};Pooling=False") .Options; return new WctMinerDbContext(options); } private static void CreateLegacyEnsureCreatedDatabase(string dbPath) { using var connection = new Microsoft.Data.Sqlite.SqliteConnection($"Data Source={dbPath};Pooling=False"); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = """ CREATE TABLE test_scenario ( id TEXT NOT NULL PRIMARY KEY, tx_panel_id TEXT, tx_hardware_id TEXT, tx_software_id TEXT, rx_type_id TEXT, test_purpose TEXT, test_date TEXT, test_sequence INTEGER, is_deleted INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE tx_panel (id TEXT NOT NULL PRIMARY KEY, name TEXT, is_deleted INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now'))); """; cmd.ExecuteNonQuery(); } private static async Task TableExistsAsync(WctMinerDbContext context, string tableName) { var count = await context.Database.SqlQueryRaw( $"SELECT count(*) AS Value FROM sqlite_master WHERE type='table' AND name = '{tableName}'").FirstOrDefaultAsync(); return count > 0; } private static async Task ColumnExistsAsync(WctMinerDbContext context, string table, string column) { var count = await context.Database.SqlQueryRaw( $"SELECT count(*) AS Value FROM pragma_table_info('{table}') WHERE name = '{column}'").FirstOrDefaultAsync(); return count > 0; } }