返回 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
36 req, entries := captureTokenProfileSurface(t, tc.tokenMode)
37 wantNames := unifiedBootToolNames()
38 if got := toolSchemaNames(req.Tools); !reflect.DeepEqual(got, wantNames) {
39 t.Fatalf("%s provider-visible tool surface changed\ngot %v\nwant %v", tc.name, got, wantNames)
40 }
41 if len(entries) != len(req.Tools) {
42 t.Fatalf("contract entries = %d, provider tools = %d\ncontract=%v\nprovider=%v", len(entries), len(req.Tools), contractEntryNames(entries), toolSchemaNames(req.Tools))
43 }
44 for i, e := range entries {
45 s := req.Tools[i]
46 if e.Name != s.Name {
47 t.Fatalf("tool[%d] name = %q, want %q\ncontract=%v\nprovider=%v", i, e.Name, s.Name, contractEntryNames(entries), toolSchemaNames(req.Tools))
48 }
49 if e.Description != strings.TrimSpace(s.Description) {
50 t.Fatalf("%s description drift\ncontract=%q\nprovider=%q", e.Name, e.Description, s.Description)
51 }
52 if !json.Valid(e.Schema) {
53 t.Fatalf("%s contract schema is invalid JSON: %s", e.Name, e.Schema)
54 }
55 if got := string(provider.CanonicalizeSchema(e.Schema)); got != string(e.Schema) {
56 t.Fatalf("%s contract schema is not canonical", e.Name)
57 }
58 if string(e.Schema) != string(s.Parameters) {
59 t.Fatalf("%s schema drift\ncontract=%s\nprovider=%s", e.Name, e.Schema, s.Parameters)
60 }
61 }
62 readOnly := map[string]bool{}
63 for _, e := range entries {
64 readOnly[e.Name] = e.ReadOnly
65 }
66 for name, want := range map[string]bool{
67 platformShellToolName(): false,
68 "read_file": true,
69 "view_image": true,
70 "use_capability": true,
71 } {
72 got, ok := readOnly[name]
73 if !ok {
74 t.Fatalf("contract missing %s; tools=%v", name, contractEntryNames(entries))
75 }
76 if got != want {
77 t.Fatalf("%s ReadOnly = %v, want %v", name, got, want)
78 }
79 }
80 if _, ok := readOnly["connect_tool_source"]; ok {
81 t.Fatalf("connect_tool_source must not appear on the provider-visible surface")
82 }
83 })
84 }
85 }
86
86 lines GO