返回 DeepSeek-Reasonix
hardening_test.go
根目录 / internal / remote / bootstrap / hardening_test.go
1 package bootstrap
2
3 import (
4 "context"
5 "os"
6 "strings"
7 "sync/atomic"
8 "testing"
9 "time"
10
11 "reasonix/internal/remote"
12 )
13
14 func TestEnsureServeRejectsStalePortFile(t *testing.T) {
15 skipOnWindows(t)
16 root := t.TempDir()
17 paths := pathsFor(root, root)
18 if err := os.MkdirAll(paths.Dir, 0o700); err != nil {
19 t.Fatal(err)
20 }
21 if err := os.WriteFile(paths.PortFile, []byte("127.0.0.1:49999\n"), 0o600); err != nil {
22 t.Fatal(err)
23 }
24 conn := newFakeConn(t, root, func(cmd string) (remote.ExecResult, error) {
25 switch {
26 case strings.Contains(cmd, "uname"):
27 return ok("Linux x86_64\n")
28 case strings.Contains(cmd, "command -v reasonix"):
29 return ok("/usr/bin/reasonix\nreasonix v9.9.0\nportfile:yes\n")
30 case strings.Contains(cmd, "nohup"):
31 if strings.Contains(cmd, "rm -f "+shellQuote(paths.PortFile)) {
32 _ = os.Remove(paths.PortFile) // model the generated launch command
33 }
34 return ok("12345\n") // the new serve never publishes a port
35 default:
36 return ok("")
37 }
38 })
39 ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
40 defer cancel()
41 if res, err := EnsureServe(ctx, conn, Options{Workspace: "~"}); err == nil {
42 t.Fatalf("accepted a stale port as a successful launch: %+v", res.State)
43 }
44 }
45
46 func TestEnsureServeSerializesConcurrentClients(t *testing.T) {
47 skipOnWindows(t)
48 root := t.TempDir()
49 paths := pathsFor(root, root)
50 var launches atomic.Int32
51 conn := newFakeConn(t, root, func(cmd string) (remote.ExecResult, error) {
52 switch {
53 case strings.Contains(cmd, "uname"):
54 return ok("Linux x86_64\n")
55 case strings.Contains(cmd, "command -v reasonix"):
56 return ok("/usr/bin/reasonix\nreasonix v9.9.0\nportfile:yes\n")
57 case strings.Contains(cmd, "nohup"):
58 launches.Add(1)
59 _ = os.WriteFile(paths.PortFile, []byte("127.0.0.1:45123\n"), 0o600)
60 return ok("321\n")
61 case strings.Contains(cmd, "ps -p 321"):
62 return ok("1\n")
63 default:
64 return ok("")
65 }
66 })
67 type outcome struct {
68 res Result
69 err error
70 }
71 start := make(chan struct{})
72 out := make(chan outcome, 2)
73 for range 2 {
74 go func() {
75 <-start
76 res, err := EnsureServe(context.Background(), conn, Options{Workspace: "~"})
77 out <- outcome{res: res, err: err}
78 }()
79 }
80 close(start)
81 var reused int
82 for range 2 {
83 got := <-out
84 if got.err != nil {
85 t.Fatal(got.err)
86 }
87 if got.res.Reused {
88 reused++
89 }
90 }
91 if got := launches.Load(); got != 1 || reused != 1 {
92 t.Fatalf("launches=%d reused=%d, want 1/1", got, reused)
93 }
94 }
95
96 func TestAutoInstallPreservesNPMFailureWhenNoUploadBinaryExists(t *testing.T) {
97 skipOnWindows(t)
98 root := t.TempDir()
99 conn := newFakeConn(t, root, func(cmd string) (remote.ExecResult, error) {
100 switch {
101 case strings.Contains(cmd, "command -v reasonix"):
102 return ok("\n")
103 case strings.Contains(cmd, "npm i -g reasonix"):
104 return remote.ExecResult{Stdout: []byte("permission denied"), ExitCode: 1}, nil
105 default:
106 return ok("")
107 }
108 })
109 _, _, err := ensureBinary(context.Background(), conn, conn.fs, Options{Install: InstallAuto}, root, "linux", "amd64", pathsFor(root, root))
110 if err == nil {
111 t.Fatal("auto install unexpectedly succeeded")
112 }
113 message := err.Error()
114 if !strings.Contains(message, "npm install failed: permission denied") || !strings.Contains(message, "no local Reasonix CLI") {
115 t.Fatalf("auto install hid the actionable failures: %v", err)
116 }
117 }
118
119 func TestAutoInstallDownloadsVerifiedCrossPlatformBinaryAfterNPMFailure(t *testing.T) {
120 skipOnWindows(t)
121 root := t.TempDir()
122 uploaded := uploadedBinPath(root)
123 conn := newFakeConn(t, root, func(cmd string) (remote.ExecResult, error) {
124 switch {
125 case strings.Contains(cmd, "npm i -g reasonix"):
126 return remote.ExecResult{Stdout: []byte("npm: command not found"), ExitCode: 127}, nil
127 case strings.Contains(cmd, "command -v reasonix"):
128 if _, err := os.Stat(uploaded); err == nil {
129 return ok(uploaded + "\nreasonix v1.2.3\nportfile:yes\n")
130 }
131 return ok("\n")
132 default:
133 return ok("")
134 }
135 })
136 fetched := false
137 bin, _, err := ensureBinary(context.Background(), conn, conn.fs, Options{
138 Install: InstallAuto, LocalBinary: "/local/reasonix", LocalGOOS: "darwin", LocalGOARCH: "arm64",
139 ProductVersion: "v1.2.3",
140 FetchBinary: func(_ context.Context, version, goos, goarch string) ([]byte, error) {
141 fetched = true
142 if version != "v1.2.3" || goos != "linux" || goarch != "amd64" {
143 t.Fatalf("fetch target = %s %s/%s", version, goos, goarch)
144 }
145 return []byte("linux-amd64-cli"), nil
146 },
147 }, root, "linux", "amd64", pathsFor(root, root))
148 if err != nil {
149 t.Fatal(err)
150 }
151 if !fetched || bin != uploaded {
152 t.Fatalf("bin=%q fetched=%v", bin, fetched)
153 }
154 }
155
155 lines GO