Add Recipes
This commit is contained in:
@@ -17,7 +17,7 @@ APP_TOKEN_VALIDITY = 7200
|
||||
|
||||
# Development Database Settings (overridden by PyTest app_config Fixture)
|
||||
DB_DRIVER = 'sqlite'
|
||||
DB_FILE = 'sigl-test.db'
|
||||
DB_FILE = 'sigl-test-next.db'
|
||||
|
||||
# Mail Configuration
|
||||
MAIL_ENABLED = True
|
||||
|
||||
47
migrations/versions/3d0cab7d7747_.py
Normal file
47
migrations/versions/3d0cab7d7747_.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 3d0cab7d7747
|
||||
Revises: c28b3a6cdc3a
|
||||
Create Date: 2023-02-25 15:37:19.626908
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3d0cab7d7747'
|
||||
down_revision = 'c28b3a6cdc3a'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('recipes',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('recipe_entries',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('recipe_id', sa.Integer(), nullable=False),
|
||||
sa.Column('product_id', sa.Integer(), nullable=False),
|
||||
sa.Column('quantity', sa.String(length=128), nullable=True),
|
||||
sa.Column('notes', sa.String(), nullable=True),
|
||||
sa.Column('createdAt', sa.DateTime(), nullable=True),
|
||||
sa.Column('modifiedAt', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
|
||||
sa.ForeignKeyConstraint(['recipe_id'], ['recipes.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('recipe_entries')
|
||||
op.drop_table('recipes')
|
||||
# ### end Alembic commands ###
|
||||
@@ -6,9 +6,17 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
|
||||
from sigl.domain.models import Product, ProductLocation
|
||||
from sigl.domain.models.list import ListEntry, ShoppingList
|
||||
from sigl.domain.models.recipe import Recipe, RecipeEntry
|
||||
|
||||
from .globals import db
|
||||
from .tables import list_entries, lists, product_locations, products
|
||||
from .tables import (
|
||||
list_entries,
|
||||
lists,
|
||||
product_locations,
|
||||
products,
|
||||
recipe_entries,
|
||||
recipes,
|
||||
)
|
||||
|
||||
__all__ = ('init_orm', )
|
||||
|
||||
@@ -27,6 +35,18 @@ def init_orm():
|
||||
)
|
||||
})
|
||||
|
||||
# Recipe Entries
|
||||
db.mapper(RecipeEntry, recipe_entries, properties={
|
||||
'product': db.relationship(
|
||||
Product,
|
||||
back_populates='recipes'
|
||||
),
|
||||
'recipe': db.relationship(
|
||||
Recipe,
|
||||
back_populates='entries',
|
||||
)
|
||||
})
|
||||
|
||||
# Products
|
||||
db.mapper(Product, products, properties={
|
||||
'entries': db.relationship(
|
||||
@@ -34,6 +54,11 @@ def init_orm():
|
||||
back_populates='product',
|
||||
cascade='all, delete-orphan',
|
||||
),
|
||||
'recipes': db.relationship(
|
||||
RecipeEntry,
|
||||
back_populates='product',
|
||||
cascade='all, delete-orphan',
|
||||
),
|
||||
'locations': db.relationship(
|
||||
ProductLocation,
|
||||
back_populates='product',
|
||||
@@ -57,3 +82,12 @@ def init_orm():
|
||||
cascade='all, delete-orphan',
|
||||
)
|
||||
})
|
||||
|
||||
# Recipes
|
||||
db.mapper(Recipe, recipes, properties={
|
||||
'entries': db.relationship(
|
||||
RecipeEntry,
|
||||
back_populates='recipe',
|
||||
cascade='all, delete-orphan',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -94,3 +94,39 @@ product_locations = db.Table(
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Recipe Table
|
||||
recipes = db.Table(
|
||||
'recipes',
|
||||
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# List Attributes
|
||||
db.Column('name', db.String(128), nullable=False),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Recipe Entry Table
|
||||
recipe_entries = db.Table(
|
||||
'recipe_entries',
|
||||
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# Shopping List and Product Link
|
||||
db.Column('recipe_id', db.ForeignKey('recipes.id'), nullable=False),
|
||||
db.Column('product_id', db.ForeignKey('products.id'), nullable=False),
|
||||
|
||||
# Entry Attributes
|
||||
db.Column('quantity', db.String(128), default=None),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('createdAt', db.DateTime(), default=None),
|
||||
db.Column('modifiedAt', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
@@ -6,10 +6,13 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
|
||||
from .list import ListEntry, ShoppingList
|
||||
from .product import Product, ProductLocation
|
||||
from .recipe import Recipe, RecipeEntry
|
||||
|
||||
__all__ = (
|
||||
'ListEntry',
|
||||
'Product',
|
||||
'ProductLocation',
|
||||
'Recipe',
|
||||
'RecipeEntry',
|
||||
'ShoppingList',
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ from .mixins import NotesMixin, TimestampMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .list import ListEntry
|
||||
from .recipe import RecipeEntry
|
||||
|
||||
__all__ = ('Product', 'ProductLocation')
|
||||
|
||||
@@ -31,6 +32,7 @@ class Product(NotesMixin, TimestampMixin):
|
||||
|
||||
# Relationship Fields
|
||||
entries: List['ListEntry'] = field(default_factory=list)
|
||||
recipes: List['RecipeEntry'] = field(default_factory=list)
|
||||
locations: List['ProductLocation'] = field(default_factory=list)
|
||||
|
||||
|
||||
|
||||
42
sigl/domain/models/recipe.py
Normal file
42
sigl/domain/models/recipe.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Sigl Recipe Domain Model.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2023 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from .mixins import NotesMixin, TimestampMixin
|
||||
from .product import Product
|
||||
|
||||
__all__ = ('Recipe', 'RecipeEntry')
|
||||
|
||||
|
||||
@dataclass
|
||||
class RecipeEntry(NotesMixin, TimestampMixin):
|
||||
"""Information about a Product in a Recipe.
|
||||
|
||||
This class contains information about a Product that is in a recipe
|
||||
list, including the quantity to be purchased and notes about the entry.
|
||||
"""
|
||||
id: int = None
|
||||
quantity: str = None
|
||||
|
||||
# Relationship Fields
|
||||
product: Product = None
|
||||
recipe: 'Recipe' = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Recipe(NotesMixin, TimestampMixin):
|
||||
"""Top-Level Recipe.
|
||||
|
||||
Contains a collection of `RecipeEntry` items which are intended to be
|
||||
added to shopping lists as a group.
|
||||
"""
|
||||
id: int = None
|
||||
name: str = None
|
||||
|
||||
# Relationship Fields
|
||||
entries: List[RecipeEntry] = field(default_factory=list)
|
||||
@@ -11,7 +11,14 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from sigl.exc import DomainError, NotFoundError
|
||||
|
||||
from .models import ListEntry, Product, ProductLocation, ShoppingList
|
||||
from .models import (
|
||||
ListEntry,
|
||||
Product,
|
||||
ProductLocation,
|
||||
Recipe,
|
||||
RecipeEntry,
|
||||
ShoppingList,
|
||||
)
|
||||
|
||||
|
||||
def list_addItem(
|
||||
@@ -20,7 +27,7 @@ def list_addItem(
|
||||
*,
|
||||
productId: Optional[int] = None,
|
||||
productName: Optional[str] = None,
|
||||
productCategory: Optional[str] = None,
|
||||
productCategory: Optional[str] = '',
|
||||
quantity: Optional[str] = None,
|
||||
remember: Optional[bool] = None,
|
||||
notes: Optional[str] = None,
|
||||
@@ -71,6 +78,51 @@ def list_addItem(
|
||||
return entry
|
||||
|
||||
|
||||
def list_addRecipe(session: Session, listId: int, recipeId: int) -> List[ListEntry]:
|
||||
"""Add a Recipe to a Shopping List.
|
||||
|
||||
This creates new `ListEntry` items for each `RecipeEntry` within the
|
||||
`Recipe` object. Note that any Products that are referenced by the Recipe
|
||||
and that are already in the Shopping List will have the quantity updated
|
||||
to include the Recipe quantity.
|
||||
"""
|
||||
sList = list_by_id(session, listId)
|
||||
if not sList:
|
||||
raise NotFoundError(f'List {listId} does not exist')
|
||||
|
||||
recipe = recipe_by_id(session, recipeId)
|
||||
if not recipe:
|
||||
raise NotFoundError(f'Recipe {recipeId} does not exist')
|
||||
|
||||
lEntries = list()
|
||||
for rEntry in recipe.entries:
|
||||
lEntry = list_entry_by_productId(session, listId, rEntry.product.id)
|
||||
if lEntry:
|
||||
if lEntry.quantity and rEntry.quantity:
|
||||
lEntry.quantity = f'{lEntry.quantity}, {rEntry.quantity} ({recipe.name})'
|
||||
elif rEntry.quantity:
|
||||
lEntry.quantity = rEntry.quantity
|
||||
|
||||
if lEntry.notes and rEntry.notes:
|
||||
lEntry.notes = f'{lEntry.notes}\n{rEntry.notes}'
|
||||
elif rEntry.notes:
|
||||
lEntry.notes = rEntry.notes
|
||||
|
||||
else:
|
||||
lEntry = ListEntry(
|
||||
shoppingList=sList,
|
||||
product=rEntry.product,
|
||||
quantity=rEntry.quantity,
|
||||
notes=rEntry.notes,
|
||||
)
|
||||
|
||||
session.add(lEntry)
|
||||
lEntries.append(lEntry)
|
||||
|
||||
session.commit()
|
||||
return lEntries
|
||||
|
||||
|
||||
def lists_all(session: Session) -> List[ShoppingList]:
|
||||
"""Return all Shopping Lists."""
|
||||
return session.query(ShoppingList).all()
|
||||
@@ -195,6 +247,19 @@ def list_update(
|
||||
return sList
|
||||
|
||||
|
||||
def list_entry_by_productId(session: Session, listId: int, productId: int) -> Optional[ListEntry]:
|
||||
"""Load a Shopping List Entry by Product Id."""
|
||||
sList = list_by_id(session, listId)
|
||||
if not sList:
|
||||
raise NotFoundError(f'List {listId} not found')
|
||||
|
||||
product = product_by_id(session, productId)
|
||||
if not product:
|
||||
raise NotFoundError(f'Product {productId} not found')
|
||||
|
||||
return session.query(ListEntry).filter(ListEntry.product == product).one_or_none()
|
||||
|
||||
|
||||
def list_entry_by_id(session: Session, listId: int, entryId: int) -> Optional[ListEntry]:
|
||||
"""Load a specific Shopping List Entry."""
|
||||
sList = list_by_id(session, listId)
|
||||
@@ -333,3 +398,159 @@ def product_removeLocation(
|
||||
session.delete(loc)
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def recipe_addItem(
|
||||
session: Session,
|
||||
id: int,
|
||||
*,
|
||||
productId: Optional[int] = None,
|
||||
productName: Optional[str] = None,
|
||||
productCategory: Optional[str] = '',
|
||||
quantity: Optional[str] = None,
|
||||
remember: Optional[bool] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> ListEntry:
|
||||
"""Add a Product to a Recipe.
|
||||
|
||||
If the `productId` parameter is provided, the method will look up the
|
||||
product by ID and add it to the list. If the `product` parameter is not
|
||||
provided, a new `Product` will be created with the provided `productName`
|
||||
and `productCategory` values.
|
||||
|
||||
If the `remember` parameter is provided and is `False`, the product will
|
||||
be removed along with the list item, and it will not be offered as a
|
||||
suggestion when adding items.
|
||||
"""
|
||||
recipe = recipe_by_id(session, id)
|
||||
if not recipe:
|
||||
raise NotFoundError(f'Recipe {id} does not exist')
|
||||
|
||||
product = None
|
||||
if not productId:
|
||||
if not productName:
|
||||
raise DomainError('Product Name cannot be empty')
|
||||
|
||||
product = product_by_name(session, productName)
|
||||
if not product:
|
||||
product = Product(name=productName, category=productCategory)
|
||||
if remember is not None:
|
||||
product.remember = remember
|
||||
|
||||
session.add(product)
|
||||
|
||||
else:
|
||||
product = product_by_id(session, productId)
|
||||
if not product:
|
||||
raise NotFoundError(f'Product {productId} does not exist')
|
||||
|
||||
entry = RecipeEntry(
|
||||
recipe=recipe,
|
||||
product=product,
|
||||
quantity=quantity,
|
||||
notes=notes,
|
||||
)
|
||||
|
||||
session.add(entry)
|
||||
session.commit()
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
def recipes_all(session: Session) -> List[Recipe]:
|
||||
"""Return all Recipes."""
|
||||
return session.query(Recipe).all()
|
||||
|
||||
|
||||
def recipe_by_id(session: Session, id: int) -> Optional[Recipe]:
|
||||
"""Load a specific Recipe."""
|
||||
return session.query(Recipe).filter(Recipe.id == id).one_or_none()
|
||||
|
||||
|
||||
def recipe_create(session: Session, name: str, *, notes=None) -> Recipe:
|
||||
"""Create a new Recipe."""
|
||||
recipe = Recipe(name=name, notes=notes)
|
||||
session.add(recipe)
|
||||
session.commit()
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
def recipe_delete(session: Session, id: int):
|
||||
"""Delete a Recipe."""
|
||||
recipe = recipe_by_id(session, id)
|
||||
if not recipe:
|
||||
raise NotFoundError(f'Recipe {id} does not exist')
|
||||
|
||||
session.delete(recipe)
|
||||
session.commit()
|
||||
|
||||
|
||||
def recipe_deleteItem(session: Session, recipeId: int, entryId: int):
|
||||
"""Delete an Entry from a Recipe."""
|
||||
entry = recipe_entry_by_id(session, recipeId, entryId)
|
||||
if not entry.product.remember:
|
||||
session.delete(entry.product)
|
||||
|
||||
session.delete(entry)
|
||||
session.commit()
|
||||
|
||||
|
||||
def recipe_editItem(
|
||||
session: Session,
|
||||
recipeId: int,
|
||||
entryId: int,
|
||||
*,
|
||||
quantity: Optional[str] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> ListEntry:
|
||||
"""Edit an Entry in a Recipe."""
|
||||
entry = recipe_entry_by_id(session, recipeId, entryId)
|
||||
entry.quantity = quantity
|
||||
entry.notes = notes
|
||||
entry.set_modified_at()
|
||||
|
||||
session.add(entry)
|
||||
session.commit()
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
def recipe_update(
|
||||
session: Session,
|
||||
id: int,
|
||||
name: Union[str, None] = None,
|
||||
notes: Union[str, None] = None,
|
||||
) -> ShoppingList:
|
||||
"""Update the Name and/or Notes of a Recipe."""
|
||||
recipe = recipe_by_id(session, id)
|
||||
if not recipe:
|
||||
raise NotFoundError(f'Recipe {id} does not exist')
|
||||
|
||||
recipe.name = name
|
||||
recipe.notes = notes
|
||||
recipe.set_modified_at()
|
||||
|
||||
session.add(recipe)
|
||||
session.commit()
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
def recipe_entry_by_id(session: Session, recipeId: int, entryId: int) -> Optional[RecipeEntry]:
|
||||
"""Load a specific Recipe Entry."""
|
||||
recipe = recipe_by_id(session, recipeId)
|
||||
if not recipe:
|
||||
raise NotFoundError(f'Recipe {recipeId} not found')
|
||||
|
||||
entry = session.query(RecipeEntry).filter(RecipeEntry.id == entryId).one_or_none()
|
||||
if not entry:
|
||||
raise NotFoundError(f'Recipe Entry {entryId} not found')
|
||||
|
||||
if entry.recipe != recipe:
|
||||
raise DomainError(
|
||||
f'List Entry {entryId} does not belong to List {recipe.name}',
|
||||
status_code=422,
|
||||
)
|
||||
|
||||
return entry
|
||||
|
||||
101
tests/test_22_recipe_service.py
Normal file
101
tests/test_22_recipe_service.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""Test the Product Service Entry Points.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from sigl.domain.service import (
|
||||
product_create,
|
||||
recipe_addItem,
|
||||
recipe_by_id,
|
||||
recipe_create,
|
||||
)
|
||||
|
||||
# Always use 'app' fixture so ORM gets initialized
|
||||
pytestmark = pytest.mark.usefixtures('app')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_recipe_create_defaults(session):
|
||||
"""Test newly created Recipes are empty."""
|
||||
lc = recipe_create(session, 'Test')
|
||||
recipe = recipe_by_id(session, lc.id)
|
||||
|
||||
assert recipe.name == 'Test'
|
||||
assert not recipe.entries
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_recipe_add_product_defaults(session):
|
||||
"""Test adding a Product to a Recipe."""
|
||||
recipe = recipe_create(session, 'Test')
|
||||
entry = recipe_addItem(session, recipe.id, productName='Eggs', productCategory='Dairy')
|
||||
|
||||
assert entry.id is not None
|
||||
assert entry.product is not None
|
||||
assert entry.product.name == 'Eggs'
|
||||
assert entry.product.category == 'Dairy'
|
||||
assert entry.product.remember is True
|
||||
|
||||
assert len(recipe.entries) == 1
|
||||
assert recipe.entries[0] == entry
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_recipe_add_product_by_id(session):
|
||||
"""Test adding an existing Product to a Recipe by Id."""
|
||||
p1 = product_create(session, 'Eggs', category='Dairy')
|
||||
|
||||
recipe = recipe_create(session, 'Test')
|
||||
entry = recipe_addItem(session, recipe.id, productId=p1.id)
|
||||
|
||||
assert entry.id is not None
|
||||
assert entry.product is not None
|
||||
assert entry.product.name == 'Eggs'
|
||||
assert entry.product.category == 'Dairy'
|
||||
assert entry.product.remember is True
|
||||
|
||||
assert len(recipe.entries) == 1
|
||||
assert recipe.entries[0] == entry
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_recipe_add_product_by_name(session):
|
||||
"""Test adding an existing Product to a Recipe by Name."""
|
||||
product_create(session, 'Eggs', category='Dairy')
|
||||
|
||||
recipe = recipe_create(session, 'Test')
|
||||
entry = recipe_addItem(session, recipe.id, productName='eggs')
|
||||
|
||||
assert entry.id is not None
|
||||
assert entry.product is not None
|
||||
assert entry.product.name == 'Eggs'
|
||||
assert entry.product.category == 'Dairy'
|
||||
assert entry.product.remember is True
|
||||
|
||||
assert len(recipe.entries) == 1
|
||||
assert recipe.entries[0] == entry
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_recipe_add_product_no_remember(session):
|
||||
"""Test adding a Product to a Recipe without remembering it."""
|
||||
recipe = recipe_create(session, 'Test')
|
||||
entry = recipe_addItem(
|
||||
session,
|
||||
recipe.id,
|
||||
productName='Eggs',
|
||||
productCategory='Dairy',
|
||||
remember=False,
|
||||
)
|
||||
|
||||
assert entry.id is not None
|
||||
assert entry.product is not None
|
||||
assert entry.product.name == 'Eggs'
|
||||
assert entry.product.category == 'Dairy'
|
||||
assert entry.product.remember is False
|
||||
|
||||
assert len(recipe.entries) == 1
|
||||
assert recipe.entries[0] == entry
|
||||
90
tests/test_31_list_recipes.py
Normal file
90
tests/test_31_list_recipes.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Test the List Recipe Service Entry Points.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from sigl.domain.service import (
|
||||
list_addItem,
|
||||
list_addRecipe,
|
||||
list_create,
|
||||
product_create,
|
||||
recipe_addItem,
|
||||
recipe_create,
|
||||
)
|
||||
|
||||
# Always use 'app' fixture so ORM gets initialized
|
||||
pytestmark = pytest.mark.usefixtures('app')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_add_recipe_empty(session):
|
||||
"""Test adding a Recipe to an empty list."""
|
||||
pEggs = product_create(session, 'Eggs')
|
||||
|
||||
recipe = recipe_create(session, 'Test Recipe')
|
||||
recipe_addItem(session, recipe.id, productId=pEggs.id, quantity='2', notes='Extra Large')
|
||||
recipe_addItem(session, recipe.id, productName='Milk', quantity='1 cup')
|
||||
|
||||
lc = list_create(session, 'Test')
|
||||
lEntries = list_addRecipe(session, lc.id, recipe.id)
|
||||
|
||||
assert(len(lEntries) == 2)
|
||||
assert(len(lc.entries) == 2)
|
||||
|
||||
assert(lc.entries[0].product.name == 'Eggs')
|
||||
assert(lc.entries[0].quantity == '2')
|
||||
assert(lc.entries[0].notes == 'Extra Large')
|
||||
|
||||
assert(lc.entries[1].product.name == 'Milk')
|
||||
assert(lc.entries[1].quantity == '1 cup')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_add_recipe_merge_quantity(session):
|
||||
"""Test adding a Recipe to a list with existing items, merging quantity."""
|
||||
pEggs = product_create(session, 'Eggs')
|
||||
|
||||
recipe = recipe_create(session, 'Test Recipe')
|
||||
recipe_addItem(session, recipe.id, productId=pEggs.id, quantity='2', notes='Extra Large')
|
||||
recipe_addItem(session, recipe.id, productName='Milk', quantity='1 cup')
|
||||
|
||||
lc = list_create(session, 'Test')
|
||||
list_addItem(session, lc.id, productId=pEggs.id, quantity='12')
|
||||
lEntries = list_addRecipe(session, lc.id, recipe.id)
|
||||
|
||||
assert(len(lEntries) == 2)
|
||||
assert(len(lc.entries) == 2)
|
||||
|
||||
assert(lc.entries[0].product.name == 'Eggs')
|
||||
assert(lc.entries[0].quantity == '12, 2 (Test Recipe)')
|
||||
assert(lc.entries[0].notes == 'Extra Large')
|
||||
|
||||
assert(lc.entries[1].product.name == 'Milk')
|
||||
assert(lc.entries[1].quantity == '1 cup')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_add_recipe_merge_notes(session):
|
||||
"""Test adding a Recipe to a list with existing items, merging notes."""
|
||||
pEggs = product_create(session, 'Eggs')
|
||||
|
||||
recipe = recipe_create(session, 'Test Recipe')
|
||||
recipe_addItem(session, recipe.id, productId=pEggs.id, quantity='2', notes='Extra Large')
|
||||
recipe_addItem(session, recipe.id, productName='Milk', quantity='1 cup')
|
||||
|
||||
lc = list_create(session, 'Test')
|
||||
list_addItem(session, lc.id, productId=pEggs.id, notes='Brown, Cage Free')
|
||||
lEntries = list_addRecipe(session, lc.id, recipe.id)
|
||||
|
||||
assert(len(lEntries) == 2)
|
||||
assert(len(lc.entries) == 2)
|
||||
|
||||
assert(lc.entries[0].product.name == 'Eggs')
|
||||
assert(lc.entries[0].quantity == '2')
|
||||
assert(lc.entries[0].notes == 'Brown, Cage Free\nExtra Large')
|
||||
|
||||
assert(lc.entries[1].product.name == 'Milk')
|
||||
assert(lc.entries[1].quantity == '1 cup')
|
||||
Reference in New Issue
Block a user