返回 DeepSeek-Reasonix
project_mcp_decision_test.go
根目录 / desktop / project_mcp_decision_test.go
1 package main
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "path/filepath"
9 "testing"
10 "time"
11
12 "reasonix/internal/boot"
13 "reasonix/internal/config"
14 "reasonix/internal/control"
15 "reasonix/internal/plugin"
16 )
17
18 // enableProjectMCPForTest records the user's enable decision for each server
19 // root's project files declare with auto_start on.
20 func enableProjectMCPForTest(t testing.TB, root string) {
21 t.Helper()
22 enableProjectMCPForWorkspace(t, root, root)
23 }
24
25 // enableProjectMCPForWorkspace records the decision under workspace, which the
26 // test app's global tab leaves empty while config still loads from root.
27 func enableProjectMCPForWorkspace(t testing.TB, root, workspace string) {
28 t.Helper()
29 cfg, err := config.LoadForRootReadOnly(root)
30 if err != nil {
31 t.Fatal(err)
32 }
33 for _, p := range cfg.Plugins {
34 if p.Source.ProjectScoped() && p.ShouldAutoStart() {
35 if err := config.DefaultMCPActivationStore().SetServerEnabled(p, workspace, true); err != nil {
36 t.Fatal(err)
37 }
38 }
39 }
40 }
41
42 // Project configuration cannot choose MCP servers the desktop starts: without
43 // the user's decision the shared host never connects to one.
44 func TestDesktopSharedHostProjectMCPWaitsForTheUser(t *testing.T) {
45 isolateDesktopUserDirs(t)
46 dir := robustTempDir(t)
47 t.Chdir(dir)
48
49 srv := desktopMCPHTTPServer(t)
50 defer srv.Close()
51 if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), fmt.Appendf(nil, `
52 [[plugins]]
53 name = "h"
54 type = "http"
55 url = %q
56 `, srv.URL), 0o644); err != nil {
57 t.Fatal(err)
58 }
59
60 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
61 defer cancel()
62 sharedHost := plugin.NewHost()
63 defer sharedHost.Close()
64 ctrl, err := boot.Build(ctx, boot.Options{
65 WorkspaceRoot: dir,
66 SessionDir: filepath.Join(dir, "sessions"),
67 SharedHost: sharedHost,
68 Stderr: io.Discard,
69 })
70 if err != nil {
71 t.Fatalf("boot.Build: %v", err)
72 }
73 defer ctrl.Close()
74
75 time.Sleep(time.Second)
76 if sharedHost.HasClient("h") {
77 t.Fatal("project MCP connected with no user decision")
78 }
79 }
80
81 func TestCapabilitiesShowsDefaultMCPAsAutomaticIdleNotDisabled(t *testing.T) {
82 isolateDesktopUserDirs(t)
83 dir := robustTempDir(t)
84 t.Chdir(dir)
85 if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), []byte(`
86 [[plugins]]
87 name = "playwright"
88 command = "npx"
89 args = ["-y", "@playwright/mcp"]
90 `), 0o644); err != nil {
91 t.Fatal(err)
92 }
93 enableProjectMCPForWorkspace(t, dir, "")
94
95 app := NewApp()
96 app.setTestCtrl(control.New(control.Options{Host: plugin.NewHost()}), "")
97 defer func() {
98 if c := app.activeCtrl(); c != nil {
99 c.Close()
100 }
101 }()
102
103 view := app.Capabilities()
104 for _, s := range view.Servers {
105 if s.Name == "playwright" {
106 if s.Status != "deferred" || s.StartIntent != "automatic" || s.RuntimeState != "idle" {
107 t.Fatalf("default MCP view = %+v, want deferred automatic idle", s)
108 }
109 return
110 }
111 }
112 t.Fatalf("playwright MCP missing from Capabilities: %+v", view.Servers)
113 }
114
114 lines GO