Cleanup after initial plan

This commit is contained in:
Aerilyn Weber 2026-05-19 10:13:40 +09:00
parent d2a7e652b3
commit 245520fb50
53 changed files with 6733 additions and 621 deletions

View file

@ -161,5 +161,112 @@ describe(SuggestionEngineService.name, () => {
// Variety calculation: 7 days ago / 14 days = 0.5
expect(suggestions[0]!.scores.variety).toBeCloseTo(0.5, 1);
});
it('triggers reasoning branches for partial coverage and urgent items', async () => {
const recipeC = {
_id: 'recipeC',
name: 'Recipe C',
ingredients: [
{ productId: 'prod1', quantity: 10, isOptional: false },
{ productId: 'prod2', quantity: 10, isOptional: false },
],
};
mockRecipesRepo.findByHousehold.mockResolvedValue({ data: [recipeC] });
// 1. Coverage: (10/10 + 5/10)/2 = 0.75 (hits >0.5)
// 2. Urgency: both set to urgent = 1.0 (hits >0.7)
mockPantryRepo.findActiveByHousehold.mockResolvedValue([
{ productId: 'prod1', quantity: 10, freshnessEstimate: { urgency: 'urgent' } },
{ productId: 'prod2', quantity: 5, freshnessEstimate: { urgency: 'urgent' } },
]);
mockNutritionRepo.findByUser.mockResolvedValue(null);
mockMealPlanRepo.findByHousehold.mockResolvedValue({ data: [] });
const suggestions = await service.getSuggestions('hh1', 'user1');
expect(suggestions[0]!.scores.coverage).toBe(0.75);
expect(suggestions[0]!.scores.urgency).toBe(1);
expect(suggestions[0]!.reasoning).toContain('Uses several ingredients already stocked in your pantry.');
expect(suggestions[0]!.reasoning).toContain('High priority: Saves expiring pantry items from going to waste!');
});
it('triggers reasoning for moderately soon-to-expire items', async () => {
const recipeD = {
_id: 'recipeD',
name: 'Recipe D',
ingredients: [{ productId: 'prod1', quantity: 5, isOptional: false }],
};
mockRecipesRepo.findByHousehold.mockResolvedValue({ data: [recipeD] });
// Urgency soon/expiringSoon has weight 0.7 (hits >0.4 branch)
mockPantryRepo.findActiveByHousehold.mockResolvedValue([
{ productId: 'prod1', quantity: 5, freshnessEstimate: { urgency: 'expiringSoon' } },
]);
mockNutritionRepo.findByUser.mockResolvedValue(null);
mockMealPlanRepo.findByHousehold.mockResolvedValue({ data: [] });
const suggestions = await service.getSuggestions('hh1', 'user1');
expect(suggestions[0]!.scores.urgency).toBe(0.7);
expect(suggestions[0]!.reasoning).toContain('Helps use up items that should be consumed soon.');
});
it('aggregates duplicate pantry items and handles normal/default urgencies', async () => {
const recipeE = {
_id: 'recipeE',
name: 'Recipe E',
ingredients: [
{ productId: 'prod1', quantity: 5, isOptional: false },
],
};
mockRecipesRepo.findByHousehold.mockResolvedValue({ data: [recipeE] });
mockPantryRepo.findActiveByHousehold.mockResolvedValue([
{ productId: 'prod1', quantity: 2, freshnessEstimate: { daysRemaining: 5, urgency: 'normal' } },
{ productId: 'prod1', quantity: 3, freshnessEstimate: { daysRemaining: 10, urgency: 'unknown-type' } },
]);
mockNutritionRepo.findByUser.mockResolvedValue(null);
mockMealPlanRepo.findByHousehold.mockResolvedValue({ data: [] });
const suggestions = await service.getSuggestions('hh1', 'user1');
expect(suggestions[0]!.scores.coverage).toBe(1);
expect(suggestions[0]!.scores.urgency).toBe(0.3);
});
it('covers boundary logic for nameless recipes, custom meals, default targets and private weights', async () => {
// 1. Nameless recipe and recipe without ingredients
const rawRecipe = { _id: 'recipeMissingProps' };
mockRecipesRepo.findByHousehold.mockResolvedValue({ data: [rawRecipe] });
// 2. Active target with partial/falsy info
mockNutritionRepo.findByUser.mockResolvedValue({ dailyCalories: 0, proteinG: 0 });
// 3. Last eaten containing a custom meal without recipeId (should continue)
mockMealPlanRepo.findByHousehold.mockResolvedValue({
data: [
{
days: [
{
date: '2026-05-19',
meals: [
{ customName: 'Snack' }, // no recipeId!
],
},
],
},
],
});
mockPantryRepo.findActiveByHousehold.mockResolvedValue([]);
const suggestions = await service.getSuggestions('hh1', 'user1');
expect(suggestions).toHaveLength(1);
// 4. Direct call to getUrgencyWeight default branch
const defaultWeight = (service as any).getUrgencyWeight('mystery-status');
expect(defaultWeight).toBe(0);
});
});
});