server_save_draft_test.go

  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}