package db import ( "context" "testing" ) // turnIDMigrationID 是回合标识列的迁移 id(design/15 §2.5.9)。 const turnIDMigrationID = "0018_chat_message_turn_id" // TestMigrateTurnIDColumnReversible 验证 0018(chat_messages.turn_id,回合标识列) // 可上下翻滚:up 建列、重复 up 是空操作、down 删列、再 up 恢复。它用自己的内存库, // 绝不碰 data/cyrene.db。 func TestMigrateTurnIDColumnReversible(t *testing.T) { t.Parallel() ctx := context.Background() conn := openMemory(t) if err := MigrateUp(ctx, conn, testLogger()); err != nil { t.Fatalf("MigrateUp: %v", err) } if !columnExists(t, ctx, conn, "chat_messages", "turn_id") { t.Fatal("chat_messages.turn_id should exist after MigrateUp") } // 重复 up:已记录的迁移被跳过,列仍在且不报错(幂等)。 if err := MigrateUp(ctx, conn, testLogger()); err != nil { t.Fatalf("MigrateUp second call: %v", err) } if !columnExists(t, ctx, conn, "chat_messages", "turn_id") { t.Fatal("chat_messages.turn_id should still exist after idempotent re-run") } // 恰好回滚到 0018(含它之后新加的迁移),因此日后再加迁移也不会让本测试失效。 ids := upMigrationIDs(t) idx := -1 for i, id := range ids { if id == turnIDMigrationID { idx = i } } if idx < 0 { t.Fatalf("%s not found in %v", turnIDMigrationID, ids) } if err := MigrateDown(ctx, conn, len(ids)-idx, testLogger()); err != nil { t.Fatalf("MigrateDown(%d): %v", len(ids)-idx, err) } if columnExists(t, ctx, conn, "chat_messages", "turn_id") { t.Fatal("chat_messages.turn_id should be dropped by the 0018 down") } // 重新 up 恢复端到端可用:默认空串意味着「这一行不属于任何已知回合」。 if err := MigrateUp(ctx, conn, testLogger()); err != nil { t.Fatalf("re-MigrateUp: %v", err) } if !columnExists(t, ctx, conn, "chat_messages", "turn_id") { t.Fatal("chat_messages.turn_id should exist again after re-MigrateUp") } if _, err := conn.ExecContext(ctx, `INSERT INTO chat_messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)`, "m1", "web", "user", "hi", 1000); err != nil { t.Fatalf("insert without turn id: %v", err) } var turnID string if err := conn.QueryRowContext(ctx, `SELECT turn_id FROM chat_messages WHERE id = ?`, "m1").Scan(&turnID); err != nil { t.Fatalf("select turn_id: %v", err) } if turnID != "" { t.Fatalf("turn_id = %q, want the empty default", turnID) } }