1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
4
5package mcp
6
7import (
8 "context"
9 "reflect"
10 "strings"
11 "testing"
12)
13
14// tools.READ_TOOL.1 tools.SCHEMA.4 tools.RESPONSES.6
15func TestCallToolACIDToolsReadTool1RejectsUnsupportedTargetBeforeCallingCooked(t *testing.T) {
16 backend := &fakeBackend{}
17 server := NewServer(backend, "test")
18
19 _, err := server.CallReadTool(context.Background(), ReadArguments{Target: "meal_plan"})
20 if err == nil {
21 t.Fatal("CallReadTool() error = nil, want unsupported target error")
22 }
23 if err.Error() != `unsupported read target "meal_plan" (supported: shopping_list, recipes, recipe, example_recipes)` {
24 t.Fatalf("CallReadTool() error = %q", err.Error())
25 }
26 if backend.readShoppingListCalls != 0 || backend.metadataCalls != 0 || backend.contentCalls != 0 {
27 t.Fatalf(
28 "backend calls = shopping list %d metadata %d content %d, want none",
29 backend.readShoppingListCalls,
30 backend.metadataCalls,
31 backend.contentCalls,
32 )
33 }
34}
35
36// recipes.SAVE.10 tools.SCHEMA.4 tools.RESPONSES.6
37func TestCallToolACIDRecipesSave10RejectsUnsupportedSourceBeforeCallingCooked(t *testing.T) {
38 backend := &fakeBackend{}
39 server := NewServer(backend, "test")
40
41 _, _, err := server.callSaveRecipeTool(context.Background(), nil, SaveRecipeArguments{Source: "unsupported"})
42 if err == nil {
43 t.Fatal("callSaveRecipeTool() error = nil, want unsupported source error")
44 }
45 if err.Error() != `unsupported save_recipe source "unsupported" (supported: raw_text, prepared, url, draft, existing)` {
46 t.Fatalf("callSaveRecipeTool() error = %q", err.Error())
47 }
48 if backend.saveCalls != 0 || backend.draftSaveCalls != 0 {
49 t.Fatalf("save calls = prepared %d draft %d, want none", backend.saveCalls, backend.draftSaveCalls)
50 }
51}
52
53// tools.SCHEMA.3 tools.SCHEMA.5 tools.SAVE_RECIPE_TOOL.1 tools.CHANGE_SHOPPING_LIST_TOOL.1
54func TestToolDescriptionsACIDToolsSchema3And5DescribeCurrentLimits(t *testing.T) {
55 read := readTool()
56 for _, want := range []string{
57 "shopping_list",
58 "recipes",
59 "recipe with recipe_id",
60 "example_recipes",
61 "Recipe results omit images and thumbnails",
62 } {
63 if !strings.Contains(read.Description, want) {
64 t.Fatalf("read description missing %q: %q", want, read.Description)
65 }
66 }
67
68 save := saveRecipeTool()
69 for _, want := range []string{
70 "raw text",
71 "prepared markdown",
72 "URL import",
73 "reviewed URL-import draft",
74 "existing recipe update",
75 "draft_id",
76 } {
77 if !strings.Contains(save.Description, want) {
78 t.Fatalf("save_recipe description missing %q: %q", want, save.Description)
79 }
80 }
81
82 shopping := changeShoppingListTool()
83 for _, want := range []string{
84 "add",
85 "update_item",
86 "replace_selection",
87 "add_selection",
88 "remove_selection",
89 "remove",
90 "clear",
91 "Remove and clear are destructive",
92 "put quantities before names",
93 "milk — 1",
94 } {
95 if !strings.Contains(shopping.Description, want) {
96 t.Fatalf("change_shopping_list description missing %q: %q", want, shopping.Description)
97 }
98 }
99}
100
101// tools.READ_TOOL.1 tools.SAVE_RECIPE_TOOL.3 tools.SAVE_RECIPE_TOOL.5 tools.SAVE_RECIPE_TOOL.6
102func TestArgumentDescriptionsACIDToolsReadTool1AndSaveRecipeTool3And5And6PointToExampleRecipes(t *testing.T) {
103 readTarget, ok := reflect.TypeFor[ReadArguments]().FieldByName("Target")
104 if !ok {
105 t.Fatal("ReadArguments.Target field missing")
106 }
107 if !strings.Contains(string(readTarget.Tag), "example_recipes") {
108 t.Fatalf("ReadArguments.Target tag = %q, want example_recipes", readTarget.Tag)
109 }
110
111 saveMarkdown, ok := reflect.TypeFor[SaveRecipeArguments]().FieldByName("Markdown")
112 if !ok {
113 t.Fatal("SaveRecipeArguments.Markdown field missing")
114 }
115 for _, want := range []string{"example_recipes", "Cooked's markdown dialect"} {
116 if !strings.Contains(string(saveMarkdown.Tag), want) {
117 t.Fatalf("SaveRecipeArguments.Markdown tag missing %q: %q", want, saveMarkdown.Tag)
118 }
119 }
120}