test(shopping): add missing service tests for stale items, dedup, and household isolation

- generateFromPlan removes stale generated items
- sourceRecipes deduplicates when same recipe appears in two slots
- checkItem throws ResourceNotFoundException on household mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 19:50:27 +02:00
parent 9d210befa1
commit eb5ee1ab5a

View File

@@ -468,6 +468,97 @@ class ShoppingServiceTest {
.isInstanceOf(ResourceNotFoundException.class); .isInstanceOf(ResourceNotFoundException.class);
} }
// ── Generate removes stale items ──
@Test
void generateFromPlanShouldRemoveStaleGeneratedItems() {
var household = testHousehold();
var plan = testWeekPlan(household);
var existingList = testShoppingList(household, plan);
var tomato = testIngredient(household, "Tomatoes", false);
var onion = testIngredient(household, "Onions", false);
// Existing list has both tomatoes and onions (generated)
var tomatoItem = testItem(existingList, tomato, new BigDecimal("2.00"), "pcs");
tomatoItem.setSourceRecipes(new UUID[]{UUID.randomUUID()});
existingList.getItems().add(tomatoItem);
var onionItem = testItem(existingList, onion, new BigDecimal("1.00"), "pcs");
onionItem.setSourceRecipes(new UUID[]{UUID.randomUUID()});
existingList.getItems().add(onionItem);
// New plan only has tomatoes — onions removed from recipes
var recipe = testRecipe(household, "Sauce");
recipe.getIngredients().add(new RecipeIngredient(recipe, tomato, new BigDecimal("3.00"), "pcs", (short) 1));
var slot = new WeekPlanSlot(plan, recipe, WEEK_START);
setId(slot, WeekPlanSlot.class, UUID.randomUUID());
plan.getSlots().add(slot);
when(weekPlanRepository.findById(plan.getId())).thenReturn(Optional.of(plan));
when(shoppingListRepository.findByHouseholdIdAndWeekPlanWeekStart(HOUSEHOLD_ID, WEEK_START))
.thenReturn(Optional.of(existingList));
when(shoppingListRepository.save(any(ShoppingList.class))).thenAnswer(i -> i.getArgument(0));
when(recipeRepository.findAllById(any())).thenReturn(List.of(recipe));
ShoppingListResponse result = shoppingService.generateFromPlan(HOUSEHOLD_ID, plan.getId());
assertThat(result.items()).hasSize(1);
assertThat(result.items().getFirst().name()).isEqualTo("Tomatoes");
}
// ── Source recipes deduplication ──
@Test
void generateFromPlanShouldDeduplicateSourceRecipesWhenSameRecipeInTwoSlots() {
var household = testHousehold();
var plan = testWeekPlan(household);
var recipe = testRecipe(household, "Pasta");
var tomato = testIngredient(household, "Tomatoes", false);
recipe.getIngredients().add(new RecipeIngredient(recipe, tomato, new BigDecimal("2.00"), "pcs", (short) 1));
// Same recipe in two slots
var slot1 = new WeekPlanSlot(plan, recipe, WEEK_START);
setId(slot1, WeekPlanSlot.class, UUID.randomUUID());
var slot2 = new WeekPlanSlot(plan, recipe, WEEK_START.plusDays(2));
setId(slot2, WeekPlanSlot.class, UUID.randomUUID());
plan.getSlots().add(slot1);
plan.getSlots().add(slot2);
when(weekPlanRepository.findById(plan.getId())).thenReturn(Optional.of(plan));
when(shoppingListRepository.findByHouseholdIdAndWeekPlanWeekStart(HOUSEHOLD_ID, WEEK_START))
.thenReturn(Optional.empty());
when(shoppingListRepository.save(any(ShoppingList.class))).thenAnswer(i -> {
ShoppingList sl = i.getArgument(0);
if (sl.getId() == null) setId(sl, ShoppingList.class, UUID.randomUUID());
return sl;
});
when(recipeRepository.findAllById(any())).thenReturn(List.of(recipe));
ShoppingListResponse result = shoppingService.generateFromPlan(HOUSEHOLD_ID, plan.getId());
assertThat(result.items()).hasSize(1);
assertThat(result.items().getFirst().sourceRecipes()).hasSize(1); // deduplicated
}
// ── checkItem household isolation ──
@Test
void checkItemShouldThrowWhenHouseholdMismatch() {
var otherHousehold = new Household("Other family", null);
setId(otherHousehold, Household.class, UUID.randomUUID());
var plan = new WeekPlan(otherHousehold, WEEK_START);
setId(plan, WeekPlan.class, UUID.randomUUID());
var list = new ShoppingList(otherHousehold, plan);
setId(list, ShoppingList.class, UUID.randomUUID());
when(shoppingListRepository.findById(list.getId())).thenReturn(Optional.of(list));
assertThatThrownBy(() -> shoppingService.checkItem(
HOUSEHOLD_ID, list.getId(), UUID.randomUUID(), new CheckItemRequest(true), UUID.randomUUID()))
.isInstanceOf(ResourceNotFoundException.class);
}
// ── Generate from plan with empty slots ── // ── Generate from plan with empty slots ──
@Test @Test