package db import ( "context" "testing" ) // documentMigrationID 是文档引用列的迁移 id(design/24 §2.4)。 const documentMigrationID = "0015_chat_message_document" // TestMigrateDocumentColumnReversible 验证 0015(chat_messages.document,文档 // 引用列)可上下翻滚:up 建列、重复 up 是空操作、down 删列、再 up 恢复。 // 它用自己的内存库,绝不碰 data/cyrene.db。 func TestMigrateDocumentColumnReversible(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", "document") { t.Fatal("chat_messages.document 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", "document") { t.Fatal("chat_messages.document should still exist after idempotent re-run") } // 恰好回滚到 0015(含它之后新加的迁移),因此日后再加迁移也不会让本测试失效。 ids := upMigrationIDs(t) idx := -1 for i, id := range ids { if id == documentMigrationID { idx = i } } if idx < 0 { t.Fatalf("%s not found in %v", documentMigrationID, 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", "document") { t.Fatal("chat_messages.document should be dropped by the 0015 down") } // 重新 up 恢复端到端可用:默认空串意味着「这条消息没有附件」。 if err := MigrateUp(ctx, conn, testLogger()); err != nil { t.Fatalf("re-MigrateUp: %v", err) } if !columnExists(t, ctx, conn, "chat_messages", "document") { t.Fatal("chat_messages.document 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 document: %v", err) } var doc string if err := conn.QueryRowContext(ctx, `SELECT document FROM chat_messages WHERE id = ?`, "m1").Scan(&doc); err != nil { t.Fatalf("select document: %v", err) } if doc != "" { t.Fatalf("document = %q, want the empty default", doc) } }