返回 DeepSeek-Reasonix
coordinator_windows_test.go
根目录 / internal / desktoplauncher / coordinator_windows_test.go
1 //go:build windows
2
3 package desktoplauncher
4
5 import (
6 "bytes"
7 "errors"
8 "os"
9 "path/filepath"
10 "testing"
11
12 "reasonix/internal/desktopinstance"
13 "reasonix/internal/installlayout"
14 )
15
16 func TestPortableRemoteLocationStopsBeforeDesktopCoordination(t *testing.T) {
17 for _, test := range []struct {
18 name string
19 interactive bool
20 location launchLocation
21 }{
22 {name: "UNC interactive", interactive: true, location: launchLocationUNC},
23 {name: "UNC noninteractive", interactive: false, location: launchLocationUNC},
24 {name: "mapped remote drive", interactive: true, location: launchLocationRemoteDrive},
25 } {
26 t.Run(test.name, func(t *testing.T) {
27 root := coordinatedLaunchFixture(t, true)
28 var stderr bytes.Buffer
29 launched, notified, logged := false, false, false
30 handled, code := coordinatedLaunchWith(root, nil, test.interactive, coordinatedLaunchDependencies{
31 classifyLocation: func(path string) (launchLocation, error) {
32 if path != root {
33 t.Fatalf("classified path = %q, want %q", path, root)
34 }
35 return test.location, nil
36 },
37 launchAndVerify: func(string, string, bool, func() error, ...string) error {
38 launched = true
39 return nil
40 },
41 notify: func(err error) {
42 notified = true
43 var failure *desktopinstance.Error
44 if !errors.As(err, &failure) || failure.Code != desktopinstance.UnsupportedPortableLocation {
45 t.Errorf("notification error = %v", err)
46 }
47 },
48 logRejected: func(_ string, locationType string, gotInteractive bool) {
49 logged = true
50 if locationType != string(test.location) || gotInteractive != test.interactive {
51 t.Errorf("logged location=%q interactive=%v", locationType, gotInteractive)
52 }
53 },
54 stderr: &stderr,
55 })
56 if !handled || code != 1 {
57 t.Fatalf("handled=%v code=%d, want true/1", handled, code)
58 }
59 if launched {
60 t.Fatal("desktop coordination started for a rejected location")
61 }
62 if notified != test.interactive {
63 t.Fatalf("notified=%v, want %v", notified, test.interactive)
64 }
65 if !logged {
66 t.Fatal("rejection was not logged")
67 }
68 if !bytes.Contains(stderr.Bytes(), []byte(desktopinstance.UnsupportedPortableLocation)) {
69 t.Fatalf("stderr = %q", stderr.String())
70 }
71 for _, want := range []string{"Copy the entire extracted folder", "请将整个解压目录复制到 Windows 本地磁盘", "use the installer", "或使用安装器"} {
72 if !bytes.Contains(stderr.Bytes(), []byte(want)) {
73 t.Errorf("stderr is missing %q: %q", want, stderr.String())
74 }
75 }
76 })
77 }
78 }
79
80 func TestPortableLocalAndInstalledRemoteLocationsKeepExistingLaunchPath(t *testing.T) {
81 for _, test := range []struct {
82 name string
83 portable bool
84 location launchLocation
85 wantClassified bool
86 }{
87 {name: "portable local", portable: true, location: launchLocationLocal, wantClassified: true},
88 {name: "installed remote", portable: false, location: launchLocationUNC, wantClassified: false},
89 } {
90 t.Run(test.name, func(t *testing.T) {
91 root := coordinatedLaunchFixture(t, test.portable)
92 classified, launched := false, false
93 handled, code := coordinatedLaunchWith(root, []string{"--safe-mode", "workspace"}, false, coordinatedLaunchDependencies{
94 classifyLocation: func(string) (launchLocation, error) {
95 classified = true
96 return test.location, nil
97 },
98 launchAndVerify: func(_ string, _ string, interactive bool, _ func() error, args ...string) error {
99 launched = true
100 if interactive {
101 t.Error("noninteractive launch became interactive")
102 }
103 if len(args) != 1 || args[0] != "workspace" {
104 t.Errorf("launch args = %q", args)
105 }
106 return nil
107 },
108 notify: func(error) { t.Fatal("unexpected notification") },
109 logRejected: func(string, string, bool) { t.Fatal("unexpected rejection log") },
110 stderr: &bytes.Buffer{},
111 })
112 if !handled || code != 0 || !launched {
113 t.Fatalf("handled=%v code=%d launched=%v", handled, code, launched)
114 }
115 if classified != test.wantClassified {
116 t.Fatalf("classified=%v, want %v", classified, test.wantClassified)
117 }
118 })
119 }
120 }
121
122 func TestPortableLocationClassificationFailureIsGenericLaunchError(t *testing.T) {
123 root := coordinatedLaunchFixture(t, true)
124 queryErr := errors.New("drive type unavailable")
125 var stderr bytes.Buffer
126 notified, logged, launched := false, false, false
127 handled, code := coordinatedLaunchWith(root, nil, true, coordinatedLaunchDependencies{
128 classifyLocation: func(string) (launchLocation, error) { return "", queryErr },
129 launchAndVerify: func(string, string, bool, func() error, ...string) error {
130 launched = true
131 return nil
132 },
133 notify: func(err error) { notified = errors.Is(err, queryErr) },
134 logRejected: func(string, string, bool) { logged = true },
135 stderr: &stderr,
136 })
137 if !handled || code != 1 || launched || !notified || logged {
138 t.Fatalf("handled=%v code=%d launched=%v notified=%v logged=%v", handled, code, launched, notified, logged)
139 }
140 if !bytes.Contains(stderr.Bytes(), []byte(queryErr.Error())) {
141 t.Fatalf("stderr = %q", stderr.String())
142 }
143 }
144
145 func coordinatedLaunchFixture(t *testing.T, portable bool) string {
146 t.Helper()
147 root := t.TempDir()
148 version := "v1.38.9-2"
149 active := filepath.Join(root, "versions", version)
150 if err := os.MkdirAll(filepath.Join(active, "app"), 0o755); err != nil {
151 t.Fatal(err)
152 }
153 if err := os.WriteFile(filepath.Join(active, "reasonix-desktop.exe"), []byte("desktop"), 0o755); err != nil {
154 t.Fatal(err)
155 }
156 if err := installlayout.WriteCurrent(root, installlayout.CurrentPointer{
157 SchemaVersion: 1,
158 ActiveVersion: version,
159 ActiveDir: filepath.ToSlash(filepath.Join("versions", version)),
160 }); err != nil {
161 t.Fatal(err)
162 }
163 if err := os.WriteFile(filepath.Join(root, installlayout.PortableAliasName()), []byte("launcher"), 0o755); err != nil {
164 t.Fatal(err)
165 }
166 if portable {
167 return root
168 }
169 if err := os.WriteFile(filepath.Join(root, "uninstall.exe"), []byte("uninstaller"), 0o755); err != nil {
170 t.Fatal(err)
171 }
172 return root
173 }
174
174 lines GO