Added some tests

This commit is contained in:
jableader 2024-04-25 14:57:39 +10:00
parent edbe082387
commit cdd4cc1f3b
9 changed files with 288 additions and 29 deletions

16
db.py
View file

@ -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)

View file

@ -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
View file

@ -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:

View file

@ -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):

View file

@ -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('''

View file

@ -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

View file

@ -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
View 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
View 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()