返回 DeepSeek-Reasonix
tool_contract_surface_test.go
根目录 / internal / boot / tool_contract_surface_test.go
1 package boot
2
3 import (
4 "encoding/json"
5 "reflect"
6 "strings"
7 "testing"
8
9 "reasonix/internal/provider"
10 )
11
12 func TestBootToolContractMatchesProviderVisibleSurface(t *testing.T) {
13 for _, tc := range []struct {
14 name string
15 tokenMode string
16 }{
17 {name: "default", tokenMode: ""},
18 {name: "economy", tokenMode: "economy"},
19 } {
20 t.Run(tc.name, func(t *testing.T) {
21 isolateConfigHome(t)
22 dir := robustTempDir(t)
23 t.Chdir(dir)
24 writeFile(t, dir, "reasonix.toml", `
25 default_model = "test-model"
26
27 [agent]
28 system_prompt = "BASE"
29
30 [[providers]]
31 name = "test-model"
32 kind = "boot-token-profile-test"
33 model = "x"
34 `)
35 approveWorkspace(t, dir)
36
37 req, entries := captureTokenProfileSurface(t, tc.tokenMode)
38 wantNames := unifiedBootToolNames()
39 if got := toolSchemaNames(req.Tools); !reflect.DeepEqual(got, wantNames) {
40 t.Fatalf("%s provider-visible tool surface changed\ngot %v\nwant %v", tc.name, got, wantNames)
41 }
42 if len(entries) != len(req.Tools) {
43 t.Fatalf("contract entries = %d, provider tools = %d\ncontract=%v\nprovider=%v", len(entries), len(req.Tools), contractEntryNames(entries), toolSchemaNames(req.Tools))
44 }
45 for i, e := range entries {
46 s := req.Tools[i]
47 if e.Name != s.Name {
48 t.Fatalf("tool[%d] name = %q, want %q\ncontract=%v\nprovider=%v", i, e.Name, s.Name, contractEntryNames(entries), toolSchemaNames(req.Tools))
49 }
50 if e.Description != strings.TrimSpace(s.Description) {
51 t.Fatalf("%s description drift\ncontract=%q\nprovider=%q", e.Name, e.Description, s.Description)
52 }
53 if !json.Valid(e.Schema) {
54 t.Fatalf("%s contract schema is invalid JSON: %s", e.Name, e.Schema)
55 }
56 if got := string(provider.CanonicalizeSchema(e.Schema)); got != string(e.Schema) {
57 t.Fatalf("%s contract schema is not canonical", e.Name)
58 }
59 if string(e.Schema) != string(s.Parameters) {
60 t.Fatalf("%s schema drift\ncontract=%s\nprovider=%s", e.Name, e.Schema, s.Parameters)
61 }
62 }
63 readOnly := map[string]bool{}
64 for _, e := range entries {
65 readOnly[e.Name] = e.ReadOnly
66 }
67 for name, want := range map[string]bool{
68 platformShellToolName(): false,
69 "read_file": true,
70 "view_image": true,
71 "use_capability": true,
72 } {
73 got, ok := readOnly[name]
74 if !ok {
75 t.Fatalf("contract missing %s; tools=%v", name, contractEntryNames(entries))
76 }
77 if got != want {
78 t.Fatalf("%s ReadOnly = %v, want %v", name, got, want)
79 }
80 }
81 if _, ok := readOnly["connect_tool_source"]; ok {
82 t.Fatalf("connect_tool_source must not appear on the provider-visible surface")
83 }
84 })
85 }
86 }
87
87 lines GO