import importlib import unittest from datetime import datetime import tests.test_data as test_data from db import connect, create import shopping from shopping.models import ShoppingList, ShoppingListItem, StoreEnum import ingredients.repository as ingredients_repo import ingredients import meals import recipes def reload_test_data(): global test_data test_data = importlib.reload(test_data) class TestShopping(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): self.conn = await connect(":memory:") await create(self.conn) await test_data.create_persons(self.conn) reload_test_data() async def asyncTearDown(self) -> None: await self.conn.close() def test_validate_request(self): ing = ingredients_repo.Ingredient( id=1, name="Broccoli", line="500g fresh broccoli", unit="g", quantity=500.0, preparation="chopped", ) ok_item = ShoppingListItem(ingredient_id=ing.id, person_id=1) shopping.validate_request(ok_item) bad_person = ShoppingListItem(ingredient_id=ing.id, person_id=-1) with self.assertRaises(ValueError): shopping.validate_request(bad_person) missing_both = ShoppingListItem(person_id=1) with self.assertRaises(ValueError): shopping.validate_request(missing_both) async def test_request_and_purchase_ingredient(self): ing = ingredients_repo.Ingredient( name="Broccoli", line="500g fresh broccoli", unit="g", quantity=500.0, preparation="chopped", ) await ingredients_repo.insert_ingredient(self.conn, ing) person = test_data.Persons.jacob req_item = await shopping.request(self.conn, person, ingredient=ing) self.assertIsNotNone(req_item.id) shop_item = ShoppingListItem(ingredient=ing, person_id=person.id) s_list = ShoppingList( store_name=StoreEnum.woolworths, purchased_by_id=person.id, items=[shop_item], ) await shopping.purchase(self.conn, s_list) self.assertIsNotNone(s_list.id) loaded = await shopping.load_shopping_list(self.conn, s_list.id) self.assertIsNotNone(loaded) self.assertEqual(loaded.id, s_list.id) async def test_to_lookups_and_is_requested(self): # Insert ingredient, recipe, meal ing = ingredients_repo.Ingredient( name="Carrot", line="1 carrot", unit="Items", quantity=1.0, preparation="", ) await ingredients_repo.insert_ingredient(self.conn, ing) recipe = recipes.Recipe( id=1, name="Test Recipe", link="http://example.com", serves=4, created_by_id=1, ) await recipes.insert_recipe(self.conn, recipe) meal = meals.Meal(id=1, suggested_date=datetime.now()) await meals.insert_meal(self.conn, meal) items = [ShoppingListItem(ingredient_id=ing.id, meal_id=meal.id, recipe_id=recipe.id)] meals_lookup, recipes_lookup, ingredients_lookup = await shopping.to_lookups( self.conn, items ) self.assertIn(meal.id, meals_lookup) self.assertIn(recipe.id, recipes_lookup) self.assertIn(ing.id, ingredients_lookup) person = test_data.Persons.jacob self.assertFalse(await shopping.is_requested(self.conn, meal)) await shopping.request(self.conn, person, meal=meal) self.assertTrue(await shopping.is_requested(self.conn, meal)) async def test_complete_meal_purchase_unrequests_and_marks_purchased(self): # Create a simple recipe with 2 ingredients recipe = recipes.Recipe( id=-1, name="Simple Recipe", link="http://example.com/simple", serves=2, created_by_id=test_data.Persons.jacob.id, ) await recipes.insert_recipe(self.conn, recipe) # Create ingredients for the recipe ingredient1 = ingredients.Ingredient( name="Bread", line="2 slices bread", unit="slices", quantity=2.0, preparation="", recipe_id=recipe.id, ) ingredient2 = ingredients.Ingredient( name="Butter", line="10g butter", unit="g", quantity=10.0, preparation="", recipe_id=recipe.id, ) await ingredients.insert_ingredient(self.conn, ingredient1) await ingredients.insert_ingredient(self.conn, ingredient2) # Load the recipe with its ingredients await recipes.load_recipe_ingredients(self.conn, recipe) # Create and insert a meal meal_recipe = meals.MealRecipe(meal_id=-1, recipe_id=recipe.id, servings=1.0, recipe=recipe) meal = meals.Meal( id=-1, suggested_date=datetime.now(), chefs=[test_data.Persons.jacob], cleanup=[test_data.Persons.ryan], consumers=[test_data.Persons.ellie], recipes=[meal_recipe], ) await meals.insert_meal(self.conn, meal) # Request the meal person = test_data.Persons.jacob await shopping.request(self.conn, person, meal=meal) self.assertTrue(await shopping.is_requested(self.conn, meal)) # Get initial outstanding requests ( outstanding_before, purchased_before, meal_requests_before, meals_lookup, recipes_lookup, ingredients_lookup, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_before), 2) self.assertEqual(len(meal_requests_before), 1) # Purchase all ingredients from the meal shopping_items = [ ShoppingListItem( ingredient_id=item.ingredient_id, person_id=person.id, meal_id=meal.id, recipe_id=item.recipe_id, ) for item in outstanding_before ] shopping_list = ShoppingList( store_name=StoreEnum.woolworths, purchased_by_id=person.id, items=shopping_items ) await shopping.purchase(self.conn, shopping_list) # Verify meal is no longer requested and is marked as purchased self.assertFalse(await shopping.is_requested(self.conn, meal)) found_meal_after = await meals.find_meal_by_id(self.conn, meal.id) self.assertIsNotNone(found_meal_after.purchase_date) # After complete purchase, there should be no outstanding/purchased items for that meal ( outstanding_after, purchased_after, meal_requests_after, _, _, _, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_after), 0) self.assertEqual(len(purchased_after), 0) self.assertEqual(len(meal_requests_after), 0) async def test_complete_meal_with_extra_ingredients_purchase(self): # Create a recipe with 1 ingredient recipe = recipes.Recipe( id=-1, name="Recipe with Extra", link="http://example.com/extra", serves=2, created_by_id=test_data.Persons.jacob.id, ) await recipes.insert_recipe(self.conn, recipe) # Recipe ingredient recipe_ingredient = ingredients.Ingredient( name="Main Ingredient", line="200g main ingredient", unit="g", quantity=200.0, preparation="", recipe_id=recipe.id, ) await ingredients.insert_ingredient(self.conn, recipe_ingredient) await recipes.load_recipe_ingredients(self.conn, recipe) # Extra ingredient (not part of recipe) extra_ingredient = ingredients.Ingredient( name="Side Dish", line="1 side dish", unit="item", quantity=1.0, preparation="" ) meal_recipe = meals.MealRecipe(meal_id=-1, recipe_id=recipe.id, servings=1.0, recipe=recipe) meal = meals.Meal( id=-1, suggested_date=datetime.now(), chefs=[test_data.Persons.jacob], cleanup=[test_data.Persons.ryan], consumers=[test_data.Persons.ellie], recipes=[meal_recipe], extra_ingredients=[extra_ingredient], ) await meals.insert_meal(self.conn, meal) person = test_data.Persons.jacob await shopping.request(self.conn, person, meal=meal) ( outstanding_before, _, _, _, _, _, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_before), 2) # recipe + extra shopping_items = [ ShoppingListItem( ingredient_id=item.ingredient_id, person_id=person.id, meal_id=meal.id, recipe_id=item.recipe_id, ) for item in outstanding_before ] shopping_list = ShoppingList( store_name=StoreEnum.woolworths, purchased_by_id=person.id, items=shopping_items ) await shopping.purchase(self.conn, shopping_list) self.assertFalse(await shopping.is_requested(self.conn, meal)) found_meal_after = await meals.find_meal_by_id(self.conn, meal.id) self.assertIsNotNone(found_meal_after.purchase_date) ( outstanding_after, purchased_after, meal_requests_after, _, _, _, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_after), 0) self.assertEqual(len(purchased_after), 0) self.assertEqual(len(meal_requests_after), 0) async def test_individual_ingredient_purchase_without_meal(self): # Create individual ingredients ingredient1 = ingredients.Ingredient( name="Milk", line="1L milk", unit="L", quantity=1.0, preparation="" ) ingredient2 = ingredients.Ingredient( name="Eggs", line="12 eggs", unit="dozen", quantity=1.0, preparation="" ) await ingredients.insert_ingredient(self.conn, ingredient1) await ingredients.insert_ingredient(self.conn, ingredient2) # Request individual ingredients person = test_data.Persons.jacob await shopping.request(self.conn, person, ingredient=ingredient1) await shopping.request(self.conn, person, ingredient=ingredient2) ( outstanding_before, purchased_before, meal_requests_before, meals_lookup, recipes_lookup, ingredients_lookup, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_before), 2) self.assertEqual(len(purchased_before), 0) self.assertEqual(len(meal_requests_before), 0) # Purchase only Milk milk_item = next( ShoppingListItem(ingredient_id=i.ingredient_id, person_id=person.id) for i in outstanding_before if ingredients_lookup.get(i.ingredient_id).name == "Milk" ) shopping_list = ShoppingList( store_name=StoreEnum.woolworths, purchased_by_id=person.id, items=[milk_item] ) await shopping.purchase(self.conn, shopping_list) ( outstanding_after, purchased_after, meal_requests_after, meals_lookup_after, recipes_lookup_after, ingredients_lookup_after, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_after), 1) self.assertEqual(len(purchased_after), 0) self.assertEqual(len(meal_requests_after), 0) self.assertEqual(ingredients_lookup_after[outstanding_after[0].ingredient_id].name, "Eggs") self.assertIsNone(outstanding_after[0].meal_id) # Purchase remaining Eggs eggs_item = ShoppingListItem( ingredient_id=outstanding_after[0].ingredient_id, person_id=person.id ) shopping_list2 = ShoppingList( store_name=StoreEnum.coles, purchased_by_id=person.id, items=[eggs_item] ) await shopping.purchase(self.conn, shopping_list2) ( outstanding_final, purchased_final, meal_requests_final, _, _, _, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_final), 0) self.assertEqual(len(purchased_final), 0) self.assertEqual(len(meal_requests_final), 0) async def test_mixed_meal_and_individual_requests(self): # Create recipe and meal recipe = recipes.Recipe( id=-1, name="Simple Pasta", link="http://example.com/pasta", serves=2, created_by_id=test_data.Persons.jacob.id, ) await recipes.insert_recipe(self.conn, recipe) pasta_ingredient = ingredients.Ingredient( name="Pasta", line="200g pasta", unit="g", quantity=200.0, preparation="", recipe_id=recipe.id, ) await ingredients.insert_ingredient(self.conn, pasta_ingredient) await recipes.load_recipe_ingredients(self.conn, recipe) meal_recipe = meals.MealRecipe(meal_id=-1, recipe_id=recipe.id, servings=1.0, recipe=recipe) meal = meals.Meal( id=-1, suggested_date=datetime.now(), chefs=[test_data.Persons.jacob], cleanup=[test_data.Persons.ryan], consumers=[test_data.Persons.ellie], recipes=[meal_recipe], ) await meals.insert_meal(self.conn, meal) snack_ingredient = ingredients.Ingredient( name="Chips", line="1 bag chips", unit="bag", quantity=1.0, preparation="" ) await ingredients.insert_ingredient(self.conn, snack_ingredient) person = test_data.Persons.jacob await shopping.request(self.conn, person, meal=meal) await shopping.request(self.conn, person, ingredient=snack_ingredient) ( outstanding_before, purchased_before, meal_requests_before, meals_lookup, recipes_lookup, ingredients_lookup, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_before), 2) self.assertEqual(len(purchased_before), 0) self.assertEqual(len(meal_requests_before), 1) # Identify items pasta_item = next( i for i in outstanding_before if ingredients_lookup.get(i.ingredient_id).name == "Pasta" ) chips_item = next( i for i in outstanding_before if ingredients_lookup.get(i.ingredient_id).name == "Chips" ) self.assertIsNotNone(pasta_item) self.assertIsNotNone(chips_item) self.assertEqual(pasta_item.meal_id, meal.id) self.assertIsNone(chips_item.meal_id) # Purchase only Chips chips_shopping_item = ShoppingListItem( ingredient_id=chips_item.ingredient_id, person_id=person.id ) shopping_list = ShoppingList( store_name=StoreEnum.woolworths, purchased_by_id=person.id, items=[chips_shopping_item] ) await shopping.purchase(self.conn, shopping_list) ( outstanding_after, purchased_after, meal_requests_after, meals_lookup_after, recipes_lookup_after, ingredients_lookup_after, ) = await shopping.get_outstanding_requests(self.conn) self.assertEqual(len(outstanding_after), 1) self.assertEqual(len(purchased_after), 0) self.assertEqual(len(meal_requests_after), 1) self.assertEqual(ingredients_lookup_after[outstanding_after[0].ingredient_id].name, "Pasta") self.assertEqual(outstanding_after[0].meal_id, meal.id)