返回 DeepSeek-Reasonix
tool_diagnostics_test.go
根目录 / internal / skill / tool_diagnostics_test.go
1 package skill
2
3 import (
4 "testing"
5
6 "reasonix/internal/config"
7 "reasonix/internal/tool"
8 _ "reasonix/internal/tool/builtin"
9 )
10
11 func TestToolReferenceDiagnostics(t *testing.T) {
12 binding := tool.MCPBinding{Package: "example-plugin", Server: "github", RawName: "search", VisibleName: "search", CallableName: "mcp__github__search", CapabilityID: "mcp-tool:github/search"}
13 other := binding
14 other.Server, other.CallableName, other.CapabilityID = "other", "mcp__other__search", "mcp-tool:other/search"
15 for _, tc := range []struct {
16 ref, code string
17 bindings []tool.MCPBinding
18 }{
19 {"use_capability", "", nil},
20 {"grep", "", nil},
21 {"new_hidden_tool", "", nil},
22 {"read_*", "", nil},
23 {"*", "", nil},
24 {"[", "skill.tool_reference_invalid", nil},
25 {"mcp-tool:github", "skill.tool_reference_invalid", nil},
26 {"mcp-server:", "skill.tool_reference_invalid", nil},
27 {"mcp__github__", "skill.tool_reference_invalid", nil},
28 {"typo_read_file", "skill.tool_reference_unknown", nil},
29 {"lsp_typo", "skill.tool_reference_unknown", nil},
30 {"mcp__future__search", "skill.tool_reference_unverified", nil},
31 {"future/*", "skill.tool_reference_unverified", nil},
32 {"session:tool_result", "skill.tool_reference_unverified", nil},
33 {"tool:docs", "skill.tool_reference_unverified", nil},
34 {"skill:review", "skill.tool_reference_unverified", nil},
35 {"memory:remember", "skill.tool_reference_unverified", nil},
36 {"github/search", "", []tool.MCPBinding{binding}},
37 {"search", "", []tool.MCPBinding{binding}},
38 {"mcp-tool:github/search", "", []tool.MCPBinding{binding}},
39 {"mcp__github__search", "", []tool.MCPBinding{binding}},
40 {"search", "skill.tool_reference_ambiguous", []tool.MCPBinding{binding, other}},
41 {"mcp__*", "", []tool.MCPBinding{binding, other}},
42 } {
43 t.Run(tc.ref+tc.code, func(t *testing.T) {
44 d := CheckToolReferences([]Skill{{Name: "example", Plugin: "example-plugin", AllowedTools: []string{tc.ref}}}, ToolReferenceOptions{
45 Known: tool.KnownToolNames(), Registered: []tool.ContractEntry{{Name: "new_hidden_tool"}}, Bindings: tc.bindings,
46 })
47 if tc.code == "" {
48 if len(d) != 0 {
49 t.Fatalf("unexpected diagnostic: %+v", d)
50 }
51 return
52 }
53 if len(d) != 1 || d[0].Code != tc.code || d[0].Reference != tc.ref || d[0].Skill != "example" {
54 t.Fatalf("got %+v, want %s", d, tc.code)
55 }
56 wantSeverity := "warning"
57 if tc.code == "skill.tool_reference_unverified" {
58 wantSeverity = "info"
59 }
60 if d[0].Severity != wantSeverity {
61 t.Fatalf("severity: %+v", d)
62 }
63 })
64 }
65 }
66
67 func TestBuiltinSkillReferencesAndMCPRequirements(t *testing.T) {
68 store := DiagnosticStore(t.TempDir(), t.TempDir(), t.TempDir(), config.Default())
69 if len(store.List()) == 0 {
70 t.Fatal("no built-in skills loaded")
71 }
72 if d := CheckToolReferences(store.List(), ToolReferenceOptions{Known: tool.KnownToolNames()}); len(d) != 0 {
73 t.Fatalf("builtin references: %+v", d)
74 }
75 sk := []Skill{{Name: "dependent", AutoUse: "require", Requires: []string{"mcp-server:github"}, AllowedTools: []string{"use_capability"}}}
76 for _, tc := range []struct {
77 configured bool
78 failed, code string
79 }{
80 {false, "", "skill.mcp_dependency_missing"},
81 {true, "", ""},
82 {true, "spawn failed", "skill.mcp_dependency_failed"},
83 } {
84 var plugins []config.PluginEntry
85 if tc.configured {
86 plugins = []config.PluginEntry{{Name: "github"}}
87 }
88 if d := CheckToolReferences(sk, ToolReferenceOptions{Known: tool.KnownToolNames()}); len(d) != 0 {
89 t.Fatal(d)
90 }
91 d := CheckMCPRequirements(sk, plugins, map[string]string{"github": tc.failed})
92 if tc.code == "" {
93 if len(d) != 0 {
94 t.Fatal(d)
95 }
96 continue
97 }
98 if len(d) != 1 || d[0].Code != tc.code {
99 t.Fatalf("got %+v, want %s", d, tc.code)
100 }
101 }
102 }
103
103 lines GO