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