diff --git a/crates/df-storage/src/migrations.rs b/crates/df-storage/src/migrations.rs index a079974..9681870 100644 --- a/crates/df-storage/src/migrations.rs +++ b/crates/df-storage/src/migrations.rs @@ -1820,4 +1820,95 @@ mod tests { .unwrap(); assert_eq!(v, 27); } + + // ============================================================ + // 全量迁移测试 — 新库从零跑完整 V1-V37 路径 + // ------------------------------------------------------------ + // 目的:某 migrate_vN 的 SQL 手滑写错(列名/类型/缺索引/缺表)只能等运行时暴露, + // 此测试一次性覆盖全部迁移路径。任何一条迁移 SQL 写错、列名拼错、缺建表 + // 都会被这里捕获,不必等到应用启动真机才报错。 + // ============================================================ + + /// 辅助:断言表存在且 PRAGMA table_info 返回的列数 > 0(即表非空、有列定义)。 + fn assert_table_has_columns(conn: &Connection, table: &str) { + let count: i64 = conn + .query_row( + &format!("SELECT COUNT(*) FROM pragma_table_info('{}')", table), + [], + |r| r.get(0), + ) + .unwrap_or_else(|e| panic!("查 {} 列信息失败: {}", table, e)); + assert!( + count > 0, + "表 {} 应存在且至少 1 列(实际 {} 列)—— 可能 migrate_vN 建表 SQL 写错或被遗漏", + table, + count + ); + } + + /// 全量迁移:新库从零跑完 V1-V37,验证关键表齐全 + 列数 > 0 + 关键列存在。 + /// + /// 覆盖至少:task / ai_conversations / ai_messages / ai_tool_executions / + /// conversation_checkpoints / ai_providers / projects / ideas。 + /// 抽查关键列:ai_providers.enabled/weight、conversation_checkpoints.snapshot、 + /// tasks.idea_id(这些列由不同 vN 加,任一漏加此处失败)。 + #[tokio::test] + async fn test_full_migration_on_fresh_db() { + // 用 Database::open_in_memory 打开新库,内部自动跑 migrations::run() 全量迁移 + let db = crate::db::Database::open_in_memory() + .await + .expect("新库应能跑完全量迁移"); + + let conn = db.conn(); + let conn = conn.lock().await; + + // 1. 关键表存在且列数 > 0 + for table in [ + "tasks", + "ai_conversations", + "ai_messages", + "ai_tool_executions", + "conversation_checkpoints", + "ai_providers", + "projects", + "ideas", + ] { + assert_table_has_columns(&conn, table); + } + + // 2. 抽查关键列存在(跨多个 vN 加的列,任一漏加此处失败) + // ai_providers.enabled / weight(V19 ALTER) + assert!( + column_exists(&conn, "ai_providers", "enabled"), + "ai_providers.enabled 列缺失(V19 加)" + ); + assert!( + column_exists(&conn, "ai_providers", "weight"), + "ai_providers.weight 列缺失(V19 加)" + ); + // conversation_checkpoints.snapshot(V37 建表) + assert!( + column_exists(&conn, "conversation_checkpoints", "snapshot"), + "conversation_checkpoints.snapshot 列缺失(V37 建表)" + ); + // tasks.idea_id(V1 建表已带,V20 老库兜底——新库应有) + assert!( + column_exists(&conn, "tasks", "idea_id"), + "tasks.idea_id 列缺失(V1 建表已带)" + ); + + // 3. schema_version 应推进到 37(全量迁移成功落版本号) + let max_version: i64 = conn + .query_row( + "SELECT COALESCE(MAX(version), 0) FROM schema_version", + [], + |r| r.get(0), + ) + .expect("查 schema_version 应成功"); + assert_eq!( + max_version, 37, + "全量迁移后 schema_version 应为 37(实际 {}),说明某条 migrate_vN 链路断在中间", + max_version + ); + } }