diff --git a/main.py b/main.py index a6427d8..dc83e32 100644 --- a/main.py +++ b/main.py @@ -152,8 +152,16 @@ async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> return meal -@app.post("/meals/") -async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: +def get_duplicates(items: List[meals.Person]) -> set[str]: + seen : set[int] = set() + duplicates : set[str] = set() + for item in items: + if item.id in seen: + duplicates.add(item.name) + seen.add(item.id) + return duplicates + +def validate_meal(meal : meals.Meal) -> JSONResponse | None: if not meal.chefs: return JSONResponse(status_code=400, content={'message': 'Meal must have at least one chef'}) @@ -165,6 +173,26 @@ async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_d if len(meal.recipes) == 0 and len(meal.extra_ingredients) == 0: return JSONResponse(status_code=400, content={'message': 'Meal must have at least one recipe or ingredient'}) + + duplicates = get_duplicates(meal.chefs) + if duplicates: + return JSONResponse(status_code=400, content={'message': f'Duplicate chef: {", ".join(duplicates)}'}) + + duplicates = get_duplicates(meal.cleanup) + if duplicates: + return JSONResponse(status_code=400, content={'message': f'Duplicate cleanup person: {", ".join(duplicates)}'}) + + duplicates = get_duplicates(meal.consumers) + if duplicates: + return JSONResponse(status_code=400, content={'message': f'Duplicate consumer: {", ".join(duplicates)}'}) + + return None + +@app.post("/meals/") +async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: + validation_response = validate_meal(meal) + if validation_response: + return validation_response await meals.insert_meal(conn, meal) await conn.commit() @@ -178,6 +206,10 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = existing = await meals.find_meal_by_id(conn, meal_id) if not existing: return JSONResponse(status_code=404, content={'message': 'Meal not found'}) + + validation_response = validate_meal(meal) + if validation_response: + return validation_response await meals.update_meal(conn, meal) await conn.commit() diff --git a/tests.py b/tests.py index 342b572..bdc87e0 100644 --- a/tests.py +++ b/tests.py @@ -1,7 +1,13 @@ import unittest -import test_data import datetime +import test_data +import importlib + +def reload_test_data(): + global test_data + test_data = importlib.reload(test_data) + import pathlib from db import connect, create @@ -20,6 +26,7 @@ class TestProducts(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): self.conn = await connect('./testdb.db') await create(self.conn) + reload_test_data() return await super().asyncSetUp() async def asyncTearDown(self) -> None: @@ -49,6 +56,8 @@ class TestRecipe(unittest.IsolatedAsyncioTestCase): self.conn = await connect('./testdb.db') await create(self.conn) await test_data.create_persons(self.conn) + reload_test_data() + return await super().asyncSetUp() async def asyncTearDown(self) -> None: @@ -113,6 +122,7 @@ class TestMeals(unittest.IsolatedAsyncioTestCase): self.conn = await connect('./testdb.db') await create(self.conn) await test_data.create_persons(self.conn) + reload_test_data() return await super().asyncSetUp() async def asyncTearDown(self) -> None: @@ -120,6 +130,48 @@ class TestMeals(unittest.IsolatedAsyncioTestCase): pathlib.Path('./testdb.db').unlink(missing_ok=True) return await super().asyncTearDown() + async def testMultiplePariticpants(self) -> None: + meal = test_data.Meals.broccoli_soup_for_jacob + recipe = meal.recipes[0] + recipe_ingredient = recipe.ingredients[0] + recipe_product = recipe_ingredient.product + extra_ingredient = meal.extra_ingredients[0] + extra_product = extra_ingredient.product + + person = test_data.Persons.jacob + + await products_db.insert_product(self.conn, recipe_product, {}) + await products_db.insert_product(self.conn, extra_product, {}) + + created_recipe = await main.create_recipe(recipe, self.conn, person) + recipe.id = created_recipe.id + + meal.chefs = [test_data.Persons.ryan, test_data.Persons.chris, test_data.Persons.ryan] + error_response = await main.create_meal(meal, self.conn) + self.assertIsNotNone(error_response) + self.assertEqual(error_response.status_code, 400) + self.assertEqual(error_response.body, b'{"message":"Duplicate chef: Ryan"}') + + + meal.chefs = [test_data.Persons.ryan, test_data.Persons.chris] + meal.consumers = [test_data.Persons.ryan, test_data.Persons.chris, test_data.Persons.ryan] + error_response = await main.create_meal(meal, self.conn) + self.assertIsNotNone(error_response) + self.assertEqual(error_response.status_code, 400) + self.assertEqual(error_response.body, b'{"message":"Duplicate consumer: Ryan"}') + + meal.consumers = [test_data.Persons.ryan, test_data.Persons.chris] + meal.cleanup = [test_data.Persons.ryan, test_data.Persons.chris, test_data.Persons.ryan] + error_response = await main.create_meal(meal, self.conn) + self.assertIsNotNone(error_response) + self.assertEqual(error_response.status_code, 400) + self.assertEqual(error_response.body, b'{"message":"Duplicate cleanup person: Ryan"}') + + meal.cleanup = [test_data.Persons.ryan, test_data.Persons.chris] + response = await main.create_meal(meal, self.conn) + self.assertIsNotNone(response) + self.assertIsInstance(response, meals.Meal, msg=response.body if hasattr(response, 'body') else response) + async def testCreateAndFind(self) -> None: meal = test_data.Meals.broccoli_soup_for_jacob recipe = meal.recipes[0]