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:
2026-04-04 18:35:31 +02:00
parent 5325f4827e
commit 16b70bd818
2 changed files with 42 additions and 0 deletions

View File

@@ -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);

View File

@@ -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();