package session import ( "os" "path/filepath" "testing" ) // TestRunStateRoundTrip:写读往返。 func TestRunStateRoundTrip(t *testing.T) { t.Setenv("HOME", t.TempDir()) want := RunState{RunID: "run-1", Status: "running", StartedAt: 1234} if err := SaveRunState("sess1", want); err != nil { t.Fatal(err) } got, ok := LoadRunState("sess1") if !ok { t.Fatal("读不回刚写的状态") } if got.RunID != want.RunID || got.Status != want.Status || got.StartedAt != want.StartedAt { t.Errorf("往返不一致: %+v vs %+v", got, want) } if _, ok := LoadRunState("nope"); ok { t.Error("不存在的会话不该返回状态") } } // TestReconcileRunStates:进程刚起来时把遗留的 running 认成 interrupted, // 其它状态原样保留(不能把上次正常完成的 done 也改掉)。 func TestReconcileRunStates(t *testing.T) { t.Setenv("HOME", t.TempDir()) if err := os.MkdirAll(Dir(), 0o755); err != nil { t.Fatal(err) } cases := []RunState{ {RunID: "a", Status: "running", StartedAt: 1}, {RunID: "b", Status: "done", StartedAt: 1, EndedAt: 2}, {RunID: "c", Status: "interrupted", StartedAt: 1}, } for i, st := range cases { if err := SaveRunState(string(rune('a'+i)), st); err != nil { t.Fatal(err) } } // 损坏的文件不该让 reconcile 崩掉 if err := os.WriteFile(filepath.Join(Dir(), "broken.run.json"), []byte("{not json"), 0o644); err != nil { t.Fatal(err) } // 非 run.json 的文件不该被动 if err := os.WriteFile(filepath.Join(Dir(), "keep.jsonl"), []byte("x"), 0o644); err != nil { t.Fatal(err) } if n := ReconcileRunStates(); n != 1 { t.Errorf("改写条数 = %d, want 1(只有 running 那条)", n) } if st, _ := LoadRunState("a"); st.Status != "interrupted" { t.Errorf("running 应被改成 interrupted,实际 %q", st.Status) } if st, _ := LoadRunState("b"); st.Status != "done" { t.Errorf("done 不该被改,实际 %q", st.Status) } if st, _ := LoadRunState("c"); st.Status != "interrupted" { t.Errorf("已经是 interrupted 的不该变,实际 %q", st.Status) } if _, err := os.Stat(filepath.Join(Dir(), "keep.jsonl")); err != nil { t.Errorf("非 run.json 文件被动过了: %v", err) } }