Files
ainovel-clients/state/run_meta_test.go
2026-03-13 00:19:21 +08:00

264 lines
6.5 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 state
import (
"fmt"
"os"
"sync"
"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",
Provider: "openrouter",
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.Provider != "openrouter" {
t.Errorf("provider mismatch: %s", loaded.Provider)
}
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 TestAppendSteerEntryConcurrent(t *testing.T) {
dir := t.TempDir()
store := NewStore(dir)
const workers = 32
var wg sync.WaitGroup
start := make(chan struct{})
for i := 0; i < workers; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
<-start
entry := domain.SteerEntry{
Input: fmt.Sprintf("steer-%02d", i),
Timestamp: fmt.Sprintf("ts-%02d", i),
}
if err := store.AppendSteerEntry(entry); err != nil {
t.Errorf("AppendSteerEntry(%d): %v", i, err)
}
}(i)
}
close(start)
wg.Wait()
meta, err := store.LoadRunMeta()
if err != nil {
t.Fatalf("LoadRunMeta: %v", err)
}
if meta == nil {
t.Fatal("expected run meta to exist")
}
if len(meta.SteerHistory) != workers {
t.Fatalf("expected %d steer entries, got %d", workers, len(meta.SteerHistory))
}
seen := make(map[string]struct{}, workers)
for _, entry := range meta.SteerHistory {
seen[entry.Input] = struct{}{}
}
if len(seen) != workers {
t.Fatalf("expected %d unique steer entries, got %d", workers, len(seen))
}
}
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",
Provider: "openrouter",
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.Provider != "openrouter" {
t.Errorf("provider should be preserved, got %s", meta.Provider)
}
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",
Provider: "openai",
Style: "fantasy",
Model: "old-model",
SteerHistory: []domain.SteerEntry{{Input: "历史干预", Timestamp: "ts"}},
PendingSteer: "待处理",
})
// InitRunMeta 应保留 SteerHistory 和 PendingSteer
_ = store.InitRunMeta("suspense", "openrouter", "new-model")
meta, _ := store.LoadRunMeta()
if meta.Style != "suspense" {
t.Errorf("style should be updated, got %s", meta.Style)
}
if meta.Provider != "openrouter" {
t.Errorf("provider should be updated, got %s", meta.Provider)
}
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 TestSetPlanningTier(t *testing.T) {
dir := t.TempDir()
store := NewStore(dir)
if err := store.SetPlanningTier(domain.PlanningTierLong); err != nil {
t.Fatalf("SetPlanningTier: %v", err)
}
meta, err := store.LoadRunMeta()
if err != nil {
t.Fatalf("LoadRunMeta: %v", err)
}
if meta == nil {
t.Fatal("expected run meta to exist")
}
if meta.PlanningTier != domain.PlanningTierLong {
t.Fatalf("expected planning tier %q, got %q", domain.PlanningTierLong, meta.PlanningTier)
}
}
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))
}
}