返回 DeepSeek-Reasonix
fixture_test.go
根目录 / desktop / internal / upgradefixture / fixture_test.go
1 package upgradefixture
2
3 import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "reasonix/desktop/internal/workspacestate"
11 )
12
13 func TestEncodeLegacyHistoryEscapesJSONContent(t *testing.T) {
14 want := []legacyMessage{
15 {Role: "user", Content: "quote \" slash \\ newline\n中文 %20 #"},
16 {Role: "assistant", Content: "second line"},
17 }
18 body, err := encodeLegacyHistory(want...)
19 if err != nil {
20 t.Fatal(err)
21 }
22 lines := strings.Split(strings.TrimSuffix(string(body), "\n"), "\n")
23 if len(lines) != len(want) {
24 t.Fatalf("encoded lines = %d, want %d: %q", len(lines), len(want), body)
25 }
26 for i, line := range lines {
27 var got legacyMessage
28 if err := json.Unmarshal([]byte(line), &got); err != nil {
29 t.Fatalf("decode line %d: %v", i, err)
30 }
31 if got != want[i] {
32 t.Fatalf("line %d = %+v, want %+v", i, got, want[i])
33 }
34 }
35 }
36
37 func TestVerifyLegacyHistoryChecksContentAfterByteRewrite(t *testing.T) {
38 path := filepath.Join(t.TempDir(), "legacy.jsonl")
39 body, err := encodeLegacyHistory(
40 legacyMessage{Role: "user", Content: fixtureQuestion},
41 legacyMessage{Role: "assistant", Content: fixtureText},
42 )
43 if err != nil {
44 t.Fatal(err)
45 }
46 // A normal shutdown may rewrite the active legacy checkpoint. Byte changes
47 // are acceptable only while the authored conversation stays intact.
48 rewritten := strings.ReplaceAll(string(body), "\n", " \n")
49 if rewritten == string(body) {
50 t.Fatal("fixture did not change legacy bytes")
51 }
52 if err := os.WriteFile(path, []byte(rewritten), 0o600); err != nil {
53 t.Fatal(err)
54 }
55 if err := verifyLegacyHistory(path, fixtureQuestion, fixtureText); err != nil {
56 t.Fatal(err)
57 }
58 changed := strings.Replace(rewritten, fixtureText, "different answer", 1)
59 if err := os.WriteFile(path, []byte(changed), 0o600); err != nil {
60 t.Fatal(err)
61 }
62 if err := verifyLegacyHistory(path, fixtureQuestion, fixtureText); err == nil {
63 t.Fatal("changed authored content passed verification")
64 }
65 }
66
67 func TestRunRestoresEnvironmentOnSuccessAndFailure(t *testing.T) {
68 for _, key := range []string{"REASONIX_HOME", "REASONIX_STATE_HOME", "REASONIX_CACHE_HOME"} {
69 t.Setenv(key, "untouched-"+key)
70 }
71 home := filepath.Join(t.TempDir(), "isolated")
72 report := filepath.Join(t.TempDir(), "fixture.json")
73 for _, mode := range []string{"create", "verify", "invalid"} {
74 err := Run(mode, home, report, "first")
75 if mode == "create" && err != nil {
76 t.Fatal(err)
77 }
78 if mode != "create" && err == nil {
79 t.Fatalf("%s must fail before app startup", mode)
80 }
81 for _, key := range []string{"REASONIX_HOME", "REASONIX_STATE_HOME", "REASONIX_CACHE_HOME"} {
82 if got := os.Getenv(key); got != "untouched-"+key {
83 t.Fatalf("%s leaked %s=%q", mode, key, got)
84 }
85 }
86 }
87 }
88
89 func TestVerifyPreparedImportRequiresOneCommittedLegacyImport(t *testing.T) {
90 legacy := filepath.Join(t.TempDir(), "sessions", fixtureSessionID+".jsonl")
91 report := fixtureReport{LegacyPath: legacy}
92 prepared := func() *workspacestate.State {
93 return &workspacestate.State{
94 Workspaces: map[string]workspacestate.Workspace{workspacestate.GlobalWorkspaceID: {SessionIDs: []string{"s1"}}},
95 SourceMappings: map[string]workspacestate.SourceMapping{"k": {
96 Path: filepath.Join(filepath.Dir(legacy), ".", filepath.Base(legacy)), Format: "legacy", SessionID: "s1", WorkspaceID: workspacestate.GlobalWorkspaceID,
97 }},
98 PendingOperations: map[string]workspacestate.Operation{"import": {Phase: "committed"}},
99 }
100 }
101 if id, err := verifyPreparedImport(prepared(), report); err != nil || id != "s1" {
102 t.Fatalf("committed legacy import = %q, %v", id, err)
103 }
104 for name, corrupt := range map[string]func(*workspacestate.State){
105 "not imported": func(s *workspacestate.State) { s.SourceMappings = nil },
106 "open operation": func(s *workspacestate.State) {
107 s.PendingOperations["import"] = workspacestate.Operation{Phase: "content_ready"}
108 },
109 "canonical format": func(s *workspacestate.State) {
110 m := s.SourceMappings["k"]
111 m.Format = "canonical"
112 s.SourceMappings["k"] = m
113 },
114 "other source": func(s *workspacestate.State) {
115 m := s.SourceMappings["k"]
116 m.Path += ".other"
117 s.SourceMappings["k"] = m
118 },
119 "extra session": func(s *workspacestate.State) {
120 s.Workspaces[workspacestate.GlobalWorkspaceID] = workspacestate.Workspace{SessionIDs: []string{"s1", "s2"}}
121 },
122 "session elsewhere": func(s *workspacestate.State) {
123 s.Workspaces[workspacestate.GlobalWorkspaceID] = workspacestate.Workspace{SessionIDs: []string{"s2"}}
124 },
125 } {
126 state := prepared()
127 corrupt(state)
128 if _, err := verifyPreparedImport(state, report); err == nil {
129 t.Errorf("%s passed prepared verification", name)
130 }
131 }
132 }
133
133 lines GO