Added some tests
This commit is contained in:
parent
edbe082387
commit
cdd4cc1f3b
9 changed files with 288 additions and 29 deletions
16
db.py
16
db.py
|
|
@ -1,11 +1,10 @@
|
|||
import aiosqlite
|
||||
|
||||
async def connect() -> aiosqlite.Connection:
|
||||
return await aiosqlite.connect('./data/your_database.db')
|
||||
async def connect(path = './data/your_database.db') -> aiosqlite.Connection:
|
||||
return await aiosqlite.connect(path)
|
||||
|
||||
async def create():
|
||||
async def create(conn: aiosqlite.Connection):
|
||||
import products.db as product_db
|
||||
conn = await connect()
|
||||
await product_db.create(conn)
|
||||
|
||||
import ingredients.db as ingredient_db
|
||||
|
|
@ -20,9 +19,10 @@ async def create():
|
|||
import meals.db as meals_db
|
||||
await meals_db.create(conn)
|
||||
|
||||
await conn.commit()
|
||||
await conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import asyncio
|
||||
asyncio.run(create())
|
||||
async def initdb():
|
||||
conn = await connect()
|
||||
await create(conn)
|
||||
|
||||
asyncio.run(initdb)
|
||||
|
|
@ -35,6 +35,12 @@ async def create(conn):
|
|||
|
||||
|
||||
async def insert_ingredient(conn, ingredient: Ingredient):
|
||||
if not ingredient.product_id and ingredient.product:
|
||||
ingredient.product_id = ingredient.product.id
|
||||
|
||||
if not ingredient.product_id:
|
||||
raise ValueError('Product must be inserted before ingredient')
|
||||
|
||||
async with conn.execute('''
|
||||
INSERT INTO Ingredient (name, line, preparation, unit, quantity, product_id, recipe_id, meal_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
|
|
|
|||
10
main.py
10
main.py
|
|
@ -1,5 +1,5 @@
|
|||
import sqlite3
|
||||
import products, recipes, db, meals, persons, ingredients
|
||||
import products, recipes, db as db, meals, persons, ingredients
|
||||
import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -14,7 +14,7 @@ app = FastAPI()
|
|||
# Add CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["*", "http://localhost:8080", "https://localhost:8080"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"]
|
||||
|
|
@ -29,7 +29,7 @@ async def get_db():
|
|||
await sql_db.close()
|
||||
|
||||
async def cookie_person(user_id: Annotated[int, Cookie(alias='user_id')], conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
|
||||
return await persons.find_person_by_id(conn, user_id)
|
||||
return await persons.get_by_id(conn, user_id)
|
||||
|
||||
@app.get("/recipes/parse")
|
||||
async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
|
||||
|
|
@ -65,6 +65,8 @@ async def load_full_recipe(conn: sqlite3.Connection, id: int) -> recipes.Recipe:
|
|||
async for ingredient in ingredients.find_ingredients_by_recipe_id(conn, id):
|
||||
r.ingredients.append(ingredient)
|
||||
|
||||
r.created_by = await persons.get_by_id(conn, r.created_by_id)
|
||||
|
||||
return r
|
||||
|
||||
@app.get("/recipes/")
|
||||
|
|
@ -135,7 +137,7 @@ async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) ->
|
|||
|
||||
@app.post("/meals/")
|
||||
async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
||||
if not meal.chef:
|
||||
if not meal.chefs:
|
||||
return JSONResponse(status_code=400, content={'message': 'Meal must have at least one chef'})
|
||||
|
||||
if not meal.cleanup:
|
||||
|
|
|
|||
31
meals/db.py
31
meals/db.py
|
|
@ -1,16 +1,19 @@
|
|||
from typing import List, ClassVar
|
||||
from typing import List, ClassVar, Optional
|
||||
from pydantic import BaseModel
|
||||
from persons import Person
|
||||
from ingredients import Ingredient, insert_ingredient, find_ingredients_by_meal_id
|
||||
from products import Product
|
||||
from recipes import Recipe, row_to_recipe
|
||||
|
||||
from recipes import Recipe, row_to_recipe, load_recipe_ingredients
|
||||
|
||||
import persons
|
||||
from persons import Person
|
||||
|
||||
import datetime
|
||||
|
||||
class Meal(BaseModel):
|
||||
KEYS: ClassVar[List[str]] = ['id', 'date']
|
||||
KEYS: ClassVar[List[str]] = ['id', 'meal_date']
|
||||
id: int
|
||||
date: datetime.datetime
|
||||
meal_date: datetime.datetime
|
||||
|
||||
chefs: List[Person] = []
|
||||
cleanup: List[Person] = []
|
||||
consumers: List[Person] = []
|
||||
|
|
@ -21,7 +24,7 @@ async def create(conn):
|
|||
await conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS Meal (
|
||||
id INTEGER PRIMARY KEY,
|
||||
date TEXT UNIQUE
|
||||
meal_date TEXT
|
||||
);''')
|
||||
|
||||
await conn.execute('''
|
||||
|
|
@ -48,6 +51,9 @@ async def insert_meal_participant(conn, meal_id: int, person_id: int, role: str)
|
|||
''', (meal_id, person_id, role))
|
||||
|
||||
async def insert_meal_recipe(conn, meal_id: int, recipe_id: int):
|
||||
if not recipe_id:
|
||||
raise ValueError('Recipe must be inserted before meal')
|
||||
|
||||
await conn.execute('''
|
||||
INSERT INTO MealRecipe (meal_id, recipe_id)
|
||||
VALUES (?, ?)
|
||||
|
|
@ -55,9 +61,9 @@ async def insert_meal_recipe(conn, meal_id: int, recipe_id: int):
|
|||
|
||||
async def insert_meal(conn, meal: Meal):
|
||||
async with conn.execute('''
|
||||
INSERT INTO Meal (date)
|
||||
INSERT INTO Meal (meal_date)
|
||||
VALUES (?)
|
||||
''', (meal.date,)) as cursor:
|
||||
''', (meal.meal_date,)) as cursor:
|
||||
meal.id = cursor.lastrowid
|
||||
|
||||
for person in meal.chefs:
|
||||
|
|
@ -110,7 +116,7 @@ async def load_participants(conn, meal: Meal) -> None:
|
|||
WHERE meal_id = ?
|
||||
''', (meal.id,)) as cursor:
|
||||
async for row in cursor:
|
||||
person = await Person.find_person_by_id(conn, row[0])
|
||||
person = await persons.get_by_id(conn, row[0])
|
||||
if row[1] == 'chef':
|
||||
meal.chefs.append(person)
|
||||
elif row[1] == 'cleanup':
|
||||
|
|
@ -127,7 +133,10 @@ async def load_recipes(conn, meal: Meal) -> None:
|
|||
WHERE MealRecipe.meal_id = ?
|
||||
''', (meal.id,)) as cursor:
|
||||
async for row in cursor:
|
||||
meal.recipes.append(row_to_recipe(zip(Recipe.KEYS, row)))
|
||||
recipe = row_to_recipe(zip(Recipe.KEYS, row))
|
||||
await load_recipe_ingredients(conn, recipe)
|
||||
|
||||
meal.recipes.append(recipe)
|
||||
|
||||
async def load_extra_ingredients(conn, meal: Meal) -> None:
|
||||
async for ingredient in find_ingredients_by_meal_id(conn, meal.id):
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ async def create(conn):
|
|||
name TEXT UNIQUE
|
||||
);''')
|
||||
|
||||
await insert(conn, Person(id=0, name='Jacob'))
|
||||
await insert(conn, Person(id=0, name='Ellie'))
|
||||
await insert(conn, Person(id=0, name='Ryan'))
|
||||
await insert(conn, Person(id=0, name='Chris'))
|
||||
await insert(conn, Person(id=1, name='Jacob'))
|
||||
await insert(conn, Person(id=2, name='Ryan'))
|
||||
await insert(conn, Person(id=3, name='Ellie'))
|
||||
await insert(conn, Person(id=4, name='Chris'))
|
||||
|
||||
async def get_by_name(conn, name: str) -> Person:
|
||||
cursor = await conn.execute('''
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from recipes.db import Recipe, insert_recipe, find_recipe_by_id, get_all, find_recipes_by_name, row_to_recipe
|
||||
from recipes.db import Recipe, insert_recipe, find_recipe_by_id, get_all, find_recipes_by_name, row_to_recipe, load_recipe_ingredients
|
||||
from recipes.scraping import scrape_recipe_ldata as _scrape_recipe_ldata
|
||||
from ingredients import parse_ingredient_from_nlp as _parse_ingredient_from_nlp, match_existing_products as _match_existing_products
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,6 @@ async def get_all(conn) -> List[Recipe]:
|
|||
async for row in cursor:
|
||||
yield row_to_recipe(zip(Recipe.KEYS, row))
|
||||
|
||||
async def load_ingredients(conn, recipe: Recipe):
|
||||
async def load_recipe_ingredients(conn, recipe: Recipe) -> None:
|
||||
async for ingredient in find_ingredients_by_recipe_id(conn, recipe.id):
|
||||
recipe.ingredients.append(ingredient)
|
||||
94
test_data.py
Normal file
94
test_data.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
from persons import db as persons_db
|
||||
|
||||
class Persons:
|
||||
jacob = persons_db.Person(
|
||||
id=1,
|
||||
name='Jacob')
|
||||
|
||||
ryan = persons_db.Person(
|
||||
id=2,
|
||||
name='Ryan')
|
||||
|
||||
ellie = persons_db.Person(
|
||||
id=3,
|
||||
name='Ellie')
|
||||
|
||||
chris = persons_db.Person(
|
||||
id=4,
|
||||
name='Chris')
|
||||
|
||||
from products import db as products_db
|
||||
|
||||
class Products:
|
||||
broccoli = products_db.Product(
|
||||
id=0,
|
||||
product_id='69',
|
||||
name='Broccoli',
|
||||
link='https://en.wikipedia.org/wiki/Broccoli',
|
||||
tags=['vegetable'],
|
||||
img_small='https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Broccoli_and_cross_section_edit.jpg/800px-Broccoli_and_cross_section_edit.jpg',
|
||||
img_large='https://upload.wikimedia.org/wikipedia/commons/0/03/Broccoli_and_cross_section_edit.jpg',
|
||||
raw_data={},
|
||||
)
|
||||
|
||||
garlic_bread = products_db.Product(
|
||||
id=0,
|
||||
product_id='420',
|
||||
name='Garlic Bread',
|
||||
link='https://en.wikipedia.org/wiki/Garlic_bread',
|
||||
tags=['bread', 'garlic'],
|
||||
img_small='https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Garlic_bread.jpg/800px-Garlic_bread.jpg',
|
||||
img_large='https://upload.wikimedia.org/wikipedia/commons/4/4b/Garlic_bread.jpg',
|
||||
raw_data={},
|
||||
)
|
||||
|
||||
from ingredients import db as ingredients_db
|
||||
|
||||
class Ingredients:
|
||||
broccoli_chopped_1kg = ingredients_db.Ingredient(
|
||||
id=0,
|
||||
line='1kg Broccoli, Chopped',
|
||||
name='Broccoli',
|
||||
unit='1kg',
|
||||
quantity='1',
|
||||
preparation='Chopped',
|
||||
product=Products.broccoli,
|
||||
)
|
||||
|
||||
garlic_bread_1_loaf = ingredients_db.Ingredient(
|
||||
id=0,
|
||||
line='1 Loaf Garlic Bread',
|
||||
name='Garlic Bread',
|
||||
unit='Loaf',
|
||||
quantity='1',
|
||||
preparation='',
|
||||
product=Products.garlic_bread,
|
||||
)
|
||||
|
||||
from recipes import db as recipes_db
|
||||
|
||||
class Recipes:
|
||||
broccoli_soup = recipes_db.Recipe(
|
||||
id=0,
|
||||
name='Broccoli Soup',
|
||||
link='https://www.bbcgoodfood.com/recipes/broccoli-soup',
|
||||
image_urls=['https://www.bbcgoodfood.com/sites/default/files/styles/recipe/public/recipe/recipe-image/2018/10/broccoli-soup.jpg'],
|
||||
ingredients=[Ingredients.broccoli_chopped_1kg],
|
||||
created_by_id=Persons.jacob.id,
|
||||
)
|
||||
|
||||
from meals import db as meals_db
|
||||
from datetime import datetime
|
||||
|
||||
class Meals:
|
||||
broccoli_soup_for_jacob = meals_db.Meal(
|
||||
id=0,
|
||||
create_date=datetime(2021, 12, 25),
|
||||
created_by=Persons.jacob,
|
||||
meal_date=datetime(2021, 12, 25),
|
||||
chefs=[Persons.jacob],
|
||||
cleanup=[Persons.ryan],
|
||||
consumers=[Persons.ellie, Persons.chris],
|
||||
recipes=[Recipes.broccoli_soup],
|
||||
extra_ingredients=[Ingredients.garlic_bread_1_loaf],
|
||||
)
|
||||
148
tests.py
Normal file
148
tests.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import unittest
|
||||
import test_data
|
||||
|
||||
import pathlib
|
||||
from db import connect, create
|
||||
|
||||
import products.db as products_db
|
||||
import recipes.db as recipes_db
|
||||
|
||||
class TestProducts(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
self.conn = await connect('./testdb.db')
|
||||
await create(self.conn)
|
||||
return await super().asyncSetUp()
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
await self.conn.close()
|
||||
pathlib.Path('./testdb.db').unlink(missing_ok=True)
|
||||
return await super().asyncTearDown()
|
||||
|
||||
async def testCreateAndFind(self) -> None:
|
||||
product = test_data.Products.broccoli
|
||||
await products_db.insert_product(self.conn, product, {})
|
||||
self.assertIsNotNone(product)
|
||||
self.assertGreater(product.id, 0)
|
||||
|
||||
product_by_id = await products_db.find_product_by_id(self.conn, product.id)
|
||||
self.assertIsNotNone(product_by_id)
|
||||
self.assertEqual(product_by_id.id, product.id)
|
||||
self.assertEqual(product_by_id.name, product.name)
|
||||
self.assertEqual(product_by_id.link, product.link)
|
||||
self.assertEqual(product_by_id.img_large, product.img_large)
|
||||
self.assertEqual(product_by_id.img_small, product.img_small)
|
||||
|
||||
import main
|
||||
|
||||
class TestRecipe(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
pathlib.Path('./testdb.db').unlink(missing_ok=True)
|
||||
self.conn = await connect('./testdb.db')
|
||||
await create(self.conn)
|
||||
return await super().asyncSetUp()
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
await self.conn.close()
|
||||
pathlib.Path('./testdb.db').unlink(missing_ok=True)
|
||||
return await super().asyncTearDown()
|
||||
|
||||
async def testCreateAndFind(self) -> None:
|
||||
recipe = test_data.Recipes.broccoli_soup
|
||||
ingredient = recipe.ingredients[0]
|
||||
product = ingredient.product
|
||||
person = test_data.Persons.jacob
|
||||
|
||||
await products_db.insert_product(self.conn, product, {})
|
||||
|
||||
create_response = await main.create_recipe(recipe, self.conn, person)
|
||||
self.assertIsNotNone(create_response)
|
||||
self.assertIsInstance(create_response, recipes_db.Recipe, msg=create_response.body if hasattr(create_response, 'body') else create_response)
|
||||
|
||||
recipe_by_id = await main.get_recipe(recipe.id, self.conn)
|
||||
self.assertIsNotNone(recipe_by_id)
|
||||
self.assertIsInstance(recipe_by_id, recipes_db.Recipe, msg=recipe_by_id.body if hasattr(recipe_by_id, 'body') else recipe_by_id)
|
||||
|
||||
self.assertIsNotNone(recipe_by_id)
|
||||
self.assertEqual(recipe_by_id.id, recipe.id)
|
||||
self.assertEqual(recipe_by_id.name, recipe.name)
|
||||
self.assertEqual(recipe_by_id.link, recipe.link)
|
||||
self.assertEqual(recipe_by_id.image_urls, recipe.image_urls)
|
||||
self.assertEqual(len(recipe_by_id.ingredients), 1)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].id, ingredient.id)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].line, ingredient.line)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].name, ingredient.name)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].unit, ingredient.unit)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].quantity, ingredient.quantity)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].preparation, ingredient.preparation)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].product.id, product.id)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].product.name, product.name)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].product.link, product.link)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].product.img_large, product.img_large)
|
||||
self.assertEqual(recipe_by_id.ingredients[0].product.img_small, product.img_small)
|
||||
self.assertEqual(recipe_by_id.created_by.id, person.id)
|
||||
self.assertEqual(recipe_by_id.created_by.name, person.name)
|
||||
|
||||
import meals
|
||||
|
||||
class TestMeals(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
pathlib.Path('./testdb.db').unlink(missing_ok=True)
|
||||
self.conn = await connect('./testdb.db')
|
||||
await create(self.conn)
|
||||
return await super().asyncSetUp()
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
await self.conn.close()
|
||||
pathlib.Path('./testdb.db').unlink(missing_ok=True)
|
||||
return await super().asyncTearDown()
|
||||
|
||||
async def testCreateAndFind(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
|
||||
|
||||
create_response = await main.create_meal(meal, self.conn)
|
||||
self.assertIsNotNone(create_response)
|
||||
self.assertIsInstance(create_response, meals.Meal, msg=create_response.body if hasattr(create_response, 'body') else create_response)
|
||||
|
||||
meal_by_id = await main.get_meal(meal.id, self.conn)
|
||||
self.assertIsNotNone(meal_by_id)
|
||||
self.assertIsInstance(meal_by_id, meals.Meal, msg=meal_by_id.body if hasattr(meal_by_id, 'body') else meal_by_id)
|
||||
|
||||
self.assertIsNotNone(meal_by_id)
|
||||
self.assertEqual(meal_by_id.id, meal.id)
|
||||
self.assertEqual(meal_by_id.meal_date, meal.meal_date)
|
||||
self.assertEqual(len(meal_by_id.chefs), 1)
|
||||
self.assertEqual(meal_by_id.chefs[0].id, meal.chefs[0].id)
|
||||
self.assertEqual(meal_by_id.chefs[0].name, meal.chefs[0].name)
|
||||
self.assertEqual(len(meal_by_id.cleanup), 1)
|
||||
self.assertEqual(meal_by_id.cleanup[0].id, meal.cleanup[0].id)
|
||||
self.assertEqual(meal_by_id.cleanup[0].name, meal.cleanup[0].name)
|
||||
self.assertEqual(len(meal_by_id.consumers), 2)
|
||||
|
||||
expected = [p.id for p in meal.consumers]
|
||||
actual = [p.id for p in meal_by_id.consumers]
|
||||
self.assertEqual(sorted(expected), sorted(actual))
|
||||
|
||||
self.assertEqual(meal_by_id.recipes[0].id, recipe.id)
|
||||
self.assertEqual(meal_by_id.recipes[0].name, recipe.name)
|
||||
self.assertEqual(meal_by_id.recipes[0].link, recipe.link)
|
||||
self.assertEqual(meal_by_id.recipes[0].image_urls, recipe.image_urls)
|
||||
self.assertEqual(len(meal_by_id.recipes[0].ingredients), 1)
|
||||
self.assertEqual(meal_by_id.recipes[0].ingredients[0].id, recipe_ingredient.id)
|
||||
self.assertEqual(meal_by_id.recipes[0].ingredients[0].line, recipe_ingredient.line)
|
||||
self.assertEqual(meal_by_id.extra_ingredients[0].id, extra_ingredient.id)
|
||||
self.assertEqual(meal_by_id.extra_ingredients[0].line, extra_ingredient.line)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue