Vibe coded some tests -_-

This commit is contained in:
jableader 2025-07-30 18:55:51 +10:00
parent 54f0dcbbb9
commit 2a66ff39be
2 changed files with 594 additions and 1 deletions

View file

@ -53,7 +53,7 @@ async def find_ingredient_by_id(conn, ingredient_id: int) -> Optional[Ingredient
async with conn.execute(f''' async with conn.execute(f'''
SELECT {','.join(ingredient_keys + product_keys)} FROM Ingredient SELECT {','.join(ingredient_keys + product_keys)} FROM Ingredient
LEFT JOIN Product ON Ingredient.product_id = Product.id LEFT JOIN Product ON Ingredient.product_id = Product.id
WHERE id = ? WHERE Ingredient.id = ?
''', (ingredient_id,)) as cursor: ''', (ingredient_id,)) as cursor:
async for row in cursor: async for row in cursor:
product_keys = {k:v for k,v in zip(Product.KEYS, row[len(Ingredient.KEYS):])} product_keys = {k:v for k,v in zip(Product.KEYS, row[len(Ingredient.KEYS):])}

593
tests/test_ingredients.py Normal file
View file

@ -0,0 +1,593 @@
import unittest
import asyncio
import tests.test_data as test_data
import importlib
def reload_test_data():
global test_data
test_data = importlib.reload(test_data)
from db import connect, create
import ingredients
import ingredients.db as ingredients_db
import products.db as products_db
import units
class TestIngredient(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()
return await super().asyncSetUp()
async def asyncTearDown(self) -> None:
await self.conn.close()
return await super().asyncTearDown()
async def test_ingredient_creation(self):
"""Test basic ingredient creation"""
ingredient = ingredients_db.Ingredient(
name="Broccoli",
line="500g fresh broccoli",
unit="g",
quantity=500.0,
preparation="chopped"
)
self.assertEqual(ingredient.name, "Broccoli")
self.assertEqual(ingredient.line, "500g fresh broccoli")
self.assertEqual(ingredient.unit, "g")
self.assertEqual(ingredient.quantity, 500.0)
self.assertEqual(ingredient.preparation, "chopped")
async def test_insert_ingredient(self):
"""Test inserting an ingredient into the database"""
ingredient = ingredients_db.Ingredient(
name="Garlic",
line="2 cloves garlic",
unit="Items",
quantity=2.0,
preparation="minced"
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
self.assertGreater(ingredient.id, 0)
# Verify it was inserted correctly
found_ingredient = await ingredients_db.find_ingredient_by_id(self.conn, ingredient.id)
self.assertIsNotNone(found_ingredient)
self.assertEqual(found_ingredient.name, "Garlic")
self.assertEqual(found_ingredient.quantity, 2.0)
async def test_insert_ingredient_with_product(self):
"""Test inserting an ingredient with an associated product"""
# First create and insert a product
product = test_data.Products.broccoli
await products_db.insert_product(self.conn, product, {})
ingredient = ingredients_db.Ingredient(
name="Fresh Broccoli",
line="1 piece fresh broccoli",
unit="Items",
quantity=1.0,
preparation="",
product_id=product.id,
product=product
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
# Verify the ingredient was inserted with the product reference
found_ingredient = await ingredients_db.find_ingredient_by_id(self.conn, ingredient.id)
self.assertIsNotNone(found_ingredient)
self.assertEqual(found_ingredient.product_id, product.id)
self.assertIsNotNone(found_ingredient.product)
self.assertEqual(found_ingredient.product.name, product.name)
async def test_find_ingredient_by_id_not_found(self):
"""Test finding a non-existent ingredient returns None"""
result = await ingredients_db.find_ingredient_by_id(self.conn, 999)
self.assertIsNone(result)
async def test_find_ingredients_by_recipe_id(self):
"""Test finding ingredients by recipe ID"""
# Create ingredients with the same recipe_id
recipe_id = 1
ingredient1 = ingredients_db.Ingredient(
name="Flour",
line="2 cups flour",
unit="cups",
quantity=2.0,
preparation="",
recipe_id=recipe_id
)
ingredient2 = ingredients_db.Ingredient(
name="Sugar",
line="1 cup sugar",
unit="cups",
quantity=1.0,
preparation="",
recipe_id=recipe_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient1)
await ingredients_db.insert_ingredient(self.conn, ingredient2)
# Find ingredients by recipe ID
ingredients_list = []
async for ingredient in ingredients_db.find_ingredients_by_recipe_id(self.conn, recipe_id):
ingredients_list.append(ingredient)
self.assertEqual(len(ingredients_list), 2)
names = [ing.name for ing in ingredients_list]
self.assertIn("Flour", names)
self.assertIn("Sugar", names)
async def test_find_ingredients_by_meal_id(self):
"""Test finding ingredients by meal ID"""
# Create ingredients with the same meal_id
meal_id = 1
ingredient1 = ingredients_db.Ingredient(
name="Chicken",
line="1 lb chicken breast",
unit="lb",
quantity=1.0,
preparation="diced",
meal_id=meal_id
)
ingredient2 = ingredients_db.Ingredient(
name="Rice",
line="2 cups rice",
unit="cups",
quantity=2.0,
preparation="",
meal_id=meal_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient1)
await ingredients_db.insert_ingredient(self.conn, ingredient2)
# Find ingredients by meal ID
ingredients_list = []
async for ingredient in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ingredient)
self.assertEqual(len(ingredients_list), 2)
names = [ing.name for ing in ingredients_list]
self.assertIn("Chicken", names)
self.assertIn("Rice", names)
async def test_delete_ingredients_by_meal_id(self):
"""Test deleting ingredients by meal ID"""
meal_id = 1
ingredient = ingredients_db.Ingredient(
name="Tomato",
line="2 tomatoes",
unit="Items",
quantity=2.0,
preparation="sliced",
meal_id=meal_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
# Verify ingredient exists
ingredients_list = []
async for ing in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ing)
self.assertEqual(len(ingredients_list), 1)
# Delete ingredients by meal ID
await ingredients_db.delete_ingredients_by_meal_id(self.conn, meal_id)
# Verify ingredients are deleted
ingredients_list = []
async for ing in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ing)
self.assertEqual(len(ingredients_list), 0)
import unittest
import asyncio
import tests.test_data as test_data
import importlib
def reload_test_data():
global test_data
test_data = importlib.reload(test_data)
from db import connect, create
import ingredients
import ingredients.db as ingredients_db
import products.db as products_db
import units
class TestIngredient(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()
return await super().asyncSetUp()
async def asyncTearDown(self) -> None:
await self.conn.close()
return await super().asyncTearDown()
async def test_ingredient_creation(self):
"""Test basic ingredient creation"""
ingredient = ingredients_db.Ingredient(
name="Broccoli",
line="500g fresh broccoli",
unit="g",
quantity=500.0,
preparation="chopped"
)
self.assertEqual(ingredient.name, "Broccoli")
self.assertEqual(ingredient.line, "500g fresh broccoli")
self.assertEqual(ingredient.unit, "g")
self.assertEqual(ingredient.quantity, 500.0)
self.assertEqual(ingredient.preparation, "chopped")
async def test_insert_ingredient(self):
"""Test inserting an ingredient into the database"""
ingredient = ingredients_db.Ingredient(
name="Garlic",
line="2 cloves garlic",
unit="Items",
quantity=2.0,
preparation="minced"
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
self.assertGreater(ingredient.id, 0)
# Verify it was inserted correctly
found_ingredient = await ingredients_db.find_ingredient_by_id(self.conn, ingredient.id)
self.assertIsNotNone(found_ingredient)
self.assertEqual(found_ingredient.name, "Garlic")
self.assertEqual(found_ingredient.quantity, 2.0)
async def test_insert_ingredient_with_product(self):
"""Test inserting an ingredient with an associated product"""
# First create and insert a product
product = test_data.Products.broccoli
await products_db.insert_product(self.conn, product, {})
ingredient = ingredients_db.Ingredient(
name="Fresh Broccoli",
line="1 piece fresh broccoli",
unit="Items",
quantity=1.0,
preparation="",
product_id=product.id,
product=product
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
# Verify the ingredient was inserted with the product reference
found_ingredient = await ingredients_db.find_ingredient_by_id(self.conn, ingredient.id)
self.assertIsNotNone(found_ingredient)
self.assertEqual(found_ingredient.product_id, product.id)
self.assertIsNotNone(found_ingredient.product)
self.assertEqual(found_ingredient.product.name, product.name)
async def test_find_ingredient_by_id_not_found(self):
"""Test finding a non-existent ingredient returns None"""
result = await ingredients_db.find_ingredient_by_id(self.conn, 999)
self.assertIsNone(result)
async def test_find_ingredients_by_recipe_id(self):
"""Test finding ingredients by recipe ID"""
# Create ingredients with the same recipe_id
recipe_id = 1
ingredient1 = ingredients_db.Ingredient(
name="Flour",
line="2 cups flour",
unit="cups",
quantity=2.0,
preparation="",
recipe_id=recipe_id
)
ingredient2 = ingredients_db.Ingredient(
name="Sugar",
line="1 cup sugar",
unit="cups",
quantity=1.0,
preparation="",
recipe_id=recipe_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient1)
await ingredients_db.insert_ingredient(self.conn, ingredient2)
# Find ingredients by recipe ID
ingredients_list = []
async for ingredient in ingredients_db.find_ingredients_by_recipe_id(self.conn, recipe_id):
ingredients_list.append(ingredient)
self.assertEqual(len(ingredients_list), 2)
names = [ing.name for ing in ingredients_list]
self.assertIn("Flour", names)
self.assertIn("Sugar", names)
async def test_find_ingredients_by_meal_id(self):
"""Test finding ingredients by meal ID"""
# Create ingredients with the same meal_id
meal_id = 1
ingredient1 = ingredients_db.Ingredient(
name="Chicken",
line="1 lb chicken breast",
unit="lb",
quantity=1.0,
preparation="diced",
meal_id=meal_id
)
ingredient2 = ingredients_db.Ingredient(
name="Rice",
line="2 cups rice",
unit="cups",
quantity=2.0,
preparation="",
meal_id=meal_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient1)
await ingredients_db.insert_ingredient(self.conn, ingredient2)
# Find ingredients by meal ID
ingredients_list = []
async for ingredient in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ingredient)
self.assertEqual(len(ingredients_list), 2)
names = [ing.name for ing in ingredients_list]
self.assertIn("Chicken", names)
self.assertIn("Rice", names)
async def test_delete_ingredients_by_meal_id(self):
"""Test deleting ingredients by meal ID"""
meal_id = 1
ingredient = ingredients_db.Ingredient(
name="Tomato",
line="2 tomatoes",
unit="Items",
quantity=2.0,
preparation="sliced",
meal_id=meal_id
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
# Verify ingredient exists
ingredients_list = []
async for ing in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ing)
self.assertEqual(len(ingredients_list), 1)
# Delete ingredients by meal ID
await ingredients_db.delete_ingredients_by_meal_id(self.conn, meal_id)
# Verify ingredients are deleted
ingredients_list = []
async for ing in ingredients_db.find_ingredients_by_meal_id(self.conn, meal_id):
ingredients_list.append(ing)
self.assertEqual(len(ingredients_list), 0)
async def test_ingredient_with_negative_product_id(self):
"""Test that negative product_id is converted to None during insertion"""
ingredient = ingredients_db.Ingredient(
name="Test Ingredient",
line="1 test ingredient",
unit="Items",
quantity=1.0,
preparation="",
product_id=-1
)
await ingredients_db.insert_ingredient(self.conn, ingredient)
found_ingredient = await ingredients_db.find_ingredient_by_id(self.conn, ingredient.id)
self.assertIsNone(found_ingredient.product_id)
class TestIngredientParsing(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()
return await super().asyncSetUp()
async def asyncTearDown(self) -> None:
await self.conn.close()
return await super().asyncTearDown()
async def test_parse_ingredient_from_link_invalid_format(self):
"""Test parsing ingredient from invalid link format returns None"""
invalid_links = [
"invalid link format",
"just a url https://example.com",
"no quantity https://example.com",
"",
"abc https://example.com"
]
for invalid_link in invalid_links:
result = await ingredients.parse_ingredient_from_link(self.conn, invalid_link)
self.assertIsNone(result, f"Should return None for: {invalid_link}")
async def test_parse_ingredient_from_link_valid_format_no_quantity(self):
"""Test parsing ingredient from valid link format without explicit quantity"""
link = "https://www.woolworths.com.au/shop/productdetails/134681/fresh-broccoli"
result = await ingredients.parse_ingredient_from_link(self.conn, link)
# The scraper actually works for this URL, so we should get a result
self.assertIsNotNone(result)
self.assertEqual(result.quantity, 1.0) # Default quantity when none specified
self.assertEqual(result.unit, units.ITEMS.name)
self.assertIsInstance(result, ingredients_db.Ingredient)
async def test_parse_ingredient_from_link_regex_parsing(self):
"""Test that the regex correctly parses quantity and URL from valid links"""
import re
# Test the regex pattern used in parse_ingredient_from_link
test_cases = [
("2 https://example.com", "2", "https://example.com"),
("10 https://www.woolworths.com.au/product", "10", "https://www.woolworths.com.au/product"),
("https://example.com", None, "https://example.com"),
("1 https://test.com", "1", "https://test.com")
]
for link, expected_qty, expected_url in test_cases:
match = re.match(r'^(\d+)?\s*(http.*)$', link)
if match:
quantity = int(match.group(1)) if match.group(1) else 1
url = match.group(2)
if expected_qty:
self.assertEqual(quantity, int(expected_qty))
else:
self.assertEqual(quantity, 1) # Default quantity
self.assertEqual(url, expected_url)
async def test_parse_ingredient_from_nlp_simple_cases(self):
"""Test parsing simple ingredient cases that don't require external dependencies"""
# Test the basic structure without relying on ingredient_parser
# Since ingredient_parser is an external dependency, we'll test what we can
# We can test that the function exists and handles basic error cases
try:
result = ingredients.parse_ingredient_from_nlp("2 cups flour")
# The function may fail due to missing ingredient_parser, but it should not crash
# If it works, result should be an Ingredient object
if result is not None:
self.assertIsInstance(result, ingredients_db.Ingredient)
except ImportError:
# If ingredient_parser is not available, that's expected
self.skipTest("ingredient_parser not available")
except Exception as e:
# Other exceptions should not occur in normal operation
self.fail(f"Unexpected exception: {e}")
class TestIngredientMatching(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()
return await super().asyncSetUp()
async def asyncTearDown(self) -> None:
await self.conn.close()
return await super().asyncTearDown()
async def test_match_existing_products_with_real_data(self):
"""Test matching ingredients to existing products using real operations"""
# Setup: Create and insert a product with tags
product = test_data.Products.broccoli
await products_db.insert_product(self.conn, product, {})
await products_db.add_tag(self.conn, product, "broccoli")
# Create ingredients without products
ingredient1 = ingredients_db.Ingredient(
name="broccoli",
line="1 piece broccoli",
unit="Items",
quantity=1.0,
preparation=""
)
ingredient2 = ingredients_db.Ingredient(
name="unknown vegetable",
line="1 piece unknown vegetable",
unit="Items",
quantity=1.0,
preparation=""
)
ingredients_list = [ingredient1, ingredient2]
result = await ingredients.match_existing_products(self.conn, ingredients_list)
# Check that first ingredient got matched
self.assertEqual(result[0].product_id, product.id)
self.assertIsNotNone(result[0].product)
self.assertEqual(result[0].product.name, product.name)
# Check that second ingredient remained unmatched
self.assertIsNone(result[1].product)
async def test_match_existing_products_already_has_product(self):
"""Test that ingredients with existing products are not re-matched"""
product = test_data.Products.broccoli
ingredient = ingredients_db.Ingredient(
name="broccoli",
line="1 piece broccoli",
unit="Items",
quantity=1.0,
preparation="",
product=product,
product_id=product.id
)
ingredients_list = [ingredient]
result = await ingredients.match_existing_products(self.conn, ingredients_list)
# Should remain unchanged
self.assertEqual(result[0].product_id, product.id)
self.assertEqual(result[0].product, product)
async def test_match_existing_products_empty_list(self):
"""Test matching empty ingredients list"""
result = await ingredients.match_existing_products(self.conn, [])
self.assertEqual(result, [])
async def test_ingredient_keys_constant(self):
"""Test that the KEYS constant contains expected fields"""
expected_keys = ['id', 'name', 'line', 'preparation', 'unit', 'quantity', 'product_id', 'recipe_id', 'meal_id']
self.assertEqual(ingredients_db.Ingredient.KEYS, expected_keys)
async def test_ingredient_default_values(self):
"""Test ingredient default values"""
ingredient = ingredients_db.Ingredient(
name="Test",
line="Test line",
unit="Items",
quantity=1.0,
preparation=""
)
self.assertEqual(ingredient.id, -1)
self.assertIsNone(ingredient.product_id)
self.assertIsNone(ingredient.recipe_id)
self.assertIsNone(ingredient.meal_id)
self.assertIsNone(ingredient.product)
if __name__ == '__main__':
unittest.main()