返回 DeepSeek-Reasonix
schema_test.go
根目录 / internal / browser / schema_test.go
1 package browser
2
3 import (
4 "encoding/json"
5 "slices"
6 "strings"
7 "testing"
8
9 "reasonix/internal/provider"
10 )
11
12 func TestSchemasAreCanonicalAndClosed(t *testing.T) {
13 first := Tools(&fakeExecutor{})
14 second := Tools(&fakeExecutor{})
15 for i, tl := range first {
16 name := tl.Name()
17 raw := tl.Schema()
18 if !json.Valid(raw) {
19 t.Errorf("%s schema is invalid JSON: %s", name, raw)
20 continue
21 }
22 if got := string(provider.CanonicalizeSchema(raw)); got != string(raw) {
23 t.Errorf("%s schema is not canonical:\n got %s\nwant %s", name, raw, got)
24 }
25 if string(tl.Schema()) != string(raw) || string(second[i].Schema()) != string(raw) {
26 t.Errorf("%s schema is not deterministic", name)
27 }
28 var s struct {
29 Type string `json:"type"`
30 AdditionalProperties *bool `json:"additionalProperties"`
31 Properties map[string]json.RawMessage `json:"properties"`
32 Required *[]string `json:"required"`
33 }
34 if err := json.Unmarshal(raw, &s); err != nil {
35 t.Fatalf("%s: %v", name, err)
36 }
37 if s.Type != "object" || s.AdditionalProperties == nil || *s.AdditionalProperties {
38 t.Errorf("%s schema is not a closed object: %s", name, raw)
39 }
40 if s.Required == nil {
41 t.Errorf("%s schema declares no required list", name)
42 continue
43 }
44 if len(*s.Required) == 0 && name != "browser_tabs" {
45 t.Errorf("%s schema has an empty required list", name)
46 }
47 if !slices.IsSorted(*s.Required) {
48 t.Errorf("%s required is not sorted: %v", name, *s.Required)
49 }
50 for _, r := range *s.Required {
51 if _, ok := s.Properties[r]; !ok {
52 t.Errorf("%s requires undeclared property %q", name, r)
53 }
54 }
55 if readOnlyTools[name] {
56 if _, ok := s.Properties["operationId"]; ok {
57 t.Errorf("%s is read-only but declares operationId", name)
58 }
59 continue
60 }
61 if !slices.Contains(*s.Required, "operationId") {
62 t.Errorf("%s is a write but does not require operationId: %v", name, *s.Required)
63 }
64 }
65 }
66
67 func TestSchemaTeachesWorkflow(t *testing.T) {
68 click := string(toolByName(t, nil, "browser_click").Schema())
69 if !strings.Contains(click, `"pattern":"^[A-Za-z0-9_-]{1,100}$"`) {
70 t.Errorf("browser_click operationId lacks the pattern: %s", click)
71 }
72 for _, want := range []string{"never reuse", "documentToken", "browser_snapshot"} {
73 if !strings.Contains(click, want) {
74 t.Errorf("browser_click schema does not mention %q", want)
75 }
76 }
77 nav := string(toolByName(t, nil, "browser_navigate").Schema())
78 if !strings.Contains(nav, `"enum":["url","back","forward","reload"]`) {
79 t.Errorf("browser_navigate action enum missing: %s", nav)
80 }
81 if got := string(toolByName(t, nil, "browser_tabs").Schema()); got != `{"additionalProperties":false,"properties":{},"required":[],"type":"object"}` {
82 t.Errorf("browser_tabs schema = %s", got)
83 }
84 }
85
85 lines GO