返回 DeepSeek-Reasonix
purge_compatibility_test.go
根目录 / desktop / internal / workspacestate / purge_compatibility_test.go
1 package workspacestate
2
3 import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "os"
8 "testing"
9
10 previous "reasonix/desktop/internal/workspacestate/testdata/v2previous"
11 )
12
13 func TestPurgeV2UpgradeRejectsPreviousWriterWithoutLosingEvidence(t *testing.T) {
14 for _, phase := range []string{"prepared", "tombstoned", "content_removed", "committed"} {
15 t.Run(phase, func(t *testing.T) {
16 store, expected := seedArchivedProcessState(t)
17 legacy, err := store.Load(t.Context())
18 if err != nil {
19 t.Fatal(err)
20 }
21 legacy.Version = 2
22 legacyBody, err := json.Marshal(legacy)
23 if err != nil {
24 t.Fatal(err)
25 }
26 if err := os.WriteFile(store.Path(), legacyBody, 0o600); err != nil {
27 t.Fatal(err)
28 }
29 old := previous.NewStore(store.Path())
30 // Produce a real legacy prepare using the previous implementation.
31 if err := old.BeginPurge(t.Context(), "victim", expected); err != nil {
32 t.Fatal(err)
33 }
34 if err := store.mutate(t.Context(), func(s *State) error {
35 op := s.PendingOperations["purge-victim"]
36 op.extra = map[string]json.RawMessage{"future": json.RawMessage(`{"nested":[1,2]}`)}
37 s.PendingOperations[op.ID] = op
38 s.extra = map[string]json.RawMessage{"futureRoot": json.RawMessage(`{"keep":true}`)}
39 return nil
40 }); err != nil {
41 t.Fatal(err)
42 }
43 state, err := store.Load(t.Context())
44 if err != nil {
45 t.Fatal(err)
46 }
47 if phase != "prepared" {
48 if err := store.ResumePurge(t.Context(), "victim", state.PendingOperations["purge-victim"]); err != nil {
49 t.Fatal(err)
50 }
51 }
52 if phase == "content_removed" || phase == "committed" {
53 if err := store.AdvancePurge(t.Context(), "victim", "content_removed"); err != nil {
54 t.Fatal(err)
55 }
56 }
57 if phase == "committed" {
58 if err := store.CompletePurge(t.Context(), "victim"); err != nil {
59 t.Fatal(err)
60 }
61 }
62 state, err = store.Load(t.Context())
63 if err != nil {
64 t.Fatal(err)
65 }
66 want, _ := json.Marshal(state.PendingOperations["purge-victim"])
67 if state.PendingOperations["purge-victim"].Phase != phase {
68 t.Fatalf("upgraded reader lost purge phase %s", phase)
69 }
70 before, err := os.ReadFile(store.Path())
71 if err != nil {
72 t.Fatal(err)
73 }
74 if _, err := old.Load(t.Context()); !errors.Is(err, previous.ErrUnsupportedVersion) {
75 t.Fatalf("previous reader must reject v3: %v", err)
76 }
77 if err := old.RenameWorkspace(t.Context(), GlobalWorkspaceID, "Old writer"); !errors.Is(err, previous.ErrUnsupportedVersion) {
78 t.Fatalf("previous writer must reject v3: %v", err)
79 }
80 after, err := os.ReadFile(store.Path())
81 if err != nil || !bytes.Equal(before, after) {
82 t.Fatal("rejected previous writer modified v3 registry")
83 }
84 if err := store.RenameWorkspace(t.Context(), GlobalWorkspaceID, "Unrelated title"); err != nil {
85 t.Fatal(err)
86 }
87 state, err = store.Load(t.Context())
88 if err != nil {
89 t.Fatal(err)
90 }
91 got, _ := json.Marshal(state.PendingOperations["purge-victim"])
92 if string(got) != string(want) || string(state.extra["futureRoot"]) != `{"keep":true}` {
93 t.Fatalf("upgraded writer dropped evidence: %s => %s", want, got)
94 }
95 if phase == "committed" {
96 before, _ := os.ReadFile(store.Path())
97 if err := store.CompletePurge(t.Context(), "victim"); err != nil {
98 t.Fatal(err)
99 }
100 after, _ := os.ReadFile(store.Path())
101 if string(before) != string(after) {
102 t.Fatal("idempotent completion rewrote registry")
103 }
104 }
105 })
106 }
107 }
108
108 lines GO