Files
ttstd-infinite-canvas/backend/internal/repository/text_replay_test.go
dlh 35dee568c9 feat(create): 创作对话文本模式接入 text-replay 持久化与回放
把后端既有但未接线的 text-replay 能力在前端打通:前端直连模型生成正文时,
把生成过程/结果持久化到后端任务,支持刷新重放与任务中心可见。

后端(最小配套,text-replay 本为前端自管设计):
- 新增 TaskStatusTextReplay 任务状态:不排入 worker 队列、不计费、不执行,
  仅作为正文增量(text-deltas)的存储容器;
- CreateTask 识别 input.replay=true 的请求,走 createTextReplayTask 轻量创建;
- 新增 POST /tasks/:id/text-replay-complete: 写入最终正文、置为 succeeded、
  触发 text-deltas 归并,使 queryTaskTextReplay 能读到 finalText。

前端:
- 新增 creation-text-replay.ts 发布器: start 建任务、publish 节流上报增量、
  finish 存最终全文,失败静默降级不阻塞生成;
- create 文本模式生成时接入发布器,正文流不经展示改动、额外持久化到后端。

测试: 新增 isTextReplayTaskRequest 纯函数测(本地)、text-replay 闭环仓测
(建任务后写增量 -> complete -> succeeded + finalText,CI/cgo 验证)。
build/vet/tsc 全绿。
2026-08-19 22:04:41 +08:00

92 lines
2.9 KiB
Go
Raw 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 repository
import (
"testing"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 验证 text-replay 闭环:text_replay 任务可写增量,完成后置为 succeeded 并写入最终正文。
func TestTextReplayLifecycle(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file:text-replay-lifecycle?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.Task{}, &model.TaskTextDelta{}); err != nil {
t.Fatal(err)
}
task := model.Task{
ID: "replay-1", UserID: "user-1", Type: "text", Prompt: "写一段剧本",
Status: model.TaskStatusTextReplay, Stage: "文本持久化(前端自管)",
}
if err := db.Create(&task).Error; err != nil {
t.Fatal(err)
}
repo := New(db)
now := time.Now()
limits := TextReplayLimits{MaxTaskBytes: 1 << 20, MaxUserBytes: 1 << 20, MaxTaskEvents: 100}
// text_replay 状态应允许写入增量(未结束)
if _, err := repo.AppendTaskTextDelta("user-1", "replay-1", "第一段内容", now.Add(time.Hour), limits); err != nil {
t.Fatalf("text_replay 任务写入增量失败: %v", err)
}
if _, err := repo.AppendTaskTextDelta("user-1", "replay-1", "第二段内容", now.Add(time.Hour), limits); err != nil {
t.Fatalf("text_replay 任务写入增量失败: %v", err)
}
// 完成:写入最终正文并置为 succeeded
resultJSON := `{"mode":"text","text":"第一段内容第二段内容"}`
completed, err := repo.CompleteTextReplayTask("user-1", "replay-1", resultJSON, now)
if err != nil {
t.Fatal(err)
}
if !completed {
t.Fatal("expected complete to succeed")
}
stored, err := repo.Task("replay-1")
if err != nil {
t.Fatal(err)
}
if stored.Status != model.TaskStatusSucceeded {
t.Fatalf("expected status succeeded, got %s", stored.Status)
}
if stored.ResultJSON != resultJSON {
t.Fatalf("unexpected result_json: %s", stored.ResultJSON)
}
if stored.CompletedAt == nil {
t.Fatal("expected completed_at set")
}
// 已完成的 text_replay 任务不能再写入增量(进入 closed)
if _, err := repo.AppendTaskTextDelta("user-1", "replay-1", "追加", now.Add(time.Hour), limits); err == nil {
t.Fatal("expected ErrTextReplayClosed after completion")
}
// 重复完成应返回 completed=false(状态已不是 text_replay)
again, err := repo.CompleteTextReplayTask("user-1", "replay-1", resultJSON, now.Add(time.Minute))
if err != nil {
t.Fatal(err)
}
if again {
t.Fatal("expected second complete to be a no-op")
}
// 非 text_replay 任务不能完成
other := model.Task{ID: "queued-1", UserID: "user-1", Type: "text", Status: model.TaskStatusQueued}
if err := db.Create(&other).Error; err != nil {
t.Fatal(err)
}
done, err := repo.CompleteTextReplayTask("user-1", "queued-1", resultJSON, now)
if err != nil {
t.Fatal(err)
}
if done {
t.Fatal("queued task must not be completable as text_replay")
}
}