feat(shopping): add GET /v1/shopping-lists endpoint and planner-only guard
New week-based lookup endpoint with optional weekStart param (defaults to current week). Generate endpoint now enforced with @RequiresHouseholdRole. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.recipeapp.shopping;
|
||||
|
||||
import com.recipeapp.common.RequiresHouseholdRole;
|
||||
import com.recipeapp.common.ResourceNotFoundException;
|
||||
import com.recipeapp.recipe.HouseholdResolver;
|
||||
import com.recipeapp.shopping.dto.*;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@@ -19,8 +22,21 @@ public class ShoppingListController {
|
||||
this.householdResolver = householdResolver;
|
||||
}
|
||||
|
||||
@GetMapping("/v1/shopping-lists")
|
||||
public ShoppingListResponse getByWeekStart(
|
||||
@RequestParam(required = false) LocalDate weekStart,
|
||||
Principal principal) {
|
||||
UUID householdId = householdResolver.resolve(principal.getName());
|
||||
ShoppingListResponse response = shoppingService.getByWeekStart(householdId, weekStart);
|
||||
if (response == null) {
|
||||
throw new ResourceNotFoundException("No shopping list for this week");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@PostMapping("/v1/week-plans/{id}/shopping-list")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@RequiresHouseholdRole("planner")
|
||||
public ShoppingListResponse generateFromPlan(@PathVariable UUID id, Principal principal) {
|
||||
UUID householdId = householdResolver.resolve(principal.getName());
|
||||
return shoppingService.generateFromPlan(householdId, id);
|
||||
|
||||
@@ -49,6 +49,32 @@ class ShoppingListControllerTest {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByWeekStartShouldReturn200() throws Exception {
|
||||
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 3, List.of());
|
||||
|
||||
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
|
||||
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(response);
|
||||
|
||||
mockMvc.perform(get("/v1/shopping-lists")
|
||||
.param("weekStart", "2026-04-06")
|
||||
.principal(() -> "sarah@example.com"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(LIST_ID.toString()))
|
||||
.andExpect(jsonPath("$.filteredStaplesCount").value(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByWeekStartShouldReturn404WhenNoListExists() throws Exception {
|
||||
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
|
||||
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(null);
|
||||
|
||||
mockMvc.perform(get("/v1/shopping-lists")
|
||||
.param("weekStart", "2026-04-06")
|
||||
.principal(() -> "sarah@example.com"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateFromPlanShouldReturn201() throws Exception {
|
||||
var recipeId = UUID.randomUUID();
|
||||
|
||||
Reference in New Issue
Block a user