返回 DeepSeek-Reasonix
source_identity_test.go
根目录 / desktop / internal / workspacestate / source_identity_test.go
1 package workspacestate
2
3 import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "os"
10 "path/filepath"
11 "slices"
12 "testing"
13 )
14
15 func TestSourceIdentityAliasesSurviveReopenWithoutRewritingReceipts(t *testing.T) {
16 path := filepath.Join(t.TempDir(), "MixedCase", "history")
17 if err := os.MkdirAll(path, 0700); err != nil {
18 t.Fatal(err)
19 }
20 physical, err := sourcePathKey(path)
21 if err != nil {
22 t.Fatal(err)
23 }
24 current := func(head string) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(physical+"\x00"+head))) }
25 state := newState()
26 // Simulate durable pre-normalization keys on every CI filesystem.
27 for _, head := range []string{"", "main", "fork"} {
28 key := "old-" + head
29 state.SourceMappings[key] = SourceMapping{SourceKey: key, Path: path, HeadID: head, SessionID: "session-" + head, WorkspaceID: GlobalWorkspaceID, Fingerprint: "fingerprint"}
30 }
31 version := state.SourceMappings["old-main"]
32 version.SourceKey, version.SessionID = "old-main:review:version-1", "reviewed"
33 state.SourceMappings[version.SourceKey] = version
34 pending := SourceMapping{SourceKey: "old-pending", Path: path, HeadID: "pending"}
35 state.PendingOperations["pending"] = Operation{ID: "pending", Mapping: &pending, Kind: "import", Phase: "content_ready", Lifecycle: Active}
36 body, err := json.Marshal(state)
37 if err != nil {
38 t.Fatal(err)
39 }
40 file := filepath.Join(t.TempDir(), "state.json")
41 if err := os.WriteFile(file, body, 0600); err != nil {
42 t.Fatal(err)
43 }
44 for range 2 {
45 store := NewStore(file)
46 for _, project := range []bool{false, true} {
47 view, err := store.loadSnapshot(t.Context(), project)
48 if err != nil {
49 t.Fatal(err)
50 }
51 for _, head := range []string{"", "main", "fork"} {
52 mapping, found, err := view.ResolveSource(current(head))
53 if err != nil || !found || mapping.SourceKey != "old-"+head || mapping.SessionID != "session-"+head {
54 t.Fatalf("head %q resolved to %+v, %v", head, mapping, err)
55 }
56 }
57 mapping, found, err := view.ResolveSource(current("main") + ":review:version-1")
58 if err != nil || !found || mapping.SessionID != "reviewed" {
59 t.Fatalf("reviewed version: %+v %v", mapping, err)
60 }
61 if _, found, _ := view.ResolveSource(current("unadopted")); found {
62 t.Fatal("independent head was consumed")
63 }
64 if _, found, _ := view.ResolveSource(current("main") + ":review:version-2"); found {
65 t.Fatal("new version was consumed")
66 }
67 if !slices.Contains(view.SourceKeys("old-pending"), current("pending")) {
68 t.Fatal("pending operation lost its alias")
69 }
70 keys := view.SourceKeys("old-main")
71 keys[0] = "changed"
72 if view.SourceKeys("old-main")[0] != "old-main" {
73 t.Fatal("caller changed cached aliases")
74 }
75 }
76 snapshot, err := store.VerifySnapshot(t.Context())
77 if err != nil {
78 t.Fatal(err)
79 }
80 mapping, found, err := snapshot.ResolveSource(current(""))
81 if err != nil || !found || mapping.SourceKey != "old-" {
82 t.Fatalf("execution snapshot: %+v %v", mapping, err)
83 }
84 after, err := os.ReadFile(file)
85 if err != nil || !bytes.Equal(after, body) {
86 t.Fatal("read aliases rewrote persisted receipts")
87 }
88 }
89 }
90
91 func TestSourceIdentityAliasesRejectAmbiguousOwnership(t *testing.T) {
92 path := t.TempDir()
93 physical, err := sourcePathKey(path)
94 if err != nil {
95 t.Fatal(err)
96 }
97 key := fmt.Sprintf("%x", sha256.Sum256([]byte(physical+"\x00")))
98 state := newState()
99 for _, id := range []string{"first", "second"} {
100 state.SourceMappings[id] = SourceMapping{SourceKey: id, Path: path, SessionID: id}
101 }
102 if _, found, err := state.ResolveSource(key); found || !errors.Is(err, ErrMutationConflict) {
103 t.Fatalf("ambiguous source: found=%v err=%v", found, err)
104 }
105 if mapping, found, err := state.ResolveSource("first"); err != nil || !found || mapping.SessionID != "first" {
106 t.Fatal("exact durable identity was lost")
107 }
108 }
109
110 func TestConvertedDirectoryIdentityIncludesHeadlessDiscovery(t *testing.T) {
111 path := t.TempDir()
112 physical, err := sourcePathKey(path)
113 if err != nil {
114 t.Fatal(err)
115 }
116 key := func(head string) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(physical+"\x00"+head))) }
117 for _, format := range []string{"canonical", "legacy"} {
118 t.Run(format, func(t *testing.T) {
119 state := newState()
120 mapping := SourceMapping{SourceKey: "previous-head-key", Path: path, HeadID: "main", Format: format, SessionID: "adopted"}
121 state.SourceMappings[mapping.SourceKey] = mapping
122 mapped, found, err := state.ResolveSource(key(""))
123 if err != nil || found != (format == "canonical") || (found && mapped.SessionID != "adopted") {
124 t.Fatalf("headless discovery: %+v %v %v", mapped, found, err)
125 }
126 if _, found, _ := state.ResolveSource(key("fork")); found {
127 t.Fatal("independent head became an alias")
128 }
129 delete(state.SourceMappings, mapping.SourceKey)
130 mapping.SourceKey += ":review:version-1"
131 state.SourceMappings[mapping.SourceKey] = mapping
132 if _, found, _ := state.ResolveSource(key("")); found {
133 t.Fatal("reviewed version consumed its parent identity")
134 }
135 _, found, err = state.ResolveSource(key("") + ":review:version-1")
136 if err != nil || found != (format == "canonical") {
137 t.Fatalf("reviewed directory identity: %v %v", found, err)
138 }
139 delete(state.SourceMappings, mapping.SourceKey)
140 state.PendingOperations["interrupted"] = Operation{ID: "interrupted", Mapping: &mapping}
141 if got := slices.Contains(state.SourceKeys(mapping.SourceKey), key("")+":review:version-1"); got != (format == "canonical") {
142 t.Fatal("interrupted conversion lost its directory identity")
143 }
144 })
145 }
146 state := newState()
147 for _, head := range []string{"main", "fork"} {
148 state.SourceMappings[head] = SourceMapping{SourceKey: head, Path: path, HeadID: head, Format: "canonical", SessionID: head}
149 }
150 if _, found, err := state.ResolveSource(key("")); found || !errors.Is(err, ErrMutationConflict) {
151 t.Fatalf("ambiguous converted directory chose a target: %v %v", found, err)
152 }
153 }
154
154 lines GO