返回 DeepSeek-Reasonix
windows_payload_tree_test.go
根目录 / desktop / internal / update / windows_payload_tree_test.go
1 package update
2
3 import (
4 "encoding/json"
5 "slices"
6 "strings"
7 "testing"
8 )
9
10 var windowsPayloadTreeNames = []string{
11 "app/Reasonix.exe",
12 "app/resources/app.asar",
13 "app/locales/en-US.pak",
14 }
15
16 func windowsPayloadTreeHashes() map[string]string {
17 hashes := make(map[string]string, len(windowsPayloadFileNames)+len(windowsPayloadTreeNames))
18 for _, name := range windowsPayloadFileNames {
19 hashes[name] = WindowsPayloadSHA256([]byte(name))
20 }
21 for _, name := range windowsPayloadTreeNames {
22 hashes[name] = WindowsPayloadSHA256([]byte(name))
23 }
24 return hashes
25 }
26
27 func TestWindowsPayloadManifestSchema2RoundTripsShellTree(t *testing.T) {
28 hashes := windowsPayloadTreeHashes()
29 b, err := EncodeWindowsPayloadManifest("v2.0.0", hashes)
30 if err != nil {
31 t.Fatal(err)
32 }
33 var manifest WindowsPayloadManifest
34 if err := json.Unmarshal(b, &manifest); err != nil {
35 t.Fatal(err)
36 }
37 if manifest.SchemaVersion != 2 || WindowsPayloadManifestSchemaVersion != 2 {
38 t.Fatalf("schema = %d (constant %d), want 2", manifest.SchemaVersion, WindowsPayloadManifestSchemaVersion)
39 }
40 names := make([]string, 0, len(manifest.Files))
41 for _, file := range manifest.Files {
42 names = append(names, file.Name)
43 }
44 if !slices.IsSorted(names) || len(names) != len(hashes) {
45 t.Fatalf("manifest names = %v, want sorted exact set", names)
46 }
47 decoded, err := DecodeWindowsPayloadManifest(b, "v2.0.0")
48 if err != nil {
49 t.Fatal(err)
50 }
51 for name, hash := range hashes {
52 if decoded[name] != hash {
53 t.Fatalf("decoded[%s] = %q, want %q", name, decoded[name], hash)
54 }
55 }
56 want := []string{
57 "app/Reasonix.exe",
58 "app/locales/en-US.pak",
59 "app/resources/app.asar",
60 "reasonix-cli.exe",
61 "reasonix-desktop.exe",
62 "reasonix-update-helper.exe",
63 }
64 if got := WindowsPayloadVersionMembers(decoded); !slices.Equal(got, want) {
65 t.Fatalf("version members = %v, want %v", got, want)
66 }
67 }
68
69 func TestWindowsPayloadManifestAcceptsSchema1FlatList(t *testing.T) {
70 var files []string
71 for _, name := range windowsPayloadFileNames {
72 files = append(files, `{"name":"`+name+`","sha256":"`+strings.Repeat("a", 64)+`"}`)
73 }
74 flat := `{"schemaVersion":1,"version":"v1","files":[` + strings.Join(files, ",") + `]}`
75 hashes, err := DecodeWindowsPayloadManifest([]byte(flat), "v1")
76 if err != nil {
77 t.Fatalf("schema 1 manifest rejected: %v", err)
78 }
79 if len(hashes) != len(windowsPayloadFileNames) {
80 t.Fatalf("schema 1 members = %d, want %d", len(hashes), len(windowsPayloadFileNames))
81 }
82 if got := WindowsPayloadVersionMembers(hashes); !slices.Equal(got, []string{"reasonix-cli.exe", "reasonix-desktop.exe", "reasonix-update-helper.exe"}) {
83 t.Fatalf("schema 1 version members = %v", got)
84 }
85 tree := strings.Replace(flat, `"files":[`, `"files":[{"name":"app/Reasonix.exe","sha256":"`+strings.Repeat("a", 64)+`"},`, 1)
86 if _, err := DecodeWindowsPayloadManifest([]byte(tree), "v1"); err == nil {
87 t.Fatal("schema 1 manifest with a tree member was accepted")
88 }
89 if _, err := DecodeWindowsPayloadManifest([]byte(strings.Replace(flat, `"schemaVersion":1`, `"schemaVersion":3`, 1)), "v1"); err == nil {
90 t.Fatal("unknown schema version was accepted")
91 }
92 }
93
94 func TestWindowsPayloadManifestRejectsInvalidTreeNames(t *testing.T) {
95 for _, name := range []string{
96 "app/../reasonix-desktop.exe",
97 `app\Reasonix.exe`,
98 "lib/x.dll",
99 "/app/x",
100 "app/",
101 "app",
102 "app/./x",
103 "App/x",
104 "app/c:x",
105 "app//x",
106 } {
107 hashes := windowsPayloadTreeHashes()
108 hashes[name] = strings.Repeat("a", 64)
109 if _, err := EncodeWindowsPayloadManifest("v2", hashes); err == nil {
110 t.Fatalf("encode accepted tree name %q", name)
111 }
112 if ValidWindowsPayloadTreeName(name) {
113 t.Fatalf("ValidWindowsPayloadTreeName(%q) = true", name)
114 }
115 }
116 b, err := EncodeWindowsPayloadManifest("v2", windowsPayloadTreeHashes())
117 if err != nil {
118 t.Fatal(err)
119 }
120 escaped := strings.Replace(string(b), `"app/Reasonix.exe"`, `"app/../Reasonix.exe"`, 1)
121 if _, err := DecodeWindowsPayloadManifest([]byte(escaped), "v2"); err == nil {
122 t.Fatal("decode accepted a traversal tree name")
123 }
124 }
125
126 func TestWindowsPayloadManifestSchema2RequiresFlatReleaseUnit(t *testing.T) {
127 hashes := windowsPayloadTreeHashes()
128 delete(hashes, "reasonix-guard.exe")
129 if _, err := EncodeWindowsPayloadManifest("v2", hashes); err == nil {
130 t.Fatal("manifest without the flat release unit was encoded")
131 }
132 b, err := EncodeWindowsPayloadManifest("v2", windowsPayloadTreeHashes())
133 if err != nil {
134 t.Fatal(err)
135 }
136 dropped := strings.Replace(string(b), `"name": "reasonix-guard.exe"`, `"name": "app/guard.exe"`, 1)
137 if _, err := DecodeWindowsPayloadManifest([]byte(dropped), "v2"); err == nil {
138 t.Fatal("manifest missing a flat member was decoded")
139 }
140 }
141
141 lines GO