// Tests for the /rewind command wiring: listing restore points and restoring // files + conversation. The file-snapshot journal itself is tested in // agenttool; here we exercise runRewind's REPL-level behavior over a replDeps. package repl import ( "bytes" "os" "path/filepath" "strings" "testing" "github.com/smallnest/pigo/internal/agentcore" "github.com/smallnest/pigo/internal/agenttool" ) // /rewind with no argument lists the committed restore points. func TestREPLRewindListsPoints(t *testing.T) { p := &replProvider{reply: "hi"} deps, _ := newTestDeps(t, p) deps.snap = agenttool.NewFileSnapshotRecorder() dir := t.TempDir() f := filepath.Join(dir, "a.txt") if err := os.WriteFile(f, []byte("v0"), 0o644); err != nil { t.Fatal(err) } deps.snap.Record(f) deps.snap.Commit("", "add feature X") var out bytes.Buffer runRewind(&out, &deps, "/rewind") got := out.String() if !strings.Contains(got, "restore points") || !strings.Contains(got, "add feature X") { t.Errorf("listing missing expected content:\n%s", got) } } // /rewind N restores the file to its baseline and resets the conversation when // the point's leaf is empty (it was the session's first turn). func TestREPLRewindRestoresFiles(t *testing.T) { p := &replProvider{reply: "hi"} deps, _ := newTestDeps(t, p) deps.snap = agenttool.NewFileSnapshotRecorder() deps.agentCtx.Messages = agentcore.MessageList{ agentcore.UserMessage{RoleField: agentcore.RoleUser}, } deps.persisted = 1 dir := t.TempDir() f := filepath.Join(dir, "a.txt") if err := os.WriteFile(f, []byte("original"), 0o644); err != nil { t.Fatal(err) } deps.snap.Record(f) // baseline "original" if err := os.WriteFile(f, []byte("changed"), 0o644); err != nil { t.Fatal(err) } deps.snap.Commit("", "edit a.txt") var out bytes.Buffer runRewind(&out, &deps, "/rewind 1") if data, _ := os.ReadFile(f); string(data) != "original" { t.Errorf("file not restored: got %q, want original", string(data)) } if len(deps.agentCtx.Messages) != 0 { t.Errorf("conversation not reset: %d messages remain", len(deps.agentCtx.Messages)) } if len(deps.snap.Points()) != 0 { t.Errorf("journal not truncated after rewind") } if !strings.Contains(out.String(), "rewound to before point 1") { t.Errorf("missing confirmation:\n%s", out.String()) } } // /rewind is unavailable when file tools are disabled (nil recorder). func TestREPLRewindDisabled(t *testing.T) { p := &replProvider{reply: "hi"} deps, _ := newTestDeps(t, p) deps.snap = nil var out bytes.Buffer runRewind(&out, &deps, "/rewind") if !strings.Contains(out.String(), "unavailable") { t.Errorf("want unavailable message, got:\n%s", out.String()) } }