diff --git a/internal/cooked/client.go b/internal/cooked/client.go index fdc4197e43c2879f4d0c1a47a4d3d942d5707295..722dc78194d628b9438345b5f0ab55276e4cee57 100644 --- a/internal/cooked/client.go +++ b/internal/cooked/client.go @@ -167,6 +167,22 @@ func (c *Client) RemoveShoppingListProductGroups(ctx context.Context, ids []stri return c.doAuthenticated(ctx, http.MethodDelete, "/api/user/{username}/shopping-list/product-groups", body, nil) } +// ReplaceShoppingListSelection logs in when needed and replaces the selected shopping-list product groups. +func (c *Client) ReplaceShoppingListSelection(ctx context.Context, ids []string) error { + body, err := json.Marshal(replaceShoppingListSelectionRequest{Selected: ids}) + if err != nil { + return fmt.Errorf("encode Cooked shopping-list selection request: %w", err) + } + + return c.doAuthenticated( + ctx, + http.MethodPut, + "/api/user/{username}/shopping-list/product-groups/selection", + 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{} @@ -422,6 +438,10 @@ type removeShoppingListProductGroupsRequest struct { IDs []string `json:"ids"` } +type replaceShoppingListSelectionRequest struct { + Selected []string `json:"selected"` +} + type recipeListResponse struct { Recipes []RecipeCard `json:"recipes"` } diff --git a/internal/cooked/client_test.go b/internal/cooked/client_test.go index cce4f9e77892f84c8850ea8be89b3b3587e3330f..29bc27e12ad009cfb6e6d57c17668699a8bb8561 100644 --- a/internal/cooked/client_test.go +++ b/internal/cooked/client_test.go @@ -229,6 +229,21 @@ func TestRemoveShoppingListProductGroupsACIDShoppingListRemove1RemovesProductGro } } +func TestReplaceShoppingListSelectionACIDShoppingListSelection1And8ReplacesSelectedSet(t *testing.T) { + var replaceCalls int + client, closeServer := newTestClient(t, replaceShoppingListSelectionTestHandler(t, &replaceCalls)) + defer closeServer() + + err := client.ReplaceShoppingListSelection(context.Background(), []string{"pasta", "tomato"}) + if err != nil { + t.Fatalf("ReplaceShoppingListSelection() error = %v", err) + } + + if replaceCalls != 1 { + t.Fatalf("replace calls = %d, want 1", replaceCalls) + } +} + func TestUserPathEscapesAuthenticatedUsername(t *testing.T) { client := &Client{authenticatedUsername: "user/name"} @@ -531,6 +546,25 @@ func removeShoppingListProductGroupsTestHandler(t *testing.T, removeCalls *int) } } +func replaceShoppingListSelectionTestHandler(t *testing.T, replaceCalls *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/selection": + requireMethod(t, r, http.MethodPut, "replace shopping-list selection") + requireSessionCookie(t, r) + requireReplaceShoppingListSelectionRequest(t, r) + (*replaceCalls)++ + w.WriteHeader(http.StatusNoContent) + default: + t.Fatalf("unexpected path %s", r.URL.EscapedPath()) + } + } +} + func requireRemoveShoppingListProductGroupsRequest(t *testing.T, r *http.Request) { t.Helper() @@ -545,6 +579,20 @@ func requireRemoveShoppingListProductGroupsRequest(t *testing.T, r *http.Request } } +func requireReplaceShoppingListSelectionRequest(t *testing.T, r *http.Request) { + t.Helper() + + var request struct { + Selected []string `json:"selected"` + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + t.Fatalf("decode replace shopping-list selection request: %v", err) + } + if len(request.Selected) != 2 || request.Selected[0] != "pasta" || request.Selected[1] != "tomato" { + t.Fatalf("selected IDs = %#v, want pasta and tomato", request.Selected) + } +} + func requirePreviewRecipeTextRequest(t *testing.T, r *http.Request) { t.Helper()