feat: D1 — Shopping list (Issue #30) #43
@@ -468,6 +468,97 @@ class ShoppingServiceTest {
|
||||
.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 ──
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user