Detailed changes
@@ -15,6 +15,7 @@ components:
6-2: A single recipe read includes owner or edit-permission information when Cooked provides it.
7: A single recipe read includes recipe content.
8: A single recipe read includes portions.
+ 8-1: Recipe reads preserve fractional portions when Cooked provides them.
9: Recipe reads omit image URLs unless a future feature explicitly exposes them.
PREVIEW_TEXT:
requirements:
@@ -24,6 +25,7 @@ components:
2: The preview_recipe_text tool returns the preview title.
3: The preview_recipe_text tool returns preview markdown.
4: The preview_recipe_text tool returns preview portions.
+ 4-1: The preview_recipe_text tool preserves fractional portions when Cooked provides them.
SAVE:
requirements:
1: The save_recipe tool saves a recipe from raw text.
@@ -46,6 +48,7 @@ components:
8-1: Updating an existing saved recipe requires a recipe ID.
8-2: Updating an existing saved recipe requires recipe markdown.
8-3: Updating an existing saved recipe requires portions.
+ 8-4: Saving or updating recipes preserves fractional portions supplied by the agent.
9: Recipe save and update results include the affected recipe ID.
10: Recipe save requests use explicit source values.
10-1: The recipe save source values are raw_text, prepared, url, draft, and existing.
@@ -12,11 +12,9 @@ import (
"errors"
"fmt"
"io"
- "math/big"
"net/http"
"net/http/cookiejar"
"net/url"
- "strconv"
"strings"
"sync"
"time"
@@ -89,37 +87,15 @@ type RecipeMetadata struct {
// RecipeContent is the Markdown-style body and portions for a Cooked recipe.
type RecipeContent struct {
- Content string `json:"content"`
- Portions int `json:"portions"`
+ Content string `json:"content"`
+ Portions float64 `json:"portions"`
}
// RecipeTextPreview is Cooked's preview of raw recipe text before saving.
type RecipeTextPreview struct {
- Title string `json:"title"`
- Markdown string `json:"markdown"`
- Portions int `json:"portions"`
-}
-
-// UnmarshalJSON accepts Cooked's integer-valued decimal portions, such as 1.0.
-func (p *RecipeTextPreview) UnmarshalJSON(data []byte) error {
- type raw RecipeTextPreview
- var response struct {
- raw
- Portions json.Number `json:"portions"`
- }
- if err := json.Unmarshal(data, &response); err != nil {
- return err
- }
-
- portions, err := jsonNumberToInt(response.Portions)
- if err != nil {
- return fmt.Errorf("portions: %w", err)
- }
-
- *p = RecipeTextPreview(response.raw)
- p.Portions = portions
-
- return nil
+ Title string `json:"title"`
+ Markdown string `json:"markdown"`
+ Portions float64 `json:"portions"`
}
// RecipeURLImport is the result of importing a recipe URL.
@@ -316,7 +292,7 @@ func (c *Client) PreviewRecipeText(ctx context.Context, title, text string) (Rec
}
// SavePreparedRecipe logs in when needed and saves prepared recipe markdown as a new recipe.
-func (c *Client) SavePreparedRecipe(ctx context.Context, title, markdown string, portions int) (string, error) {
+func (c *Client) SavePreparedRecipe(ctx context.Context, title, markdown string, portions float64) (string, error) {
body, err := json.Marshal(savePreparedRecipeRequest{Title: title, Description: markdown, Portions: portions})
if err != nil {
return "", fmt.Errorf("encode Cooked prepared recipe save request: %w", err)
@@ -337,7 +313,7 @@ func (c *Client) SavePreparedRecipe(ctx context.Context, title, markdown string,
}
// SaveRecipeDraft logs in when needed and saves a reviewed URL import draft.
-func (c *Client) SaveRecipeDraft(ctx context.Context, draftID, markdown string, portions int) (string, error) {
+func (c *Client) SaveRecipeDraft(ctx context.Context, draftID, markdown string, portions float64) (string, error) {
body, err := json.Marshal(recipeContentRequest{Description: markdown, Portions: portions})
if err != nil {
return "", fmt.Errorf("encode Cooked recipe draft save request: %w", err)
@@ -359,7 +335,7 @@ func (c *Client) SaveRecipeDraft(ctx context.Context, draftID, markdown string,
}
// UpdateRecipeContent logs in when needed and updates an existing recipe's content.
-func (c *Client) UpdateRecipeContent(ctx context.Context, recipeID, markdown string, portions int) error {
+func (c *Client) UpdateRecipeContent(ctx context.Context, recipeID, markdown string, portions float64) error {
body, err := json.Marshal(recipeContentRequest{Description: markdown, Portions: portions})
if err != nil {
return fmt.Errorf("encode Cooked recipe update request: %w", err)
@@ -498,35 +474,6 @@ func validateShoppingListProductGroup(aisleIndex, productIndex int, product Prod
return nil
}
-func jsonNumberToInt(number json.Number) (int, error) {
- text := number.String()
- if text == "" {
- return 0, fmt.Errorf("missing")
- }
-
- rational, ok := new(big.Rat).SetString(text)
- if !ok {
- return 0, fmt.Errorf("invalid JSON number %q", text)
- }
- if !rational.IsInt() {
- return 0, fmt.Errorf("must be integer-valued, got %s", text)
- }
-
- integer := rational.Num()
- if !integer.IsInt64() {
- return 0, fmt.Errorf("out of range, got %s", text)
- }
-
- value := integer.Int64()
- maxInt := int64(1<<(strconv.IntSize-1) - 1)
- minInt := -maxInt - 1
- if value < minInt || value > maxInt {
- return 0, fmt.Errorf("out of range for int, got %s", text)
- }
-
- return int(value), nil
-}
-
func (c *Client) doAuthenticated(
ctx context.Context,
method, path string,
@@ -686,14 +633,14 @@ type previewRecipeTextRequest struct {
}
type savePreparedRecipeRequest struct {
- Title string `json:"title"`
- Description string `json:"description"`
- Portions int `json:"portions"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Portions float64 `json:"portions"`
}
type recipeContentRequest struct {
- Description string `json:"description"`
- Portions int `json:"portions"`
+ Description string `json:"description"`
+ Portions float64 `json:"portions"`
}
type importRecipeURLRequest struct {
@@ -7,7 +7,6 @@ package cooked
import (
"context"
"net/http"
- "strings"
"testing"
)
@@ -28,11 +27,12 @@ func TestPreviewRecipeTextACIDAPIClientResponses1AcceptsIntegerDecimalPortions(t
t.Fatalf("preview calls = %d, want 1", previewCalls)
}
if preview.Portions != 1 {
- t.Fatalf("preview portions = %d, want 1", preview.Portions)
+ t.Fatalf("preview portions = %g, want 1", preview.Portions)
}
}
-func TestPreviewRecipeTextACIDAPIClientResponses1RejectsFractionalPortions(t *testing.T) {
+// api-client.RESPONSES.1 recipes.PREVIEW_TEXT.4-1
+func TestPreviewRecipeTextACIDAPIClientResponses1AndRecipesPreviewText4_1PreservesFractionalPortions(t *testing.T) {
var previewCalls int
client, closeServer := newTestClient(t, rawPreviewRecipeTextTestHandler(
t,
@@ -41,16 +41,16 @@ func TestPreviewRecipeTextACIDAPIClientResponses1RejectsFractionalPortions(t *te
))
defer closeServer()
- _, err := client.PreviewRecipeText(context.Background(), "Pasta", "Boil pasta.")
- if err == nil {
- t.Fatal("PreviewRecipeText() error = nil, want fractional portions error")
- }
- if !strings.Contains(err.Error(), "must be integer-valued") {
- t.Fatalf("PreviewRecipeText() error = %v, want integer-valued portions error", err)
+ preview, err := client.PreviewRecipeText(context.Background(), "Pasta", "Boil pasta.")
+ if err != nil {
+ t.Fatalf("PreviewRecipeText() error = %v", err)
}
if previewCalls != 1 {
t.Fatalf("preview calls = %d, want 1", previewCalls)
}
+ if preview.Portions != 1.5 {
+ t.Fatalf("preview portions = %g, want 1.5", preview.Portions)
+ }
}
func rawPreviewRecipeTextTestHandler(t *testing.T, previewCalls *int, body string) http.HandlerFunc {
@@ -111,7 +111,7 @@ func TestSavePreparedRecipeACIDAPIClientResponses1RejectsMissingOrBlankRecipeID(
context.Background(),
"Pasta",
"# Pasta\n\n1. Boil pasta.",
- 2,
+ 2.5,
)
if err == nil {
t.Fatal("SavePreparedRecipe() error = nil, want missing recipe ID error")
@@ -126,6 +126,28 @@ func TestSavePreparedRecipeACIDAPIClientResponses1RejectsMissingOrBlankRecipeID(
}
}
+// api-client.RESPONSES.1 recipes.READ.8-1
+func TestReadRecipeContentACIDAPIClientResponses1AndRecipesRead8_1PreservesFractionalPortions(t *testing.T) {
+ var contentCalls int
+ client, closeServer := newTestClient(t, rawRecipeContentTestHandler(
+ t,
+ &contentCalls,
+ `{"content":"# Pasta","portions":1.5}`,
+ ))
+ defer closeServer()
+
+ content, err := client.ReadRecipeContent(context.Background(), "recipe/with space")
+ if err != nil {
+ t.Fatalf("ReadRecipeContent() error = %v", err)
+ }
+ if contentCalls != 1 {
+ t.Fatalf("content calls = %d, want 1", contentCalls)
+ }
+ if content.Portions != 1.5 {
+ t.Fatalf("content portions = %g, want 1.5", content.Portions)
+ }
+}
+
// api-client.RESPONSES.1 recipes.READ.7 recipes.READ.8
func TestReadRecipeContentACIDAPIClientResponses1RejectsMissingContentOrPortions(t *testing.T) {
tests := []struct {
@@ -99,7 +99,7 @@ func TestReadRecipeContentACIDRecipesRead7And8ReadsContentAndPortions(t *testing
t.Fatalf("content = %q, want recipe markdown", content.Content)
}
if content.Portions != 2 {
- t.Fatalf("portions = %d, want 2", content.Portions)
+ t.Fatalf("portions = %g, want 2", content.Portions)
}
}
@@ -123,16 +123,17 @@ func TestPreviewRecipeTextACIDRecipesPreviewText1To4PreviewsWithoutSaving(t *tes
t.Fatalf("preview markdown = %q, want recipe markdown", preview.Markdown)
}
if preview.Portions != 2 {
- t.Fatalf("preview portions = %d, want 2", preview.Portions)
+ t.Fatalf("preview portions = %g, want 2", preview.Portions)
}
}
-func TestSavePreparedRecipeACIDRecipesSave2And9And13SavesMarkdownAsDescription(t *testing.T) {
+// recipes.SAVE.2 recipes.SAVE.8-4 recipes.SAVE.9 recipes.SAVE.13
+func TestSavePreparedRecipeACIDRecipesSave2And8_4And9And13SavesMarkdownAsDescription(t *testing.T) {
var saveCalls int
client, closeServer := newTestClient(t, savePreparedRecipeTestHandler(t, &saveCalls))
defer closeServer()
- recipeID, err := client.SavePreparedRecipe(context.Background(), "Pasta", "# Pasta\n\n1. Boil pasta.", 2)
+ recipeID, err := client.SavePreparedRecipe(context.Background(), "Pasta", "# Pasta\n\n1. Boil pasta.", 2.5)
if err != nil {
t.Fatalf("SavePreparedRecipe() error = %v", err)
}
@@ -833,8 +834,8 @@ func requireSavePreparedRecipeRequest(t *testing.T, r *http.Request) {
if request.Description != "# Pasta\n\n1. Boil pasta." {
t.Fatalf("save description = %q, want markdown", request.Description)
}
- if request.Portions != 2 {
- t.Fatalf("save portions = %d, want 2", request.Portions)
+ if request.Portions != 2.5 {
+ t.Fatalf("save portions = %g, want 2.5", request.Portions)
}
}
@@ -849,7 +850,7 @@ func requireRecipeContentRequest(t *testing.T, r *http.Request, description stri
t.Fatalf("%s description = %q, want markdown", description, request.Description)
}
if request.Portions != 4 {
- t.Fatalf("%s portions = %d, want 4", description, request.Portions)
+ t.Fatalf("%s portions = %g, want 4", description, request.Portions)
}
}
@@ -8,6 +8,7 @@ package mcp
import (
"context"
"fmt"
+ "strconv"
"strings"
sdk "github.com/modelcontextprotocol/go-sdk/mcp"
@@ -33,9 +34,9 @@ type Backend interface {
ReadRecipeMetadata(ctx context.Context, recipeID string) (cooked.RecipeMetadata, error)
ReadRecipeContent(ctx context.Context, recipeID string) (cooked.RecipeContent, error)
PreviewRecipeText(ctx context.Context, title, text string) (cooked.RecipeTextPreview, error)
- SavePreparedRecipe(ctx context.Context, title, markdown string, portions int) (string, error)
- SaveRecipeDraft(ctx context.Context, draftID, markdown string, portions int) (string, error)
- UpdateRecipeContent(ctx context.Context, recipeID, markdown string, portions int) error
+ SavePreparedRecipe(ctx context.Context, title, markdown string, portions float64) (string, error)
+ SaveRecipeDraft(ctx context.Context, draftID, markdown string, portions float64) (string, error)
+ UpdateRecipeContent(ctx context.Context, recipeID, markdown string, portions float64) error
ImportRecipeURL(ctx context.Context, recipeURL string) (cooked.RecipeURLImport, error)
DeleteRecipe(ctx context.Context, recipeID string) error
ClearShoppingList(ctx context.Context) error
@@ -390,7 +391,7 @@ func (s *Server) callExistingSaveRecipeTool(
return newRecipeSaveResult(recipeID)
}
-func validateSaveRecipeContent(source, markdown string, portions int) error {
+func validateSaveRecipeContent(source, markdown string, portions float64) error {
if strings.TrimSpace(markdown) == "" {
return fmt.Errorf("markdown is required when source is %s", source)
}
@@ -404,7 +405,7 @@ func validateSaveRecipeContent(source, markdown string, portions int) error {
func (s *Server) savePreparedRecipe(
ctx context.Context,
title, markdown string,
- portions int,
+ portions float64,
) (*sdk.CallToolResult, SaveRecipeOutput, error) {
recipeID, err := s.backend.SavePreparedRecipe(ctx, title, markdown, portions)
if err != nil {
@@ -1007,7 +1008,7 @@ func formatRecipe(recipe RecipeDetail) string {
builder.WriteByte('\n')
}
fmt.Fprintf(&builder, "Edit permission: %t\n", recipe.EditPermission)
- fmt.Fprintf(&builder, "Portions: %d\n", recipe.Portions)
+ fmt.Fprintf(&builder, "Portions: %s\n", formatPortions(recipe.Portions))
content := strings.TrimSpace(recipe.Content)
if content == "" {
@@ -1030,7 +1031,7 @@ func formatRecipeTextPreview(preview PreviewRecipeTextOutput) string {
builder.WriteString("Recipe text preview: ")
builder.WriteString(title)
builder.WriteByte('\n')
- fmt.Fprintf(&builder, "Portions: %d\n", preview.Portions)
+ fmt.Fprintf(&builder, "Portions: %s\n", formatPortions(preview.Portions))
markdown := strings.TrimSpace(preview.Markdown)
if markdown == "" {
@@ -1043,6 +1044,8 @@ func formatRecipeTextPreview(preview PreviewRecipeTextOutput) string {
return strings.TrimRight(builder.String(), "\n")
}
+func formatPortions(portions float64) string { return strconv.FormatFloat(portions, 'f', -1, 64) }
+
func formatRecipeSave(output SaveRecipeOutput) string {
return "Saved recipe (id: " + output.RecipeID + ")."
}
@@ -1060,7 +1063,7 @@ func formatRecipeURLImportDraft(output SaveRecipeOutput) string {
builder.WriteString(output.DraftID)
builder.WriteString("). Review the markdown and portions before saving.")
if output.Portions > 0 {
- fmt.Fprintf(&builder, "\nPortions: %d", output.Portions)
+ fmt.Fprintf(&builder, "\nPortions: %s", formatPortions(output.Portions))
}
if strings.TrimSpace(output.Markdown) != "" {
builder.WriteString("\n\n")
@@ -1108,12 +1111,12 @@ type RecipeSummary struct {
// RecipeDetail is the MCP-safe single recipe output.
type RecipeDetail struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Owner string `json:"owner"`
- EditPermission bool `json:"edit_permission"`
- Content string `json:"content"`
- Portions int `json:"portions"`
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Owner string `json:"owner"`
+ EditPermission bool `json:"edit_permission"`
+ Content string `json:"content"`
+ Portions float64 `json:"portions"`
}
// PreviewRecipeTextArguments contains preview_recipe_text tool arguments.
@@ -1124,30 +1127,30 @@ type PreviewRecipeTextArguments struct {
// PreviewRecipeTextOutput is the structured output for preview_recipe_text.
type PreviewRecipeTextOutput struct {
- Title string `json:"title"`
- Markdown string `json:"markdown"`
- Portions int `json:"portions"`
+ Title string `json:"title"`
+ Markdown string `json:"markdown"`
+ Portions float64 `json:"portions"`
}
// SaveRecipeArguments contains save_recipe tool arguments.
type SaveRecipeArguments struct {
- Source string `json:"source" jsonschema:"Recipe save source: raw_text, prepared, url, draft, or existing."`
- RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for existing recipe updates."`
- DraftID string `json:"draft_id,omitempty" jsonschema:"Draft ID for reviewed URL import draft saves."`
- Title string `json:"title,omitempty" jsonschema:"Recipe title for raw_text or prepared saves."`
- Text string `json:"text,omitempty" jsonschema:"Raw recipe text for raw_text saves."`
- URL string `json:"url,omitempty" jsonschema:"Recipe URL for url imports."`
- Markdown string `json:"markdown,omitempty" jsonschema:"Recipe markdown for prepared saves, reviewed URL import drafts, or existing recipe updates."`
- Portions int `json:"portions,omitempty" jsonschema:"Recipe portions for prepared saves, reviewed URL import drafts, or existing recipe updates."`
+ Source string `json:"source" jsonschema:"Recipe save source: raw_text, prepared, url, draft, or existing."`
+ RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for existing recipe updates."`
+ DraftID string `json:"draft_id,omitempty" jsonschema:"Draft ID for reviewed URL import draft saves."`
+ Title string `json:"title,omitempty" jsonschema:"Recipe title for raw_text or prepared saves."`
+ Text string `json:"text,omitempty" jsonschema:"Raw recipe text for raw_text saves."`
+ URL string `json:"url,omitempty" jsonschema:"Recipe URL for url imports."`
+ Markdown string `json:"markdown,omitempty" jsonschema:"Recipe markdown for prepared saves, reviewed URL import drafts, or existing recipe updates."`
+ Portions float64 `json:"portions,omitempty" jsonschema:"Recipe portions for prepared saves, reviewed URL import drafts, or existing recipe updates."`
}
// SaveRecipeOutput is the structured output for save_recipe.
type SaveRecipeOutput struct {
- RecipeID string `json:"recipe_id,omitempty"`
- DraftID string `json:"draft_id,omitempty"`
- Title string `json:"title,omitempty"`
- Markdown string `json:"markdown,omitempty"`
- Portions int `json:"portions,omitempty"`
+ RecipeID string `json:"recipe_id,omitempty"`
+ DraftID string `json:"draft_id,omitempty"`
+ Title string `json:"title,omitempty"`
+ Markdown string `json:"markdown,omitempty"`
+ Portions float64 `json:"portions,omitempty"`
}
// DeleteRecipeArguments contains delete_recipe tool arguments.
@@ -38,7 +38,7 @@ func TestCallToolACIDRecipesSave7And9And16SavesReviewedDraft(t *testing.T) {
t.Fatalf("backend draft markdown = %q, want raw markdown preserved", backend.draftSaveMarkdown)
}
if backend.draftSavePortions != 2 {
- t.Fatalf("backend draft portions = %d, want 2", backend.draftSavePortions)
+ t.Fatalf("backend draft portions = %g, want 2", backend.draftSavePortions)
}
if output.RecipeID != "recipe-1" || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
t.Fatalf("draft save output = %#v, want recipe_id only", output)
@@ -94,7 +94,7 @@ func TestCallToolACIDRecipesSave5And12And14And15ReturnsURLImportDraft(t *testing
t.Fatalf("draft output identity = %#v, want draft-1 Pasta with no recipe ID", output)
}
if output.Markdown != "# Pasta\n\n1. Boil pasta." || output.Portions != 2 {
- t.Fatalf("draft output content = %q/%d, want markdown/2", output.Markdown, output.Portions)
+ t.Fatalf("draft output content = %q/%g, want markdown/2", output.Markdown, output.Portions)
}
if backend.saveCalls != 0 || backend.updateCalls != 0 {
t.Fatalf("save/update calls = %d/%d, want none", backend.saveCalls, backend.updateCalls)
@@ -118,7 +118,8 @@ func TestCallToolACIDRecipesRead2SearchesRecipes(t *testing.T) {
}
}
-func TestCallToolACIDRecipesRead5ReadsSingleRecipe(t *testing.T) {
+// recipes.READ.5 recipes.READ.8-1
+func TestCallToolACIDRecipesRead5And8_1ReadsSingleRecipe(t *testing.T) {
backend := &fakeBackend{
recipeMetadata: cooked.RecipeMetadata{
Title: "Pasta",
@@ -128,7 +129,7 @@ func TestCallToolACIDRecipesRead5ReadsSingleRecipe(t *testing.T) {
},
recipeContent: cooked.RecipeContent{
Content: "# Pasta\n\n- 200g pasta\n\n1. Boil pasta.",
- Portions: 2,
+ Portions: 1.5,
},
}
server := NewServer(backend, "test")
@@ -179,9 +180,9 @@ func requireSingleRecipeOutput(t *testing.T, output ReadOutput) {
output.Recipe.EditPermission,
)
}
- if output.Recipe.Content != "# Pasta\n\n- 200g pasta\n\n1. Boil pasta." || output.Recipe.Portions != 2 {
+ if output.Recipe.Content != "# Pasta\n\n- 200g pasta\n\n1. Boil pasta." || output.Recipe.Portions != 1.5 {
t.Fatalf(
- "structured recipe content/portions = %q/%d, want markdown/2",
+ "structured recipe content/portions = %q/%g, want markdown/1.5",
output.Recipe.Content,
output.Recipe.Portions,
)
@@ -219,11 +220,12 @@ func TestCallToolACIDToolsReadTool3RequiresRecipeIDForRecipeTarget(t *testing.T)
}
}
-func TestCallToolACIDRecipesPreviewText1PreviewsRawText(t *testing.T) {
+// recipes.PREVIEW_TEXT.1 recipes.PREVIEW_TEXT.4-1
+func TestCallToolACIDRecipesPreviewText1And4_1PreviewsRawText(t *testing.T) {
backend := &fakeBackend{preview: cooked.RecipeTextPreview{
Title: "Pasta with Tomato Sauce",
Markdown: "# Pasta\n\n1. Boil pasta.",
- Portions: 2,
+ Portions: 2.5,
}}
server := NewServer(backend, "test")
@@ -246,7 +248,7 @@ func TestCallToolACIDRecipesPreviewText1PreviewsRawText(t *testing.T) {
t.Fatalf("backend preview text = %q, want raw text preserved", backend.previewText)
}
if output.Title != "Pasta with Tomato Sauce" || output.Markdown != "# Pasta\n\n1. Boil pasta." ||
- output.Portions != 2 {
+ output.Portions != 2.5 {
t.Fatalf("preview output = %#v, want title/markdown/portions", output)
}
requireStructuredContent(t, result, output)
@@ -286,7 +288,8 @@ func TestCallToolACIDToolsPreviewRecipeTextTool2RequiresText(t *testing.T) {
}
}
-func TestCallToolACIDRecipesSave2And9SavesPreparedRecipe(t *testing.T) {
+// recipes.SAVE.2 recipes.SAVE.8-4 recipes.SAVE.9
+func TestCallToolACIDRecipesSave2And8_4And9SavesPreparedRecipe(t *testing.T) {
backend := &fakeBackend{saveRecipeID: "recipe-1"}
server := NewServer(backend, "test")
@@ -297,7 +300,7 @@ func TestCallToolACIDRecipesSave2And9SavesPreparedRecipe(t *testing.T) {
Source: " prepared ",
Title: " Pasta ",
Markdown: " # Pasta\n\n1. Boil pasta.\n",
- Portions: 2,
+ Portions: 2.5,
},
)
if err != nil {
@@ -313,8 +316,8 @@ func TestCallToolACIDRecipesSave2And9SavesPreparedRecipe(t *testing.T) {
if backend.saveMarkdown != " # Pasta\n\n1. Boil pasta.\n" {
t.Fatalf("backend save markdown = %q, want raw markdown preserved", backend.saveMarkdown)
}
- if backend.savePortions != 2 {
- t.Fatalf("backend save portions = %d, want 2", backend.savePortions)
+ if backend.savePortions != 2.5 {
+ t.Fatalf("backend save portions = %g, want 2.5", backend.savePortions)
}
if output.RecipeID != "recipe-1" {
t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID)
@@ -393,7 +396,7 @@ func TestCallToolACIDRecipesSave1And11SavesRawTextRecipe(t *testing.T) {
t.Fatalf("save markdown = %q, want preview markdown", backend.saveMarkdown)
}
if backend.savePortions != 2 {
- t.Fatalf("save portions = %d, want preview portions 2", backend.savePortions)
+ t.Fatalf("save portions = %g, want preview portions 2", backend.savePortions)
}
if output.RecipeID != "recipe-1" {
t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID)
@@ -425,7 +428,8 @@ func TestCallToolACIDRecipesSave1_1To1_2RequiresRawTextFields(t *testing.T) {
}
}
-func TestCallToolACIDRecipesSave8And9UpdatesExistingRecipe(t *testing.T) {
+// recipes.SAVE.8 recipes.SAVE.8-4 recipes.SAVE.9
+func TestCallToolACIDRecipesSave8And8_4And9UpdatesExistingRecipe(t *testing.T) {
backend := &fakeBackend{}
server := NewServer(backend, "test")
@@ -436,7 +440,7 @@ func TestCallToolACIDRecipesSave8And9UpdatesExistingRecipe(t *testing.T) {
Source: " existing ",
RecipeID: " recipe-1 ",
Markdown: " # Pasta\n\n1. Boil pasta.\n",
- Portions: 4,
+ Portions: 4.5,
},
)
if err != nil {
@@ -452,8 +456,8 @@ func TestCallToolACIDRecipesSave8And9UpdatesExistingRecipe(t *testing.T) {
if backend.updateMarkdown != " # Pasta\n\n1. Boil pasta.\n" {
t.Fatalf("backend update markdown = %q, want raw markdown preserved", backend.updateMarkdown)
}
- if backend.updatePortions != 4 {
- t.Fatalf("backend update portions = %d, want 4", backend.updatePortions)
+ if backend.updatePortions != 4.5 {
+ t.Fatalf("backend update portions = %g, want 4.5", backend.updatePortions)
}
if output.RecipeID != "recipe-1" {
t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID)
@@ -958,13 +962,13 @@ type fakeBackend struct {
importURL string
saveTitle string
saveMarkdown string
- savePortions int
+ savePortions float64
draftSaveID string
draftSaveMarkdown string
- draftSavePortions int
+ draftSavePortions float64
updateRecipeID string
updateMarkdown string
- updatePortions int
+ updatePortions float64
deleteRecipeID string
addShoppingListResult cooked.AddShoppingListResult
addIngredients string
@@ -1031,7 +1035,7 @@ func (f *fakeBackend) PreviewRecipeText(_ context.Context, title, text string) (
return f.preview, nil
}
-func (f *fakeBackend) SavePreparedRecipe(_ context.Context, title, markdown string, portions int) (string, error) {
+func (f *fakeBackend) SavePreparedRecipe(_ context.Context, title, markdown string, portions float64) (string, error) {
f.saveTitle = title
f.saveMarkdown = markdown
f.savePortions = portions
@@ -1043,7 +1047,7 @@ func (f *fakeBackend) SavePreparedRecipe(_ context.Context, title, markdown stri
return "recipe-1", nil
}
-func (f *fakeBackend) SaveRecipeDraft(_ context.Context, draftID, markdown string, portions int) (string, error) {
+func (f *fakeBackend) SaveRecipeDraft(_ context.Context, draftID, markdown string, portions float64) (string, error) {
f.draftSaveID = draftID
f.draftSaveMarkdown = markdown
f.draftSavePortions = portions
@@ -1055,7 +1059,7 @@ func (f *fakeBackend) SaveRecipeDraft(_ context.Context, draftID, markdown strin
return "recipe-1", nil
}
-func (f *fakeBackend) UpdateRecipeContent(_ context.Context, recipeID, markdown string, portions int) error {
+func (f *fakeBackend) UpdateRecipeContent(_ context.Context, recipeID, markdown string, portions float64) error {
f.updateRecipeID = recipeID
f.updateMarkdown = markdown
f.updatePortions = portions