返回 DeepSeek-Reasonix
projection_bind_test.go
根目录 / internal / control / projection_bind_test.go
1 package control
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7 "testing"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/event"
11 "reasonix/internal/provider"
12 "reasonix/internal/tool"
13 )
14
15 func TestNewSessionRebindsProjectionSidecarPath(t *testing.T) {
16 dir := t.TempDir()
17 oldPath := agent.NewSessionPath(dir, "old")
18 sess := agent.NewSession("sys")
19 sess.Add(provider.Message{Role: provider.RoleUser, Content: "old work"})
20 if err := sess.Save(oldPath); err != nil {
21 t.Fatal(err)
22 }
23 // Seed an old projection sidecar.
24 if err := agent.SaveCompactionState(oldPath, agent.CompactionState{
25 SchemaVersion: 1,
26 Projection: agent.ContextProjection{
27 Messages: []provider.Message{{Role: provider.RoleSystem, Content: "sys"}},
28 CoveredCount: 1,
29 },
30 PromptCacheKey: "ws|old|model",
31 }); err != nil {
32 t.Fatal(err)
33 }
34
35 exec := agent.New(nil, nil, agent.NewSession("sys"), agent.Options{
36 ContextWindow: 1000,
37 SessionPath: oldPath,
38 ModelRef: "test/model",
39 WorkspaceID: "ws",
40 }, event.Discard)
41 c := newOwnedTestController(t, Options{Executor: exec, SessionDir: dir, Label: "test", DisableColdResumePrune: true})
42 c.Resume(sess, oldPath)
43 if got := exec.SessionPath(); got != oldPath {
44 t.Fatalf("after resume SessionPath = %q, want %q", got, oldPath)
45 }
46
47 if err := c.NewSession(); err != nil {
48 t.Fatalf("NewSession: %v", err)
49 }
50 newPath := c.SessionPath()
51 if newPath == "" || newPath == oldPath {
52 t.Fatalf("NewSession path = %q, want a distinct path", newPath)
53 }
54 if got := exec.SessionPath(); got != newPath {
55 t.Fatalf("executor SessionPath after NewSession = %q, want %q", got, newPath)
56 }
57 // Old sidecar must remain; new path must not inherit the old projection body.
58 if _, ok, err := agent.LoadCompactionState(oldPath); err != nil || !ok {
59 t.Fatalf("old sidecar should remain: ok=%v err=%v", ok, err)
60 }
61 if st, ok, err := agent.LoadCompactionState(newPath); err != nil {
62 t.Fatalf("load new sidecar: %v", err)
63 } else if ok && len(st.Projection.Messages) > 0 {
64 t.Fatalf("new session should not load old projection: %+v", st.Projection)
65 }
66 // Writing a projection after rotation must land on the new path.
67 if err := agent.SaveCompactionState(exec.SessionPath(), agent.CompactionState{
68 SchemaVersion: 1,
69 Projection: agent.ContextProjection{
70 Messages: []provider.Message{{Role: provider.RoleSystem, Content: "sys"}},
71 CoveredCount: 1,
72 CoveredPrefixHash: "x",
73 },
74 }); err != nil {
75 t.Fatal(err)
76 }
77 if _, ok, err := agent.LoadCompactionState(newPath); err != nil || !ok {
78 t.Fatalf("projection must be readable at new path: ok=%v err=%v", ok, err)
79 }
80 // Ensure we did not overwrite old sidecar content with the new key.
81 old, ok, err := agent.LoadCompactionState(oldPath)
82 if err != nil || !ok {
83 t.Fatalf("old sidecar: ok=%v err=%v", ok, err)
84 }
85 if old.PromptCacheKey != "ws|old|model" {
86 t.Fatalf("old sidecar polluted: %+v", old)
87 }
88 }
89
90 func TestClearSessionRebindsProjectionSidecarPath(t *testing.T) {
91 dir := t.TempDir()
92 path := agent.NewSessionPath(dir, "s")
93 sess := agent.NewSession("sys")
94 sess.Add(provider.Message{Role: provider.RoleUser, Content: "hi"})
95 if err := sess.Save(path); err != nil {
96 t.Fatal(err)
97 }
98 exec := agent.New(nil, nil, agent.NewSession("sys"), agent.Options{SessionPath: path}, event.Discard)
99 c := newOwnedTestController(t, Options{Executor: exec, SessionDir: dir, Label: "test", DisableColdResumePrune: true})
100 c.Resume(sess, path)
101
102 if err := c.ClearSession(); err != nil {
103 t.Fatalf("ClearSession: %v", err)
104 }
105 if got := exec.SessionPath(); got == path || got == "" {
106 t.Fatalf("ClearSession left executor path at %q", got)
107 }
108 if exec.SessionPath() != c.SessionPath() {
109 t.Fatalf("executor=%q controller=%q", exec.SessionPath(), c.SessionPath())
110 }
111 }
112
113 func TestBranchRebindsProjectionSidecarPath(t *testing.T) {
114 dir := schemaOneTempDir(t)
115 path := agent.NewSessionPath(dir, "main")
116 sess := agent.NewSession("sys")
117 sess.Add(provider.Message{Role: provider.RoleUser, Content: "task"})
118 sess.Add(provider.Message{Role: provider.RoleAssistant, Content: "ok"})
119 for i := range 30 {
120 id := fmt.Sprintf("bulk-%d", i)
121 sess.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: id, Name: "read_file", Arguments: "{}"}}})
122 sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "read_file", Content: strings.Repeat("line\n", 200)})
123 }
124 if err := sess.Save(path); err != nil {
125 t.Fatal(err)
126 }
127 if _, err := agent.EnsureBranchMeta(path); err != nil {
128 t.Fatal(err)
129 }
130 prov := &scriptedTurns{turns: [][]provider.Chunk{textTurn("summary")}}
131 exec := agent.New(prov, tool.NewRegistry(), agent.NewSession("sys"), agent.Options{
132 SessionPath: path, ContextWindow: 10_000, CompactRatio: 0.8, RecentKeep: 2,
133 }, event.Discard)
134 c := newOwnedTestController(t, Options{Executor: exec, SessionDir: dir, Label: "test", DisableColdResumePrune: true})
135 c.Resume(sess, path)
136 if err := exec.CompactNow(context.Background(), ""); err != nil {
137 t.Fatalf("CompactNow: %v", err)
138 }
139 before := exec.ContextMaintenanceSnapshot()
140 if before.ProjectionVersion == 0 {
141 t.Fatal("branch fixture installed no projection")
142 }
143
144 newPath, err := c.Branch("side")
145 if err != nil {
146 t.Fatalf("Branch: %v", err)
147 }
148 if newPath == path {
149 t.Fatal("Branch returned the same path")
150 }
151 if got := exec.SessionPath(); got != newPath {
152 t.Fatalf("executor SessionPath after Branch = %q, want %q", got, newPath)
153 }
154 if got := c.SessionPath(); got != newPath {
155 t.Fatalf("controller SessionPath after Branch = %q, want %q", got, newPath)
156 }
157 after := exec.ContextMaintenanceSnapshot()
158 if after.ProjectionVersion != before.ProjectionVersion {
159 t.Fatalf("branch projection version %d -> %d", before.ProjectionVersion, after.ProjectionVersion)
160 }
161 if _, ok, err := agent.LoadCompactionState(newPath); err != nil || !ok {
162 t.Fatalf("branch projection sidecar: ok=%v err=%v", ok, err)
163 }
164 }
165
165 lines GO