返回 DeepSeek-Reasonix
tree_test.go
根目录 / internal / installlayout / tree_test.go
1 package installlayout
2
3 import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "strings"
8 "testing"
9 )
10
11 var shellTreeNames = []string{
12 AppShellDirName + "/" + ShellExecutableName(),
13 AppShellDirName + "/resources/app.asar",
14 AppShellDirName + "/locales/en-US.pak",
15 }
16
17 func treeMemberNames() []string {
18 return append(AllowedVersionMembers(), shellTreeNames...)
19 }
20
21 func writeTreeMembers(t *testing.T, src, body string) []Member {
22 t.Helper()
23 names := treeMemberNames()
24 members := make([]Member, 0, len(names))
25 for _, name := range names {
26 path := filepath.Join(src, filepath.FromSlash(name))
27 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
28 t.Fatal(err)
29 }
30 if err := os.WriteFile(path, []byte(body+"-"+name), 0o755); err != nil {
31 t.Fatal(err)
32 }
33 members = append(members, Member{Name: name, Path: path})
34 }
35 return members
36 }
37
38 func activateTree(t *testing.T, root, version, requestID string, members []Member) error {
39 t.Helper()
40 return ActivateVersion(ActivationRequest{
41 InstallRoot: root,
42 Version: version,
43 RequestID: requestID,
44 Members: members,
45 RequiredNames: treeMemberNames(),
46 })
47 }
48
49 func TestActivateVersionPublishesAppShellTree(t *testing.T) {
50 root := t.TempDir()
51 src := t.TempDir()
52 if err := activateTree(t, root, "v1.30.0", "tree-publish", writeTreeMembers(t, src, "new")); err != nil {
53 t.Fatal(err)
54 }
55 versionDir := filepath.Join(root, VersionsDirName, "v1.30.0")
56 for _, name := range shellTreeNames {
57 raw, err := os.ReadFile(filepath.Join(versionDir, filepath.FromSlash(name)))
58 if err != nil || string(raw) != "new-"+name {
59 t.Fatalf("tree member %s = %q err=%v", name, raw, err)
60 }
61 }
62 desktop, err := ActiveDesktopPath(root)
63 if err != nil || filepath.Base(desktop) != DesktopBinaryName() {
64 t.Fatalf("active desktop = %s err=%v", desktop, err)
65 }
66 relaunch, err := StableRelaunchPath(root)
67 if err != nil || relaunch != desktop {
68 t.Fatalf("stable relaunch = %s err=%v, want %s", relaunch, err, desktop)
69 }
70 }
71
72 func TestActivateVersionTreeFailureKeepsOldPointerAndTree(t *testing.T) {
73 root := t.TempDir()
74 if err := activateTree(t, root, "v1.30.0", "tree-seed", writeTreeMembers(t, t.TempDir(), "old")); err != nil {
75 t.Fatal(err)
76 }
77 badSrc := t.TempDir()
78 members := writeTreeMembers(t, badSrc, "new")
79 asar := filepath.Join(badSrc, AppShellDirName, "resources", "app.asar")
80 if err := os.Remove(asar); err != nil {
81 t.Fatal(err)
82 }
83 if err := os.Mkdir(asar, 0o755); err != nil {
84 t.Fatal(err)
85 }
86 if err := activateTree(t, root, "v1.31.0", "tree-fault", members); err == nil {
87 t.Fatal("expected activation failure for a non-regular tree source")
88 }
89 ptr, err := ReadCurrent(root)
90 if err != nil || ptr.ActiveVersion != "v1.30.0" {
91 t.Fatalf("pointer=%+v err=%v", ptr, err)
92 }
93 if _, err := os.Stat(filepath.Join(root, VersionsDirName, "v1.31.0")); !os.IsNotExist(err) {
94 t.Fatalf("failed version directory survived: %v", err)
95 }
96 shell := filepath.Join(root, VersionsDirName, "v1.30.0", AppShellDirName, ShellExecutableName())
97 if raw, err := os.ReadFile(shell); err != nil || !strings.HasPrefix(string(raw), "old-") {
98 t.Fatalf("active shell = %q err=%v", raw, err)
99 }
100 entries, err := os.ReadDir(filepath.Join(root, VersionsDirName))
101 if err != nil {
102 t.Fatal(err)
103 }
104 for _, entry := range entries {
105 if strings.HasPrefix(entry.Name(), ".staging-") {
106 t.Fatalf("staging directory %s survived the failed activation", entry.Name())
107 }
108 }
109 }
110
111 func TestActivateVersionRejectsTreeMembersOutsideWhitelist(t *testing.T) {
112 root := t.TempDir()
113 members := writeTreeMembers(t, t.TempDir(), "x")
114 if err := ActivateVersion(ActivationRequest{
115 InstallRoot: root,
116 Version: "v1.30.0",
117 RequestID: "tree-default-list",
118 Members: members,
119 }); err == nil {
120 t.Fatal("default whitelist must reject app/ members")
121 }
122 flat := members[:len(AllowedVersionMembers())]
123 if err := ActivateVersion(ActivationRequest{
124 InstallRoot: root,
125 Version: "v1.30.0",
126 RequestID: "tree-root",
127 Members: flat,
128 RootMembers: []Member{{Name: shellTreeNames[0], Path: members[len(flat)].Path}},
129 RequiredRootNames: []string{shellTreeNames[0]},
130 }); err == nil {
131 t.Fatal("root entries must stay flat")
132 }
133 if HasCurrent(root) {
134 t.Fatal("current.json must not exist after rejected activations")
135 }
136 }
137
138 func TestActivateVersionRejectsTreeTraversalNames(t *testing.T) {
139 root := t.TempDir()
140 src := t.TempDir()
141 members := writeTreeMembers(t, src, "x")
142 for _, bad := range []string{
143 "../" + AppShellDirName + "/x",
144 AppShellDirName + "/../" + DesktopBinaryName(),
145 AppShellDirName + "/./x",
146 AppShellDirName + "//x",
147 AppShellDirName + "/x/",
148 AppShellDirName + `\x`,
149 "/" + AppShellDirName + "/x",
150 "lib/x",
151 AppShellDirName + "/c:x",
152 " " + AppShellDirName + "/x",
153 } {
154 req := ActivationRequest{
155 InstallRoot: root,
156 Version: "v1.30.0",
157 RequestID: "tree-traversal",
158 Members: append(append([]Member(nil), members...), Member{Name: bad, Path: members[0].Path}),
159 RequiredNames: append(treeMemberNames(), bad),
160 }
161 if err := ActivateVersion(req); err == nil {
162 t.Fatalf("member name %q was accepted", bad)
163 }
164 if err := ValidateMemberName(bad); err == nil {
165 t.Fatalf("ValidateMemberName(%q) accepted", bad)
166 }
167 }
168 for _, good := range append([]string{DesktopBinaryName()}, shellTreeNames...) {
169 if err := ValidateMemberName(good); err != nil {
170 t.Fatalf("ValidateMemberName(%q) = %v", good, err)
171 }
172 }
173 if HasCurrent(root) {
174 t.Fatal("current.json must not exist after rejected activations")
175 }
176 }
177
178 func TestActivateVersionRejectsTreeSymlinkSource(t *testing.T) {
179 if runtime.GOOS == "windows" {
180 t.Skip("symlink privilege varies on Windows CI")
181 }
182 root := t.TempDir()
183 src := t.TempDir()
184 members := writeTreeMembers(t, src, "x")
185 shell := filepath.Join(src, AppShellDirName, ShellExecutableName())
186 real := writeTempMember(t, src, "real-shell", "body")
187 if err := os.Remove(shell); err != nil {
188 t.Fatal(err)
189 }
190 if err := os.Symlink(real, shell); err != nil {
191 t.Fatal(err)
192 }
193 if err := activateTree(t, root, "v1.30.0", "tree-symlink", members); err == nil {
194 t.Fatal("expected symlink tree source rejection")
195 }
196 if HasCurrent(root) {
197 t.Fatal("current.json must not be written after failed activation")
198 }
199 }
200
200 lines GO