From 604f123eeec67eb2629d37366f4f65712de31e17 Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 10 Jun 2026 15:50:12 -0600 Subject: [PATCH] shopping-list: remove product groups client --- internal/cooked/client.go | 14 ++++++++++ internal/cooked/client_test.go | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/internal/cooked/client.go b/internal/cooked/client.go index 741b995553695d20e23f11a3fa7c880871e4b3aa..fdc4197e43c2879f4d0c1a47a4d3d942d5707295 100644 --- a/internal/cooked/client.go +++ b/internal/cooked/client.go @@ -157,6 +157,16 @@ func (c *Client) AddShoppingListIngredients( return AddShoppingListResult{AddedCount: *response.AddedCount, Ingredients: response.Ingredients}, nil } +// RemoveShoppingListProductGroups logs in when needed and removes shopping-list product groups. +func (c *Client) RemoveShoppingListProductGroups(ctx context.Context, ids []string) error { + body, err := json.Marshal(removeShoppingListProductGroupsRequest{IDs: ids}) + if err != nil { + return fmt.Errorf("encode Cooked shopping-list remove request: %w", err) + } + + return c.doAuthenticated(ctx, http.MethodDelete, "/api/user/{username}/shopping-list/product-groups", body, nil) +} + // ListRecipes logs in when needed and returns one page of saved recipes. func (c *Client) ListRecipes(ctx context.Context, page, limit int) ([]RecipeCard, error) { query := url.Values{} @@ -408,6 +418,10 @@ type addShoppingListResponse struct { Ingredients []string `json:"ingredients"` } +type removeShoppingListProductGroupsRequest struct { + IDs []string `json:"ids"` +} + type recipeListResponse struct { Recipes []RecipeCard `json:"recipes"` } diff --git a/internal/cooked/client_test.go b/internal/cooked/client_test.go index 719ba50a6b9edd803a2b8d5f388210859800f1e2..cce4f9e77892f84c8850ea8be89b3b3587e3330f 100644 --- a/internal/cooked/client_test.go +++ b/internal/cooked/client_test.go @@ -214,6 +214,21 @@ func TestAddShoppingListIngredientsACIDShoppingListAdd1To4AddsIngredients(t *tes } } +func TestRemoveShoppingListProductGroupsACIDShoppingListRemove1RemovesProductGroups(t *testing.T) { + var removeCalls int + client, closeServer := newTestClient(t, removeShoppingListProductGroupsTestHandler(t, &removeCalls)) + defer closeServer() + + err := client.RemoveShoppingListProductGroups(context.Background(), []string{"pasta", "tomato"}) + if err != nil { + t.Fatalf("RemoveShoppingListProductGroups() error = %v", err) + } + + if removeCalls != 1 { + t.Fatalf("remove calls = %d, want 1", removeCalls) + } +} + func TestUserPathEscapesAuthenticatedUsername(t *testing.T) { client := &Client{authenticatedUsername: "user/name"} @@ -497,6 +512,39 @@ func writeAddShoppingListIngredientsResponse(t *testing.T, w http.ResponseWriter } } +func removeShoppingListProductGroupsTestHandler(t *testing.T, removeCalls *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/user/returned-user/shopping-list/product-groups": + requireMethod(t, r, http.MethodDelete, "remove shopping-list product groups") + requireSessionCookie(t, r) + requireRemoveShoppingListProductGroupsRequest(t, r) + (*removeCalls)++ + w.WriteHeader(http.StatusNoContent) + default: + t.Fatalf("unexpected path %s", r.URL.EscapedPath()) + } + } +} + +func requireRemoveShoppingListProductGroupsRequest(t *testing.T, r *http.Request) { + t.Helper() + + var request struct { + IDs []string `json:"ids"` + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + t.Fatalf("decode remove shopping-list product groups request: %v", err) + } + if len(request.IDs) != 2 || request.IDs[0] != "pasta" || request.IDs[1] != "tomato" { + t.Fatalf("remove IDs = %#v, want pasta and tomato", request.IDs) + } +} + func requirePreviewRecipeTextRequest(t *testing.T, r *http.Request) { t.Helper()