返回 DeepSeek-Reasonix
process_test.go
根目录 / internal / extension / sidecar / process_test.go
1 package sidecar
2
3 import (
4 "context"
5 "errors"
6 "runtime"
7 "strings"
8 "testing"
9 "time"
10
11 "reasonix/internal/pluginpkg"
12 )
13
14 func TestResolveRuntimeCommandContract(t *testing.T) {
15 root := t.TempDir()
16 shellPath := "/bin/sh"
17 if runtime.GOOS == "windows" {
18 shellPath = `C:\Windows\System32\cmd.exe`
19 }
20 cases := []struct {
21 name string
22 command string
23 wantErr string
24 }{
25 {name: "empty", command: " ", wantErr: "empty"},
26 {name: "relative bare name", command: "node", wantErr: "not an absolute path"},
27 {name: "relative path", command: "bin/sidecar", wantErr: "not an absolute path"},
28 {name: "shell indirection", command: shellPath, wantErr: "exec form"},
29 }
30 for _, tc := range cases {
31 t.Run(tc.name, func(t *testing.T) {
32 _, err := resolveRuntimeCommand(&pluginpkg.RuntimeSpec{Command: tc.command}, root)
33 if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
34 t.Fatalf("resolveRuntimeCommand(%q) error = %v, want one containing %q", tc.command, err, tc.wantErr)
35 }
36 })
37 }
38 }
39
40 func TestResolveRuntimeCommandExpandsPluginRoot(t *testing.T) {
41 root := t.TempDir()
42 got, err := resolveRuntimeCommand(&pluginpkg.RuntimeSpec{Command: "${REASONIX_PLUGIN_ROOT}/bin/sidecar"}, root)
43 if err != nil {
44 t.Fatalf("resolveRuntimeCommand: %v", err)
45 }
46 if !strings.HasPrefix(got, root) || !strings.HasSuffix(got, "sidecar") {
47 t.Fatalf("expanded command = %q, want inside %q", got, root)
48 }
49 }
50
51 // TestStartupFailureRedactsAndBoundsStderr floods stderr and fails the
52 // handshake: the surfaced diagnostics must carry a bounded, redacted tail.
53 func TestStartupFailureRedactsAndBoundsStderr(t *testing.T) {
54 pkg, installed := fakeSidecarPackage(t, "fakeplugin", func(rt *pluginpkg.RuntimeSpec) {
55 rt.Env[fakeEnvMode] = "stderr_flood"
56 // Wrong major forces handshake failure after stderr flood.
57 rt.Env[fakeEnvInitResult] = `{"protocolVersion":"1","name":"fake","version":"1","stateSchemaVersion":0}`
58 })
59 _, err := StartClient(context.Background(), ClientOptions{Package: pkg, Installed: installed, Session: testSessionContext()})
60 if err == nil {
61 t.Fatal("StartClient succeeded despite protocol mismatch")
62 }
63 var failure *startupFailure
64 if !errors.As(err, &failure) {
65 t.Fatalf("error %T is not a startupFailure", err)
66 }
67 if failure.Stage != "handshake" {
68 t.Fatalf("stage = %q, want handshake", failure.Stage)
69 }
70 if len(failure.Stderr) > stderrTailBytes {
71 t.Fatalf("stderr tail is %d bytes, want <= %d", len(failure.Stderr), stderrTailBytes)
72 }
73 if strings.Contains(failure.Stderr, "sk-abcdef1234567890SECRETKEY") {
74 t.Fatalf("stderr tail leaks the credential: %q", failure.Stderr)
75 }
76 if !strings.Contains(failure.Stderr, "***") {
77 t.Fatalf("stderr tail shows no redaction mask: %q", failure.Stderr)
78 }
79 }
80
81 func TestStartupFailureRedactsCauseWithoutLosingIdentity(t *testing.T) {
82 const secret = "sk-abcdef1234567890SECRETKEY"
83 cause := errors.New("initialize rejected api_key=" + secret)
84 err := newStartupFailure("handshake", time.Now(), "", cause)
85 if strings.Contains(err.Error(), secret) {
86 t.Fatalf("startup failure leaked its cause: %q", err)
87 }
88 if !strings.Contains(err.Error(), "****") {
89 t.Fatalf("startup failure contains no redaction marker: %q", err)
90 }
91 if !errors.Is(err, cause) {
92 t.Fatal("startup failure no longer unwraps to its original cause")
93 }
94 }
95
96 // TestRuntimeEnvFullTrustContract pins the documented contract: the sidecar
97 // inherits the unfiltered environment, manifest env layers over it, and the
98 // plugin identity variables are always set.
99 func TestRuntimeEnvFullTrustContract(t *testing.T) {
100 t.Setenv("REASONIX_TEST_INHERITED_MARKER", "present")
101 root := t.TempDir()
102 rt := &pluginpkg.RuntimeSpec{Command: "/bin/sidecar", Env: map[string]string{"MANIFEST_KEY": "manifest-value"}}
103 pkg := pluginpkg.Package{Root: root, Manifest: pluginpkg.Manifest{Name: "p", Version: "2.0.0", Runtime: rt}}
104 installed := pluginpkg.InstalledPlugin{Name: "p", Version: "1.0.0"}
105
106 env := runtimeEnv(rt, pkg, installed)
107 values := map[string]string{}
108 for _, entry := range env {
109 key, value, _ := strings.Cut(entry, "=")
110 values[key] = value
111 }
112 if values["REASONIX_TEST_INHERITED_MARKER"] != "present" {
113 t.Fatal("inherited environment was filtered")
114 }
115 if values["MANIFEST_KEY"] != "manifest-value" {
116 t.Fatal("manifest env missing")
117 }
118 if values[envPluginRoot] != root || values[envPluginName] != "p" || values[envPluginVersion] != "1.0.0" {
119 t.Fatalf("plugin identity env = %q %q %q", values[envPluginRoot], values[envPluginName], values[envPluginVersion])
120 }
121 }
122
122 lines GO