feat(shopping): extend response with generatedAt, filteredStaplesCount, RecipeRef

Shopping list response now includes generatedAt timestamp, count of
filtered staples, and recipe names (not just UUIDs) in source references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 18:29:07 +02:00
parent 7e254fc280
commit 93e8bf9e41
5 changed files with 77 additions and 12 deletions

View File

@@ -16,6 +16,7 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@@ -50,11 +51,13 @@ class ShoppingListControllerTest {
@Test
void generateFromPlanShouldReturn201() throws Exception {
var recipeId = UUID.randomUUID();
var item = new ShoppingListItemResponse(
ITEM_ID, UUID.randomUUID(), "Tomatoes",
new ShoppingListItemResponse.CategoryRef(UUID.randomUUID(), "Produce"),
new BigDecimal("4.00"), "pcs", false, null, List.of(UUID.randomUUID()));
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, List.of(item));
new BigDecimal("4.00"), "pcs", false, null,
List.of(new ShoppingListItemResponse.RecipeRef(recipeId, "Spaghetti")));
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 2, List.of(item));
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(shoppingService.generateFromPlan(HOUSEHOLD_ID, PLAN_ID)).thenReturn(response);
@@ -68,7 +71,7 @@ class ShoppingListControllerTest {
@Test
void getShoppingListShouldReturn200() throws Exception {
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, List.of());
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 0, List.of());
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(shoppingService.getShoppingList(HOUSEHOLD_ID, LIST_ID)).thenReturn(response);
@@ -84,7 +87,8 @@ class ShoppingListControllerTest {
void checkItemShouldReturn200() throws Exception {
var response = new ShoppingListItemResponse(
ITEM_ID, UUID.randomUUID(), "Tomatoes", null,
new BigDecimal("4.00"), "pcs", true, USER_ID, List.of());
new BigDecimal("4.00"), "pcs", true, USER_ID,
List.of());
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(householdResolver.resolveUserId("sarah@example.com")).thenReturn(USER_ID);
@@ -104,7 +108,8 @@ class ShoppingListControllerTest {
void addItemShouldReturn201() throws Exception {
var response = new ShoppingListItemResponse(
ITEM_ID, null, "Paper towels", null,
new BigDecimal("1"), "", false, null, List.of());
new BigDecimal("1"), "", false, null,
List.of());
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(shoppingService.addItem(eq(HOUSEHOLD_ID), eq(LIST_ID), any(AddItemRequest.class)))

View File

@@ -9,6 +9,7 @@ import com.recipeapp.planning.WeekPlanRepository;
import com.recipeapp.planning.entity.WeekPlan;
import com.recipeapp.planning.entity.WeekPlanSlot;
import com.recipeapp.recipe.IngredientRepository;
import com.recipeapp.recipe.RecipeRepository;
import com.recipeapp.recipe.entity.Ingredient;
import com.recipeapp.recipe.entity.IngredientCategory;
import com.recipeapp.recipe.entity.Recipe;
@@ -39,6 +40,7 @@ class ShoppingServiceTest {
@Mock private HouseholdRepository householdRepository;
@Mock private IngredientRepository ingredientRepository;
@Mock private UserAccountRepository userAccountRepository;
@Mock private RecipeRepository recipeRepository;
@InjectMocks private ShoppingService shoppingService;
@@ -124,15 +126,18 @@ class ShoppingServiceTest {
setId(sl, ShoppingList.class, UUID.randomUUID());
return sl;
});
when(recipeRepository.findAllById(any())).thenReturn(List.of(recipe1, recipe2));
ShoppingListResponse result = shoppingService.generateFromPlan(HOUSEHOLD_ID, plan.getId());
assertThat(result.items()).hasSize(2); // tomatoes + cheese (salt filtered)
assertThat(result.filteredStaplesCount()).isEqualTo(1); // salt
var tomatoItem = result.items().stream()
.filter(i -> "Tomatoes".equals(i.name())).findFirst().orElseThrow();
assertThat(tomatoItem.quantity()).isEqualByComparingTo(new BigDecimal("5.00")); // 2 + 3
assertThat(tomatoItem.sourceRecipes()).hasSize(2);
assertThat(tomatoItem.sourceRecipes().get(0).name()).isNotNull();
var cheeseItem = result.items().stream()
.filter(i -> "Cheese".equals(i.name())).findFirst().orElseThrow();
@@ -164,6 +169,7 @@ class ShoppingServiceTest {
ShoppingListResponse result = shoppingService.getShoppingList(HOUSEHOLD_ID, list.getId());
assertThat(result.id()).isEqualTo(list.getId());
assertThat(result.generatedAt()).isNotNull();
assertThat(result.items()).hasSize(1);
assertThat(result.items().getFirst().name()).isEqualTo("Tomatoes");
}