From 2314ecf0bb829b84c24fc41b4a781f04cef4dfbd Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 10 Jun 2026 16:51:43 -0600 Subject: [PATCH] recipes: save extraction draft client --- internal/cooked/client.go | 23 ++++++++++++++-- internal/cooked/client_test.go | 50 ++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/internal/cooked/client.go b/internal/cooked/client.go index 7207ab6b9f911c08126dd2d34dd1f24b4a0f16f1..098330d4e09f2b12e297a2b0d5ce55c0b7ba8674 100644 --- a/internal/cooked/client.go +++ b/internal/cooked/client.go @@ -295,9 +295,28 @@ func (c *Client) SavePreparedRecipe(ctx context.Context, title, markdown string, return response.RecipeID, nil } +// 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) { + body, err := json.Marshal(recipeContentRequest{Description: markdown, Portions: portions}) + if err != nil { + return "", fmt.Errorf("encode Cooked recipe draft save request: %w", err) + } + + var response saveRecipeResponse + decodeSave := func(decoder *json.Decoder) error { + return decoder.Decode(&response) + } + path := "/api/extract/" + url.PathEscape(draftID) + "/save" + if err := c.doAuthenticated(ctx, http.MethodPost, path, body, decodeSave); err != nil { + return "", err + } + + return response.RecipeID, nil +} + // 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 { - body, err := json.Marshal(updateRecipeContentRequest{Description: markdown, Portions: portions}) + body, err := json.Marshal(recipeContentRequest{Description: markdown, Portions: portions}) if err != nil { return fmt.Errorf("encode Cooked recipe update request: %w", err) } @@ -516,7 +535,7 @@ type savePreparedRecipeRequest struct { Portions int `json:"portions"` } -type updateRecipeContentRequest struct { +type recipeContentRequest struct { Description string `json:"description"` Portions int `json:"portions"` } diff --git a/internal/cooked/client_test.go b/internal/cooked/client_test.go index 611fff15cdbba58beefe6634069b8b6a160b10d9..3677dfee2649a4108605e7faa1f50980598de1e4 100644 --- a/internal/cooked/client_test.go +++ b/internal/cooked/client_test.go @@ -144,6 +144,25 @@ func TestSavePreparedRecipeACIDRecipesSave2And9And13SavesMarkdownAsDescription(t } } +// recipes.SAVE.7 recipes.SAVE.9 recipes.SAVE.13 +func TestSaveRecipeDraftACIDRecipesSave7And9And13SavesReviewedExtractionDraft(t *testing.T) { + var saveCalls int + client, closeServer := newTestClient(t, saveRecipeDraftTestHandler(t, &saveCalls)) + defer closeServer() + + recipeID, err := client.SaveRecipeDraft(context.Background(), "draft/with space", "# Pasta\n\n1. Boil pasta.", 4) + if err != nil { + t.Fatalf("SaveRecipeDraft() error = %v", err) + } + + if saveCalls != 1 { + t.Fatalf("save calls = %d, want 1", saveCalls) + } + if recipeID != "recipe-1" { + t.Fatalf("recipe ID = %q, want recipe-1", recipeID) + } +} + func TestUpdateRecipeContentACIDRecipesSave8And9And13UpdatesExistingRecipe(t *testing.T) { var updateCalls int client, closeServer := newTestClient(t, updateRecipeContentTestHandler(t, &updateCalls)) @@ -492,6 +511,25 @@ func savePreparedRecipeTestHandler(t *testing.T, saveCalls *int) http.HandlerFun } } +func saveRecipeDraftTestHandler(t *testing.T, saveCalls *int) http.HandlerFunc { + t.Helper() + + return func(w http.ResponseWriter, r *http.Request) { + switch r.URL.EscapedPath() { + case "/api/public/login": + writeLoginResponse(t, w) + case "/api/extract/draft%2Fwith%20space/save": + requireMethod(t, r, http.MethodPost, "save recipe draft") + requireSessionCookie(t, r) + requireRecipeContentRequest(t, r, "draft save") + (*saveCalls)++ + writeJSON(t, w, saveRecipeResponse{RecipeID: "recipe-1"}) + default: + t.Fatalf("unexpected path %s", r.URL.EscapedPath()) + } + } +} + func updateRecipeContentTestHandler(t *testing.T, updateCalls *int) http.HandlerFunc { t.Helper() @@ -502,7 +540,7 @@ func updateRecipeContentTestHandler(t *testing.T, updateCalls *int) http.Handler case "/api/recipe/recipe%2Fwith%20space/content": requireMethod(t, r, http.MethodPost, "update recipe content") requireSessionCookie(t, r) - requireUpdateRecipeContentRequest(t, r) + requireRecipeContentRequest(t, r, "update") (*updateCalls)++ w.WriteHeader(http.StatusNoContent) default: @@ -760,18 +798,18 @@ func requireSavePreparedRecipeRequest(t *testing.T, r *http.Request) { } } -func requireUpdateRecipeContentRequest(t *testing.T, r *http.Request) { +func requireRecipeContentRequest(t *testing.T, r *http.Request, description string) { t.Helper() - var request updateRecipeContentRequest + var request recipeContentRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - t.Fatalf("decode update recipe content request: %v", err) + t.Fatalf("decode %s recipe content request: %v", description, err) } if request.Description != "# Pasta\n\n1. Boil pasta." { - t.Fatalf("update description = %q, want markdown", request.Description) + t.Fatalf("%s description = %q, want markdown", description, request.Description) } if request.Portions != 4 { - t.Fatalf("update portions = %d, want 4", request.Portions) + t.Fatalf("%s portions = %d, want 4", description, request.Portions) } }