1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
4
5package mcp
6
7import (
8 "context"
9 "testing"
10
11 "git.secluded.site/cooked-mcp/internal/cooked"
12)
13
14// recipes.SAVE.7 recipes.SAVE.7-2 recipes.SAVE.9 recipes.SAVE.16 recipes.SAFETY.1 tools.SAVE_RECIPE_TOOL.1 tools.SAVE_RECIPE_TOOL.5
15func TestCallToolACIDRecipesSave7And9And16SavesReviewedDraft(t *testing.T) {
16 backend := &fakeBackend{draftSaveRecipeID: "recipe-1"}
17 server := NewServer(backend, "test")
18
19 _, output, err := server.callSaveRecipeTool(
20 context.Background(),
21 nil,
22 SaveRecipeArguments{
23 Source: " draft ",
24 DraftID: " draft-1 ",
25 Markdown: " # Pasta\n\n1. Boil pasta.\n",
26 Portions: 2,
27 },
28 )
29 if err != nil {
30 t.Fatalf("callSaveRecipeTool() error = %v", err)
31 }
32
33 if backend.draftSaveCalls != 1 {
34 t.Fatalf("draft save calls = %d, want 1", backend.draftSaveCalls)
35 }
36 if backend.draftSaveID != "draft-1" {
37 t.Fatalf("backend draft ID = %q, want draft-1", backend.draftSaveID)
38 }
39 if backend.draftSaveMarkdown != " # Pasta\n\n1. Boil pasta.\n" {
40 t.Fatalf("backend draft markdown = %q, want raw markdown preserved", backend.draftSaveMarkdown)
41 }
42 if backend.draftSavePortions != 2 {
43 t.Fatalf("backend draft portions = %g, want 2", backend.draftSavePortions)
44 }
45 if output.RecipeID != "recipe-1" || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
46 t.Fatalf("draft save output = %#v, want recipe_id only", output)
47 }
48 if backend.importRecipeURLCalls != 0 || backend.metadataCalls != 0 || backend.contentCalls != 0 {
49 t.Fatalf(
50 "unexpected import/read calls = import %d metadata %d content %d, want none",
51 backend.importRecipeURLCalls,
52 backend.metadataCalls,
53 backend.contentCalls,
54 )
55 }
56 if backend.saveCalls != 0 || backend.updateCalls != 0 {
57 t.Fatalf("prepared/update calls = %d/%d, want none", backend.saveCalls, backend.updateCalls)
58 }
59}
60
61// recipes.SAVE.7-5 recipes.SAVE.9-1 recipes.SAVE.16 recipes.SAFETY.1 recipes.SAFETY.2
62func TestCallToolACIDRecipesSave7_5And9_1OverwritesExistingRecipeFromDraft(t *testing.T) {
63 backend := &fakeBackend{}
64 server := NewServer(backend, "test")
65
66 _, output, err := server.callSaveRecipeTool(
67 context.Background(),
68 nil,
69 SaveRecipeArguments{
70 Source: " draft ",
71 DraftID: " draft-1 ",
72 RecipeID: " recipe-1 ",
73 Markdown: " # Pasta\n\n1. Boil pasta.\n",
74 Portions: 2,
75 },
76 )
77 if err != nil {
78 t.Fatalf("callSaveRecipeTool() error = %v", err)
79 }
80
81 if backend.updateCalls != 1 {
82 t.Fatalf("update calls = %d, want 1", backend.updateCalls)
83 }
84 if backend.updateRecipeID != "recipe-1" {
85 t.Fatalf("backend update recipe ID = %q, want recipe-1", backend.updateRecipeID)
86 }
87 if backend.updateMarkdown != " # Pasta\n\n1. Boil pasta.\n" {
88 t.Fatalf("backend update markdown = %q, want raw markdown preserved", backend.updateMarkdown)
89 }
90 if backend.updatePortions != 2 {
91 t.Fatalf("backend update portions = %g, want 2", backend.updatePortions)
92 }
93 if output.RecipeID != "recipe-1" {
94 t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID)
95 }
96 if backend.draftSaveCalls != 0 || backend.saveCalls != 0 {
97 t.Fatalf("draft save/prepared calls = %d/%d, want none", backend.draftSaveCalls, backend.saveCalls)
98 }
99}
100
101// recipes.SAVE.7 recipes.SAVE.7-3 recipes.SAVE.9 recipes.SAVE.16 tools.SAVE_RECIPE_TOOL.5
102func TestCallToolACIDRecipesSave7_3SavesCurrentDraftContentWhenContentOmitted(t *testing.T) {
103 backend := &fakeBackend{
104 draftSaveRecipeID: "recipe-1",
105 recipeContent: cooked.RecipeContent{
106 Content: "# Pasta\n\n1. Boil pasta.",
107 Portions: 1.5,
108 },
109 }
110 server := NewServer(backend, "test")
111
112 _, output, err := server.callSaveRecipeTool(
113 context.Background(),
114 nil,
115 SaveRecipeArguments{Source: "draft", DraftID: " draft-1 "},
116 )
117 if err != nil {
118 t.Fatalf("callSaveRecipeTool() error = %v", err)
119 }
120
121 if backend.contentCalls != 1 || backend.contentRecipeID != "draft-1" {
122 t.Fatalf("content reads = %d for %q, want 1 for draft-1", backend.contentCalls, backend.contentRecipeID)
123 }
124 if backend.draftSaveCalls != 1 {
125 t.Fatalf("draft save calls = %d, want 1", backend.draftSaveCalls)
126 }
127 if backend.draftSaveMarkdown != "# Pasta\n\n1. Boil pasta." {
128 t.Fatalf("backend draft markdown = %q, want current draft content", backend.draftSaveMarkdown)
129 }
130 if backend.draftSavePortions != 1.5 {
131 t.Fatalf("backend draft portions = %g, want current draft portions 1.5", backend.draftSavePortions)
132 }
133 if output.RecipeID != "recipe-1" || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
134 t.Fatalf("draft save output = %#v, want recipe_id only", output)
135 }
136 if backend.metadataCalls != 0 || backend.importRecipeURLCalls != 0 {
137 t.Fatalf(
138 "unexpected metadata/import calls = %d/%d, want none",
139 backend.metadataCalls,
140 backend.importRecipeURLCalls,
141 )
142 }
143}
144
145// recipes.SAVE.7-1 recipes.SAVE.7-4 tools.SAVE_RECIPE_TOOL.5
146func TestCallToolACIDRecipesSave7_1And7_4AndToolsSaveRecipeTool5RequiresDraftIDAndCompleteContent(t *testing.T) {
147 tests := []struct {
148 name string
149 arguments SaveRecipeArguments
150 }{
151 {
152 name: "draft_id",
153 arguments: SaveRecipeArguments{Source: "draft", DraftID: " ", Markdown: "# Pasta", Portions: 2},
154 },
155 {
156 name: "markdown_without_portions",
157 arguments: SaveRecipeArguments{Source: "draft", DraftID: "draft-1", Markdown: "# Pasta", Portions: 0},
158 },
159 {
160 name: "portions_without_markdown",
161 arguments: SaveRecipeArguments{Source: "draft", DraftID: "draft-1", Markdown: " ", Portions: 2},
162 },
163 }
164
165 for _, tt := range tests {
166 t.Run(tt.name, func(t *testing.T) {
167 backend := &fakeBackend{}
168 server := NewServer(backend, "test")
169
170 _, _, err := server.callSaveRecipeTool(context.Background(), nil, tt.arguments)
171 if err == nil {
172 t.Fatal("callSaveRecipeTool() error = nil, want missing draft field error")
173 }
174 if backend.draftSaveCalls != 0 {
175 t.Fatalf("draft save calls = %d, want none", backend.draftSaveCalls)
176 }
177 if backend.contentCalls != 0 {
178 t.Fatalf("content calls = %d, want none", backend.contentCalls)
179 }
180 if backend.saveCalls != 0 || backend.updateCalls != 0 || backend.importRecipeURLCalls != 0 {
181 t.Fatalf(
182 "unexpected backend calls = prepared %d update %d import %d, want none",
183 backend.saveCalls,
184 backend.updateCalls,
185 backend.importRecipeURLCalls,
186 )
187 }
188 })
189 }
190}