tool_test.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. package kit
  2. import (
  3. "context"
  4. "encoding/json"
  5. "testing"
  6. "github.com/mark3labs/mcp-go/mcp"
  7. )
  8. type patchArgs struct {
  9. Kind string `json:"kind" jsonschema:"类型"`
  10. IDs []int `json:"ids" jsonschema:"编号"`
  11. }
  12. func TestDefProperties(t *testing.T) {
  13. d := Def[patchArgs]{
  14. Name: "patch_test", Title: "补充 schema", Description: "测试", ReadOnly: true,
  15. Handler: func(context.Context, patchArgs) (*mcp.CallToolResult, error) { return nil, nil },
  16. Properties: map[string]map[string]any{
  17. "kind": {"enum": []string{"pid", "vid"}},
  18. "ids": {"type": "array", "minItems": 1, "items": map[string]any{"type": "integer", "minimum": 0}},
  19. },
  20. }
  21. var schema struct {
  22. Properties map[string]map[string]any `json:"properties"`
  23. Required []string `json:"required"`
  24. }
  25. if err := json.Unmarshal(d.Tool().RawInputSchema, &schema); err != nil {
  26. t.Fatal(err)
  27. }
  28. ids := schema.Properties["ids"]
  29. if ids["type"] != "array" {
  30. t.Errorf("ids.type 应为 array(不再可空),实际 %v", ids["type"])
  31. }
  32. if ids["description"] != "编号" {
  33. t.Errorf("补充约束不应覆盖生成的描述,实际 %v", ids["description"])
  34. }
  35. if items, _ := ids["items"].(map[string]any); items["minimum"] != float64(0) {
  36. t.Errorf("ids.items.minimum 未生效:%v", ids["items"])
  37. }
  38. if enum, _ := schema.Properties["kind"]["enum"].([]any); len(enum) != 2 {
  39. t.Errorf("kind.enum 未生效:%v", schema.Properties["kind"])
  40. }
  41. if len(schema.Required) != 2 {
  42. t.Errorf("required 应保持不变,实际 %v", schema.Required)
  43. }
  44. }
  45. func TestDefPropertiesUnknownField(t *testing.T) {
  46. defer func() {
  47. if recover() == nil {
  48. t.Error("补充不存在的字段应 panic")
  49. }
  50. }()
  51. Def[patchArgs]{Name: "patch_bad", Properties: map[string]map[string]any{"nope": {"type": "string"}}}.Tool()
  52. }
  53. func TestDefHints(t *testing.T) {
  54. h := func(context.Context, NoArgs) (*mcp.CallToolResult, error) { return nil, nil }
  55. cases := []struct {
  56. def Def[NoArgs]
  57. destructive, idem bool
  58. }{
  59. {Def[NoArgs]{Name: "ro", ReadOnly: true, Handler: h}, false, true},
  60. {Def[NoArgs]{Name: "write", Handler: h}, false, false},
  61. {Def[NoArgs]{Name: "overwrite", Destructive: true, Handler: h}, true, false},
  62. {Def[NoArgs]{Name: "dedup", Idempotent: true, Handler: h}, false, true},
  63. }
  64. for _, c := range cases {
  65. a := c.def.Tool().Annotations
  66. if *a.DestructiveHint != c.destructive || *a.IdempotentHint != c.idem {
  67. t.Errorf("%s: destructive=%v idempotent=%v,期望 %v %v", c.def.Name, *a.DestructiveHint, *a.IdempotentHint, c.destructive, c.idem)
  68. }
  69. }
  70. }