返回 DeepSeek-Reasonix
recovery_isolated_test.go
根目录 / internal / agent / recovery_isolated_test.go
1 package agent
2
3 import (
4 "fmt"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "reasonix/internal/provider"
10 )
11
12 func TestSaveConflictRecoveryBranchAtCapStaysBounded(t *testing.T) {
13 dir := t.TempDir()
14 path, stale := divergedSessionPair(t, dir, "session.jsonl")
15 stampRecoveryMeta(t, path, SessionRecoveryMaxDepth)
16
17 current := path
18 var firstPath string
19 for i := range 6 {
20 stale.Add(provider.Message{Role: provider.RoleUser, Content: fmt.Sprintf("tick %d", i)})
21 info, err := stale.SaveRecoveryBranch(RecoveryBranchOptions{OriginalPath: current})
22 if err != nil {
23 t.Fatalf("tick %d: SaveRecoveryBranch: %v", i, err)
24 }
25 if firstPath == "" {
26 firstPath = info.Path
27 if err := SetSessionInFlightTurn(firstPath, InFlightTurnMeta{StartMessageIndex: 2}); err != nil {
28 t.Fatalf("SetSessionInFlightTurn: %v", err)
29 }
30 } else if info.Path != firstPath {
31 t.Fatalf("tick %d rotated recovery path %q -> %q", i, firstPath, info.Path)
32 }
33 current = info.Path
34 }
35
36 meta, ok, err := LoadBranchMeta(current)
37 if err != nil || !ok {
38 t.Fatalf("LoadBranchMeta ok=%v err=%v", ok, err)
39 }
40 if meta.InFlightTurn == nil || meta.InFlightTurn.StartMessageIndex != 2 {
41 t.Fatalf("in-flight turn marker = %+v, want StartMessageIndex 2", meta.InFlightTurn)
42 }
43
44 matches, err := filepath.Glob(filepath.Join(dir, "*-recovery-*.jsonl"))
45 if err != nil {
46 t.Fatalf("glob: %v", err)
47 }
48 var isolated []string
49 for _, m := range matches {
50 if !strings.HasSuffix(m, ".events.jsonl") {
51 isolated = append(isolated, m)
52 }
53 }
54 if len(isolated) != 1 {
55 t.Fatalf("isolated copies = %d, want 1: %v", len(isolated), isolated)
56 }
57 }
58
59 func TestFixedWriterRecoverySessionPathUsesRootStem(t *testing.T) {
60 dir := t.TempDir()
61 root := fixedWriterRecoverySessionPath(filepath.Join(dir, "session.jsonl"))
62 fork := fixedWriterRecoverySessionPath(filepath.Join(dir, "session-recovery-abcd1234abcd1234.jsonl"))
63 if root != fork {
64 t.Fatalf("nested recovery name mapped to %q, want root path %q", fork, root)
65 }
66 if again := fixedWriterRecoverySessionPath(root); again != root {
67 t.Fatalf("isolated copy is not a fixed point: %s -> %s", root, again)
68 }
69 suffix := strings.TrimPrefix(BranchID(root), "session-recovery-")
70 if len(suffix) != 16 {
71 t.Fatalf("recovery suffix length = %d, want 16 for legacy discovery: %q", len(suffix), suffix)
72 }
73 }
74
75 func TestRecoveryLaneCollisionPreservesIndependentTranscript(t *testing.T) {
76 dir := t.TempDir()
77 original := filepath.Join(dir, "session.jsonl")
78 first := NewSession("sys")
79 first.Add(provider.Message{Role: provider.RoleUser, Content: "first"})
80 first.Add(provider.Message{Role: provider.RoleAssistant, Content: "one"})
81 firstInfo, err := first.SaveConflictRecoveryBranch(RecoveryBranchOptions{OriginalPath: original})
82 if err != nil {
83 t.Fatal(err)
84 }
85
86 second := NewSession("sys")
87 second.Add(provider.Message{Role: provider.RoleUser, Content: "second"})
88 second.Add(provider.Message{Role: provider.RoleAssistant, Content: "two"})
89 // Force the allocator onto the first live session's lane. The save must
90 // rotate instead of replacing that independent history.
91 second.recoveryLane = first.recoveryLane
92 secondInfo, err := second.SaveConflictRecoveryBranch(RecoveryBranchOptions{OriginalPath: original})
93 if err != nil {
94 t.Fatal(err)
95 }
96 if secondInfo.Path == firstInfo.Path {
97 t.Fatalf("independent sessions shared recovery lane %q", firstInfo.Path)
98 }
99 kept, err := LoadSession(firstInfo.Path)
100 if err != nil {
101 t.Fatal(err)
102 }
103 if got := kept.Snapshot()[2].Content; got != "one" {
104 t.Fatalf("first recovery content = %q, want preserved", got)
105 }
106
107 second.Add(provider.Message{Role: provider.RoleUser, Content: "continued"})
108 second.Add(provider.Message{Role: provider.RoleAssistant, Content: "again"})
109 again, err := second.SaveConflictRecoveryBranch(RecoveryBranchOptions{OriginalPath: secondInfo.Path})
110 if err != nil {
111 t.Fatal(err)
112 }
113 if again.Path != secondInfo.Path {
114 t.Fatalf("owned recovery lane rotated: %q -> %q", secondInfo.Path, again.Path)
115 }
116 }
117
117 lines GO