返回 DeepSeek-Reasonix
boot_safe_mode_test.go
根目录 / internal / boot / boot_safe_mode_test.go
1 package boot
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9 "testing"
10 "time"
11
12 "reasonix/internal/config"
13 "reasonix/internal/event"
14 "reasonix/internal/plugin"
15 )
16
17 // v1.20+ removes product Safe Mode. REASONIX_SAFE_MODE no longer changes boot
18 // behavior; these tests pin that the normal tool/plugin surface stays available.
19
20 func TestBuildIgnoresSafeModeEnvForTools(t *testing.T) {
21 isolateConfigHome(t)
22 dir := robustTempDir(t)
23 t.Chdir(dir)
24 t.Setenv("REASONIX_SAFE_MODE", "1")
25
26 ctrl, err := Build(context.Background(), Options{
27 SessionDir: filepath.Join(t.TempDir(), "sessions"),
28 TokenMode: TokenModeFull,
29 Sink: event.Discard,
30 })
31 if err != nil {
32 t.Fatal(err)
33 }
34 names := map[string]bool{}
35 for _, e := range ctrl.ToolContractEntries() {
36 names[e.Name] = true
37 }
38 ctrl.Close()
39 for _, name := range []string{"install_source", "run_skill", "read_skill"} {
40 if !names[name] {
41 t.Fatalf("REASONIX_SAFE_MODE must not strip %s", name)
42 }
43 }
44 }
45
46 func TestBuildMemoryMigrationFailureWarnsAndContinues(t *testing.T) {
47 isolateConfigHome(t)
48 project := robustTempDir(t)
49 t.Setenv("REASONIX_SAFE_MODE", "")
50 globalDir := filepath.Join(config.MemoryUserDir(), "memory", "global")
51 if err := os.MkdirAll(filepath.Dir(globalDir), 0o755); err != nil {
52 t.Fatal(err)
53 }
54 if err := os.WriteFile(globalDir, []byte("not a directory"), 0o644); err != nil {
55 t.Fatal(err)
56 }
57 var notices []event.Event
58 ctrl, err := Build(context.Background(), Options{
59 WorkspaceRoot: project,
60 SessionDir: filepath.Join(t.TempDir(), "sessions"),
61 Sink: event.FuncSink(func(e event.Event) {
62 if e.Kind == event.Notice {
63 notices = append(notices, e)
64 }
65 }),
66 })
67 if err != nil {
68 t.Fatalf("Build should continue after memory migration failure: %v", err)
69 }
70 ctrl.Close()
71 for _, notice := range notices {
72 if notice.Level == event.LevelWarn && strings.Contains(notice.Text, "Memory metadata migration") && notice.Detail != "" {
73 return
74 }
75 }
76 t.Fatalf("memory migration warning missing: %+v", notices)
77 }
78
79 func TestBuildNormalModeKeepsSourceConnectorAndSkillTools(t *testing.T) {
80 isolateConfigHome(t)
81 dir := robustTempDir(t)
82 t.Chdir(dir)
83 t.Setenv("REASONIX_SAFE_MODE", "")
84
85 for _, tokenMode := range []string{TokenModeFull, TokenModeEconomy} {
86 ctrl, err := Build(context.Background(), Options{
87 SessionDir: filepath.Join(t.TempDir(), "sessions"),
88 TokenMode: tokenMode,
89 Sink: event.Discard,
90 })
91 if err != nil {
92 t.Fatalf("Build(%q): %v", tokenMode, err)
93 }
94 names := map[string]bool{}
95 for _, e := range ctrl.ToolContractEntries() {
96 names[e.Name] = true
97 }
98 ctrl.Close()
99 want := []string{"connect_tool_source"}
100 if tokenMode == TokenModeFull {
101 want = []string{"install_source", "run_skill", "read_skill", "read_only_skill", "slash_command"}
102 }
103 for _, name := range want {
104 if !names[name] {
105 t.Fatalf("normal mode (%q) must register %s", tokenMode, name)
106 }
107 }
108 }
109 }
110
111 func TestBuildStillSpawnsExtraPluginsWhenSafeModeEnvSet(t *testing.T) {
112 if runtime.GOOS == "windows" {
113 t.Skip("sentinel spec uses /bin/sh")
114 }
115 isolateConfigHome(t)
116 workspace := robustTempDir(t)
117 t.Chdir(workspace)
118 t.Setenv("REASONIX_SAFE_MODE", "1")
119 marker := filepath.Join(plugin.MCPStateDir(config.ReasonixHomeDir(), workspace, "acp-extra"), "started")
120 ctrl, err := Build(context.Background(), Options{
121 SessionDir: filepath.Join(t.TempDir(), "sessions"),
122 Sink: event.Discard,
123 ExtraPlugins: []plugin.Spec{{
124 Name: "acp-extra",
125 Command: "/bin/sh",
126 Args: []string{"-c", "echo started > '" + marker + "'"},
127 }},
128 })
129 if err != nil {
130 t.Fatalf("Build: %v", err)
131 }
132 ctrl.Close()
133 deadline := time.Now().Add(5 * time.Second)
134 for {
135 if _, err := os.Stat(marker); err == nil {
136 return
137 }
138 if time.Now().After(deadline) {
139 t.Fatal("host-supplied MCP server never spawned; REASONIX_SAFE_MODE must not drop ExtraPlugins")
140 }
141 time.Sleep(10 * time.Millisecond)
142 }
143 }
144
144 lines GO