返回 DeepSeek-Reasonix
migrate_projection_test.go
根目录 / internal / session / migrate_projection_test.go
1 package session
2
3 import (
4 "encoding/json"
5 "fmt"
6 "path/filepath"
7 "reflect"
8 "strings"
9 "testing"
10
11 "reasonix/internal/agent"
12 "reasonix/internal/agent/testutil"
13 "reasonix/internal/event"
14 "reasonix/internal/provider"
15 "reasonix/internal/tool"
16 )
17
18 func legacyCompactionFixture(t *testing.T) (string, []provider.Message, []provider.Message) {
19 t.Helper()
20 path := filepath.Join(t.TempDir(), "sessions", "legacy.jsonl")
21 s := agent.NewSession("system")
22 for i := range 24 {
23 s.Add(provider.Message{Role: provider.RoleUser, Content: fmt.Sprintf("task %d", i)})
24 s.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("x", 2_000)})
25 }
26 s.Add(provider.Message{Role: provider.RoleUser, Content: "continue"})
27 s.Add(provider.Message{Role: provider.RoleAssistant, Content: "ready"})
28 if err := s.Save(path); err != nil {
29 t.Fatal(err)
30 }
31 exec := agent.New(testutil.NewMock("migration", testutil.Turn{Text: "durable summary"}), tool.NewRegistry(), s, agent.Options{
32 SessionPath: path, ContextWindow: 10_000, CompactRatio: .8,
33 }, event.Discard)
34 if err := exec.CompactNow(t.Context(), ""); err != nil {
35 t.Fatal(err)
36 }
37 return path, s.Snapshot(), provider.ModelMessages(exec.ModelHistorySnapshot())
38 }
39
40 func migratedProjection(t *testing.T, dir string) Projection {
41 t.Helper()
42 commits, err := Replay(dir, nil)
43 if err != nil {
44 t.Fatal(err)
45 }
46 projection, err := Project(commits)
47 if err != nil {
48 t.Fatal(err)
49 }
50 return projection
51 }
52
53 func TestMigrateLegacyPreservesValidContextProjection(t *testing.T) {
54 path, canonical, compacted := legacyCompactionFixture(t)
55 result, err := MigrateLegacy(t.Context(), path, filepath.Join(t.TempDir(), "sessions-v5"))
56 if err != nil {
57 t.Fatal(err)
58 }
59 projection := migratedProjection(t, result.TargetDir)
60 if !reflect.DeepEqual(projection.Messages, canonical) {
61 t.Fatal("migration changed canonical history")
62 }
63 if !reflect.DeepEqual(projection.ModelMessages, compacted) {
64 t.Fatal("migration discarded the valid compacted model projection")
65 }
66 }
67
68 func TestMigrateLegacyIgnoresInvalidProjectionWithDiagnostic(t *testing.T) {
69 path, canonical, _ := legacyCompactionFixture(t)
70 state, ok, err := agent.LoadCompactionState(path)
71 if err != nil || !ok {
72 t.Fatalf("load sidecar: ok=%v err=%v", ok, err)
73 }
74 state.Projection.CoveredPrefixHash = "does-not-match-canonical-history"
75 if err := agent.SaveCompactionState(path, state); err != nil {
76 t.Fatal(err)
77 }
78 result, err := MigrateLegacy(t.Context(), path, filepath.Join(t.TempDir(), "sessions-v5"))
79 if err != nil {
80 t.Fatal(err)
81 }
82 projection := migratedProjection(t, result.TargetDir)
83 if !reflect.DeepEqual(projection.ModelMessages, provider.ModelMessages(canonical)) {
84 t.Fatal("invalid projection replaced the canonical model history")
85 }
86 found := false
87 if err := VisitCommits(t.Context(), result.TargetDir, func(commit Commit) error {
88 for _, item := range commit.Events {
89 if item.Kind != "diagnostic" {
90 continue
91 }
92 var payload map[string]string
93 if err := json.Unmarshal(item.Payload, &payload); err != nil {
94 return err
95 }
96 found = found || payload["code"] == "legacy_context_projection_ignored"
97 }
98 return nil
99 }); err != nil {
100 t.Fatal(err)
101 }
102 if !found {
103 t.Fatal("invalid legacy projection did not leave a diagnostic")
104 }
105 }
106
107 func TestMigrateLegacyRepairsOnlyPristinePublishedTarget(t *testing.T) {
108 for _, advanced := range []bool{false, true} {
109 t.Run(fmt.Sprintf("advanced=%v", advanced), func(t *testing.T) {
110 path, canonical, compacted := legacyCompactionFixture(t)
111 targetRoot := filepath.Join(t.TempDir(), "sessions-v5")
112 oldImporter, err := freezeLegacyHead(t.Context(), path, "", false)
113 if err != nil {
114 t.Fatal(err)
115 }
116 oldImporter.modelMessages = nil
117 first, err := oldImporter.publish(t.Context(), targetRoot, CreateOptions{})
118 if err != nil {
119 t.Fatal(err)
120 }
121 if advanced {
122 target, err := OpenWithOptions(first.TargetDir, first.TargetID, OpenOptions{ExternalHistory: true})
123 if err != nil {
124 t.Fatal(err)
125 }
126 raw, _ := json.Marshal(map[string]any{"message": provider.Message{ID: "new-work", Role: provider.RoleUser, Content: "new work"}})
127 if _, err := target.Append(t.Context(), Batch{OperationID: "new-work", Events: []Event{{Kind: "message/complete", Payload: raw}}}); err != nil {
128 t.Fatal(err)
129 }
130 if _, err := target.Flush(t.Context()); err != nil {
131 t.Fatal(err)
132 }
133 if err := target.Close(t.Context()); err != nil {
134 t.Fatal(err)
135 }
136 }
137
138 currentImporter, err := freezeLegacyHead(t.Context(), path, "", false)
139 if err != nil {
140 t.Fatal(err)
141 }
142 second, err := currentImporter.publish(t.Context(), targetRoot, CreateOptions{})
143 if err != nil {
144 t.Fatal(err)
145 }
146 if !second.Reused {
147 t.Fatal("existing deterministic target was not reused")
148 }
149 projection := migratedProjection(t, second.TargetDir)
150 if advanced {
151 if reflect.DeepEqual(projection.ModelMessages, compacted) || len(projection.Messages) != len(canonical)+1 {
152 t.Fatal("repair overwrote a target that already contained new work")
153 }
154 return
155 }
156 if !reflect.DeepEqual(projection.ModelMessages, compacted) {
157 t.Fatal("pristine legacy target was not repaired with its valid projection")
158 }
159 })
160 }
161 }
162
162 lines GO