resident_artifact_recovery_test.go RawHistoryFolder
1package subagents
2
3import (
4 "os"
5 "path/filepath"
6 "reflect"
7 "testing"
8)
9
10func TestReconcileResidentJournalRestoresCapturedArtifacts(t *testing.T) {
11 for _, damage := range []string{"missing", "corrupt"} {
12 for _, captured := range []bool{false, true} {
13 name := damage + "/without-capture"
14 if captured {
15 name = damage + "/with-capture"
16 }
17 t.Run(name, func(t *testing.T) {
18 root := t.TempDir()
19 spec := ResidentChildSpec{ID: "artifact-recovery", SessionID: "session", InitialTurnID: "initial", Provider: "openai", Model: "test"}
20 journal, err := OpenResidentJournal(root, spec.ID)
21 if err != nil {
22 t.Fatal(err)
23 }
24 t.Cleanup(func() { _ = journal.Close() })
25 if err := journal.Accept(spec, "inspect and change files"); err != nil {
26 t.Fatal(err)
27 }
28 if err := journal.RecordTurnStarted(spec, spec.InitialTurnID); err != nil {
29 t.Fatal(err)
30 }
31 var capture *WorkspaceCapture
32 if captured {
33 capture = &WorkspaceCapture{Patch: []byte("partial patch\n"), ChangedFiles: []string{"README.md", "file with spaces.go"}}
34 } else if err := writeResidentPatch(journal.Dir(), []byte("stale patch from an earlier turn\n")); err != nil {
35 t.Fatal(err)
36 }
37 if err := journal.RecordTurnFinishedWithCapture(spec, spec.InitialTurnID, ErrBudgetExceeded, capture); err != nil {
38 t.Fatal(err)
39 }
40 original, err := journal.Result()
41 if err != nil {
42 t.Fatal(err)
43 }
44 if captured && (original.PatchRef != PatchRef(spec.ID) || !reflect.DeepEqual(original.ChangedFiles, capture.ChangedFiles)) {
45 t.Fatalf("capture was not published: %#v", original)
46 }
47 if err := journal.Close(); err != nil {
48 t.Fatal(err)
49 }
50 dir := filepath.Join(root, spec.ID)
51 transcriptPath := filepath.Join(dir, residentTranscriptName)
52 before, err := os.ReadFile(transcriptPath)
53 if err != nil {
54 t.Fatal(err)
55 }
56 resultPath := filepath.Join(dir, residentResultName)
57 if damage == "missing" {
58 err = os.Remove(resultPath)
59 } else {
60 err = os.WriteFile(resultPath, []byte("invalid JSON"), 0o600)
61 }
62 if err != nil {
63 t.Fatal(err)
64 }
65 for range 2 {
66 if _, err := ReconcileResidentJournal(dir); err != nil {
67 t.Fatal(err)
68 }
69 result, err := ReadResidentResult(resultPath)
70 if err != nil {
71 t.Fatal(err)
72 }
73 if result.State != ResidentBudgetExhausted || result.TurnID != spec.InitialTurnID || result.ErrorCode != residentErrorBudgetExhausted || result.Handoff == "" || result.PatchRef != original.PatchRef || !reflect.DeepEqual(result.ChangedFiles, original.ChangedFiles) {
74 t.Fatalf("reconstructed result = %#v, original = %#v", result, original)
75 }
76 }
77 after, err := os.ReadFile(transcriptPath)
78 if err != nil || string(after) != string(before) {
79 t.Fatalf("reconciliation modified transcript: %v", err)
80 }
81 if captured {
82 patch, err := os.ReadFile(filepath.Join(dir, residentPatchName))
83 if err != nil || string(patch) != string(capture.Patch) {
84 t.Fatalf("patch = %q, error = %v", patch, err)
85 }
86 }
87 })
88 }
89 }
90}