package state import ( "os" "testing" "github.com/voocel/ainovel-cli/domain" ) func TestSaveAndLoadRunMeta(t *testing.T) { dir := t.TempDir() store := NewStore(dir) meta := domain.RunMeta{ StartedAt: "2026-03-07T10:00:00+08:00", Style: "fantasy", Model: "gpt-4o", } if err := store.SaveRunMeta(meta); err != nil { t.Fatalf("SaveRunMeta: %v", err) } loaded, err := store.LoadRunMeta() if err != nil { t.Fatalf("LoadRunMeta: %v", err) } if loaded.Style != "fantasy" { t.Errorf("style mismatch: %s", loaded.Style) } if loaded.Model != "gpt-4o" { t.Errorf("model mismatch: %s", loaded.Model) } } func TestLoadRunMeta_Empty(t *testing.T) { dir := t.TempDir() store := NewStore(dir) meta, err := store.LoadRunMeta() if err != nil { t.Fatalf("LoadRunMeta on empty: %v", err) } if meta != nil { t.Fatalf("expected nil, got %+v", meta) } } func TestAppendSteerEntry(t *testing.T) { dir := t.TempDir() store := NewStore(dir) // 首次追加(meta/run.json 不存在) e1 := domain.SteerEntry{Input: "主角改成女性", Timestamp: "2026-03-07T10:01:00+08:00"} if err := store.AppendSteerEntry(e1); err != nil { t.Fatalf("AppendSteerEntry 1: %v", err) } meta, _ := store.LoadRunMeta() if len(meta.SteerHistory) != 1 { t.Fatalf("expected 1 entry, got %d", len(meta.SteerHistory)) } if meta.SteerHistory[0].Input != "主角改成女性" { t.Errorf("input mismatch: %s", meta.SteerHistory[0].Input) } // 追加第二条 e2 := domain.SteerEntry{Input: "加入反转", Timestamp: "2026-03-07T10:02:00+08:00"} _ = store.AppendSteerEntry(e2) meta, _ = store.LoadRunMeta() if len(meta.SteerHistory) != 2 { t.Fatalf("expected 2 entries, got %d", len(meta.SteerHistory)) } } func TestAppendSteerEntry_PreservesExistingMeta(t *testing.T) { dir := t.TempDir() store := NewStore(dir) // 先保存 RunMeta _ = store.SaveRunMeta(domain.RunMeta{ StartedAt: "2026-03-07T10:00:00+08:00", Style: "suspense", Model: "gpt-4o", }) // 追加 Steer 不应覆盖其他字段 _ = store.AppendSteerEntry(domain.SteerEntry{Input: "test", Timestamp: "now"}) meta, _ := store.LoadRunMeta() if meta.Style != "suspense" { t.Errorf("style should be preserved, got %s", meta.Style) } if meta.Model != "gpt-4o" { t.Errorf("model should be preserved, got %s", meta.Model) } if len(meta.SteerHistory) != 1 { t.Errorf("expected 1 steer entry, got %d", len(meta.SteerHistory)) } } func TestInitRunMeta_PreservesHistory(t *testing.T) { dir := t.TempDir() store := NewStore(dir) // 先建立带历史的 RunMeta _ = store.SaveRunMeta(domain.RunMeta{ StartedAt: "old", Style: "fantasy", Model: "old-model", SteerHistory: []domain.SteerEntry{{Input: "历史干预", Timestamp: "ts"}}, PendingSteer: "待处理", }) // InitRunMeta 应保留 SteerHistory 和 PendingSteer _ = store.InitRunMeta("suspense", "new-model") meta, _ := store.LoadRunMeta() if meta.Style != "suspense" { t.Errorf("style should be updated, got %s", meta.Style) } if meta.Model != "new-model" { t.Errorf("model should be updated, got %s", meta.Model) } if len(meta.SteerHistory) != 1 || meta.SteerHistory[0].Input != "历史干预" { t.Errorf("steer history should be preserved, got %v", meta.SteerHistory) } if meta.PendingSteer != "待处理" { t.Errorf("pending steer should be preserved, got %s", meta.PendingSteer) } } func TestSetAndClearPendingSteer(t *testing.T) { dir := t.TempDir() store := NewStore(dir) // 设置 PendingSteer if err := store.SetPendingSteer("主角改成女性"); err != nil { t.Fatalf("SetPendingSteer: %v", err) } meta, _ := store.LoadRunMeta() if meta.PendingSteer != "主角改成女性" { t.Errorf("expected pending steer, got %s", meta.PendingSteer) } // 清除 if err := store.ClearPendingSteer(); err != nil { t.Fatalf("ClearPendingSteer: %v", err) } meta, _ = store.LoadRunMeta() if meta.PendingSteer != "" { t.Errorf("expected empty pending steer, got %s", meta.PendingSteer) } } func TestClearPendingSteer_Noop(t *testing.T) { dir := t.TempDir() store := NewStore(dir) // 空 meta 上调用不报错 if err := store.ClearPendingSteer(); err != nil { t.Fatalf("ClearPendingSteer on empty: %v", err) } } func TestSaveCheckpoint(t *testing.T) { dir := t.TempDir() store := NewStore(dir) _ = store.InitProgress("test", 10) if err := store.SaveCheckpoint("ch01-commit"); err != nil { t.Fatalf("SaveCheckpoint: %v", err) } // 验证 checkpoint 目录下有文件 entries, err := os.ReadDir(dir + "/meta/checkpoints") if err != nil { t.Fatalf("read checkpoints dir: %v", err) } if len(entries) != 1 { t.Fatalf("expected 1 checkpoint, got %d", len(entries)) } }