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 recipes.SAVE.7-3 recipes.SAVE.9 recipes.SAVE.16 tools.SAVE_RECIPE_TOOL.5
62func TestCallToolACIDRecipesSave7_3SavesCurrentDraftContentWhenContentOmitted(t *testing.T) {
63 backend := &fakeBackend{
64 draftSaveRecipeID: "recipe-1",
65 recipeContent: cooked.RecipeContent{
66 Content: "# Pasta\n\n1. Boil pasta.",
67 Portions: 1.5,
68 },
69 }
70 server := NewServer(backend, "test")
71
72 _, output, err := server.callSaveRecipeTool(
73 context.Background(),
74 nil,
75 SaveRecipeArguments{Source: "draft", DraftID: " draft-1 "},
76 )
77 if err != nil {
78 t.Fatalf("callSaveRecipeTool() error = %v", err)
79 }
80
81 if backend.contentCalls != 1 || backend.contentRecipeID != "draft-1" {
82 t.Fatalf("content reads = %d for %q, want 1 for draft-1", backend.contentCalls, backend.contentRecipeID)
83 }
84 if backend.draftSaveCalls != 1 {
85 t.Fatalf("draft save calls = %d, want 1", backend.draftSaveCalls)
86 }
87 if backend.draftSaveMarkdown != "# Pasta\n\n1. Boil pasta." {
88 t.Fatalf("backend draft markdown = %q, want current draft content", backend.draftSaveMarkdown)
89 }
90 if backend.draftSavePortions != 1.5 {
91 t.Fatalf("backend draft portions = %g, want current draft portions 1.5", backend.draftSavePortions)
92 }
93 if output.RecipeID != "recipe-1" || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
94 t.Fatalf("draft save output = %#v, want recipe_id only", output)
95 }
96 if backend.metadataCalls != 0 || backend.importRecipeURLCalls != 0 {
97 t.Fatalf(
98 "unexpected metadata/import calls = %d/%d, want none",
99 backend.metadataCalls,
100 backend.importRecipeURLCalls,
101 )
102 }
103}
104
105// recipes.SAVE.7-1 recipes.SAVE.7-4 tools.SAVE_RECIPE_TOOL.5
106func TestCallToolACIDRecipesSave7_1And7_4AndToolsSaveRecipeTool5RequiresDraftIDAndCompleteContent(t *testing.T) {
107 tests := []struct {
108 name string
109 arguments SaveRecipeArguments
110 }{
111 {
112 name: "draft_id",
113 arguments: SaveRecipeArguments{Source: "draft", DraftID: " ", Markdown: "# Pasta", Portions: 2},
114 },
115 {
116 name: "markdown_without_portions",
117 arguments: SaveRecipeArguments{Source: "draft", DraftID: "draft-1", Markdown: "# Pasta", Portions: 0},
118 },
119 {
120 name: "portions_without_markdown",
121 arguments: SaveRecipeArguments{Source: "draft", DraftID: "draft-1", Markdown: " ", Portions: 2},
122 },
123 }
124
125 for _, tt := range tests {
126 t.Run(tt.name, func(t *testing.T) {
127 backend := &fakeBackend{}
128 server := NewServer(backend, "test")
129
130 _, _, err := server.callSaveRecipeTool(context.Background(), nil, tt.arguments)
131 if err == nil {
132 t.Fatal("callSaveRecipeTool() error = nil, want missing draft field error")
133 }
134 if backend.draftSaveCalls != 0 {
135 t.Fatalf("draft save calls = %d, want none", backend.draftSaveCalls)
136 }
137 if backend.contentCalls != 0 {
138 t.Fatalf("content calls = %d, want none", backend.contentCalls)
139 }
140 if backend.saveCalls != 0 || backend.updateCalls != 0 || backend.importRecipeURLCalls != 0 {
141 t.Fatalf(
142 "unexpected backend calls = prepared %d update %d import %d, want none",
143 backend.saveCalls,
144 backend.updateCalls,
145 backend.importRecipeURLCalls,
146 )
147 }
148 })
149 }
150}