Files
Cyrene_v2/internal/db/migrate_document_test.go
AskaEth 6b2b07cbef
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(runtime): document 内容类型与持久化(design/24 S2)
- model 新增 ContentTypeDocument + Content 的四字段 + DocumentMessage 构造器 +
  ValidateDocumentParts(文档部件只允许 RoleUser,非 user ⇒ ErrDocumentOnNonUser,fail-loud)。
- 两个编码器入口(buildMessages / buildAnthropicMessages)对文档部件报 ErrBadRequest:
  本期 document 永不抵达编码器,且绝不静默丢块。
- history:拆出 types.go(Message 增 Document 列),新增 document.go(DocumentRef 同时是
  落库 JSON 与 DTO 形状,6 字段含 status ⇒ §4.4 的「读不到要可见」;坏列回 unreadable 不静默)。
- chat_messages 加 document 列(迁移 0015 + NewStore 幂等前滚);historyMessage.Document DTO;
  session_persist 的 messageTextImage → messageTextImageDocument(保留窄视图以免牵连 steer 配对)。
- 只落「引用 + 统计」,绝不落抽取正文(TestPersistedDocumentHoldsNoBody 把全部列拼起来扫正文)。

验收(主会话独立复跑,非自报):隔离 worktree(HEAD + 本切片)go build/vet 绿、
全树 58 包 0 FAIL、linux/amd64 + darwin/arm64 交叉编译绿、18 条 Document* 测试 PASS。
变异独立复现:ValidateDocumentParts 开头 return nil ⇒ 非 user 负例红。
2026-10-04 17:07:24 +08:00

72 lines
2.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}