@@ -62,6 +62,14 @@ type AddShoppingListResult struct {
Ingredients []string
}
+// ShoppingListProductGroupUpdate is a full shopping-list product-group update.
+type ShoppingListProductGroupUpdate struct {
+ Name string
+ Quantity string
+ AisleID string
+ Selected bool
+}
+
// RecipeCard is a saved recipe summary returned by Cooked list and search endpoints.
type RecipeCard struct {
ID string `json:"id"`
@@ -183,6 +191,22 @@ func (c *Client) ReplaceShoppingListSelection(ctx context.Context, ids []string)
)
}
+// UpdateShoppingListProductGroup logs in when needed and updates a shopping-list product group.
+func (c *Client) UpdateShoppingListProductGroup(
+ ctx context.Context,
+ productGroupID string,
+ update ShoppingListProductGroupUpdate,
+) error {
+ body, err := json.Marshal(updateShoppingListProductGroupRequest(update))
+ if err != nil {
+ return fmt.Errorf("encode Cooked shopping-list product-group update request: %w", err)
+ }
+
+ path := "/api/user/{username}/shopping-list/product-groups/" + url.PathEscape(productGroupID)
+
+ return c.doAuthenticated(ctx, http.MethodPut, path, 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{}
@@ -442,6 +466,13 @@ type replaceShoppingListSelectionRequest struct {
Selected []string `json:"selected"`
}
+type updateShoppingListProductGroupRequest struct {
+ Name string `json:"name"`
+ Quantity string `json:"quantity"`
+ AisleID string `json:"aisle-id"`
+ Selected bool `json:"selected"`
+}
+
type recipeListResponse struct {
Recipes []RecipeCard `json:"recipes"`
}
@@ -244,6 +244,30 @@ func TestReplaceShoppingListSelectionACIDShoppingListSelection1And8ReplacesSelec
}
}
+func TestUpdateShoppingListProductGroupACIDShoppingListUpdateItem1UpdatesProductGroup(t *testing.T) {
+ var updateCalls int
+ client, closeServer := newTestClient(t, updateShoppingListProductGroupTestHandler(t, &updateCalls))
+ defer closeServer()
+
+ err := client.UpdateShoppingListProductGroup(
+ context.Background(),
+ "product/with space",
+ ShoppingListProductGroupUpdate{
+ Name: "Pasta",
+ Quantity: "250g",
+ AisleID: "pantry",
+ Selected: false,
+ },
+ )
+ if err != nil {
+ t.Fatalf("UpdateShoppingListProductGroup() error = %v", err)
+ }
+
+ if updateCalls != 1 {
+ t.Fatalf("update calls = %d, want 1", updateCalls)
+ }
+}
+
func TestUserPathEscapesAuthenticatedUsername(t *testing.T) {
client := &Client{authenticatedUsername: "user/name"}
@@ -565,6 +589,25 @@ func replaceShoppingListSelectionTestHandler(t *testing.T, replaceCalls *int) ht
}
}
+func updateShoppingListProductGroupTestHandler(t *testing.T, updateCalls *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/product%2Fwith%20space":
+ requireMethod(t, r, http.MethodPut, "update shopping-list product group")
+ requireSessionCookie(t, r)
+ requireUpdateShoppingListProductGroupRequest(t, r)
+ (*updateCalls)++
+ w.WriteHeader(http.StatusNoContent)
+ default:
+ t.Fatalf("unexpected path %s", r.URL.EscapedPath())
+ }
+ }
+}
+
func requireRemoveShoppingListProductGroupsRequest(t *testing.T, r *http.Request) {
t.Helper()
@@ -593,6 +636,29 @@ func requireReplaceShoppingListSelectionRequest(t *testing.T, r *http.Request) {
}
}
+func requireUpdateShoppingListProductGroupRequest(t *testing.T, r *http.Request) {
+ t.Helper()
+
+ var request struct {
+ Name string `json:"name"`
+ Quantity string `json:"quantity"`
+ AisleID string `json:"aisle-id"`
+ Selected *bool `json:"selected"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+ t.Fatalf("decode update shopping-list product group request: %v", err)
+ }
+ if request.Name != "Pasta" || request.Quantity != "250g" || request.AisleID != "pantry" {
+ t.Fatalf("update request = %#v, want configured name, quantity, and aisle", request)
+ }
+ if request.Selected == nil {
+ t.Fatal("update request selected missing, want explicit false")
+ }
+ if *request.Selected {
+ t.Fatal("update request selected = true, want false")
+ }
+}
+
func requirePreviewRecipeTextRequest(t *testing.T, r *http.Request) {
t.Helper()