Compare commits
8 Commits
e6e7c20479
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d0b9b015c | |||
| bc4f01756d | |||
| 66777cfabc | |||
| cff6d9cc50 | |||
| 2c4f98d567 | |||
| 386341f977 | |||
| eb1d1e1dd3 | |||
| 21ffc736bc |
30
.pre-commit-config.yaml
Normal file
30
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.7.4
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.0.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=sigl
|
||||
- --skip="./.*,*.csv,*.json"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==5.1.1
|
||||
files: ^(sigl|tests)/.+\.py$
|
||||
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.5.3
|
||||
hooks:
|
||||
- id: isort
|
||||
@@ -41,5 +41,8 @@ RUN mkdir -p /var/lib/sigl \
|
||||
|
||||
USER sigl
|
||||
EXPOSE 5151
|
||||
ENTRYPOINT [ "/home/sigl/docker-entry.sh" ]
|
||||
VOLUME [ "/var/lib/sigl" ]
|
||||
|
||||
CMD [ "sigl" ]
|
||||
|
||||
ENTRYPOINT [ "/home/sigl/docker-entry.sh" ]
|
||||
|
||||
2
Makefile
2
Makefile
@@ -54,7 +54,7 @@ test-x :
|
||||
test-wip :
|
||||
poetry run python -m pytest tests -m wip
|
||||
|
||||
.PHONY : css docker \
|
||||
.PHONY : css docker docker-deploy \
|
||||
db-init db-migrate db-upgrad db-downgrade \
|
||||
lint shell serve \
|
||||
requirements.txt requirements-dev.txt \
|
||||
|
||||
26
migrations/versions/c28b3a6cdc3a_.py
Normal file
26
migrations/versions/c28b3a6cdc3a_.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: c28b3a6cdc3a
|
||||
Revises: 22dc32e475dd
|
||||
Create Date: 2022-12-24 08:56:13.784788
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c28b3a6cdc3a'
|
||||
down_revision = '22dc32e475dd'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Add the 'remember' column and set to true (original default)
|
||||
op.add_column('products', sa.Column('remember', sa.Boolean(), nullable=True, default=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('products', 'remember')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sigl",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Simple Grocery List",
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3.1.6"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "sigl"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
description = "Simple Grocery List"
|
||||
authors = ["Jonathan Krauss <jkrauss@asymworks.com>"]
|
||||
license = "BSD-3-Clause"
|
||||
|
||||
@@ -8,15 +8,11 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
def init_shell(): # pragma: no cover
|
||||
"""Initialize the Flask Shell Context."""
|
||||
import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from sigl.database import db
|
||||
from sigl.domain.models import (
|
||||
ListEntry,
|
||||
Product,
|
||||
ProductLocation,
|
||||
ShoppingList,
|
||||
)
|
||||
from sigl.domain.models import ListEntry, Product, ProductLocation, ShoppingList
|
||||
|
||||
return {
|
||||
# Imports
|
||||
|
||||
@@ -4,27 +4,18 @@ Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from sigl.domain.models import (
|
||||
Product,
|
||||
ProductLocation,
|
||||
)
|
||||
from sigl.domain.models import Product, ProductLocation
|
||||
from sigl.domain.models.list import ListEntry, ShoppingList
|
||||
|
||||
from .globals import db
|
||||
from .tables import (
|
||||
list_entries,
|
||||
lists,
|
||||
product_locations,
|
||||
products,
|
||||
)
|
||||
from .tables import list_entries, lists, product_locations, products
|
||||
|
||||
__all__ = ('init_orm', )
|
||||
|
||||
|
||||
def init_orm():
|
||||
"""Initialize the Sigl ORM."""
|
||||
|
||||
# # List Entries
|
||||
# List Entries
|
||||
db.mapper(ListEntry, list_entries, properties={
|
||||
'product': db.relationship(
|
||||
Product,
|
||||
|
||||
@@ -64,6 +64,7 @@ products = db.Table(
|
||||
db.Column('name', db.String(128), nullable=False),
|
||||
db.Column('category', db.String(128), nullable=False, index=True),
|
||||
db.Column('defaultQty', db.String(128), default=None),
|
||||
db.Column('remember', db.Boolean, nullable=False, default=True),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
|
||||
@@ -5,7 +5,7 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from .mixins import NotesMixin, TimestampMixin
|
||||
|
||||
@@ -27,6 +27,7 @@ class Product(NotesMixin, TimestampMixin):
|
||||
name: str = None
|
||||
category: str = None
|
||||
defaultQty: str = None
|
||||
remember: bool = True
|
||||
|
||||
# Relationship Fields
|
||||
entries: List['ListEntry'] = field(default_factory=list)
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import List, Optional, Union
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from sigl.exc import DomainError, NotFoundError
|
||||
|
||||
from .models import ListEntry, Product, ProductLocation, ShoppingList
|
||||
|
||||
|
||||
@@ -16,11 +17,12 @@ def list_addItem(
|
||||
session: Session,
|
||||
id: int,
|
||||
*,
|
||||
productId: Optional[int],
|
||||
productName: Optional[str],
|
||||
productCategory: Optional[str],
|
||||
quantity: Optional[str],
|
||||
notes: Optional[str],
|
||||
productId: Optional[int] = None,
|
||||
productName: Optional[str] = None,
|
||||
productCategory: Optional[str] = None,
|
||||
quantity: Optional[str] = None,
|
||||
remember: Optional[bool] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> ListEntry:
|
||||
"""Add a Product to a Shopping List.
|
||||
|
||||
@@ -28,6 +30,10 @@ def list_addItem(
|
||||
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.
|
||||
"""
|
||||
sList = list_by_id(session, id)
|
||||
if not sList:
|
||||
@@ -39,6 +45,8 @@ def list_addItem(
|
||||
raise DomainError('Product Name cannot be empty')
|
||||
|
||||
product = Product(name=productName, category=productCategory)
|
||||
if remember is not None:
|
||||
product.remember = remember
|
||||
|
||||
session.add(product)
|
||||
|
||||
@@ -96,6 +104,8 @@ def list_deleteCrossedOff(session: Session, id: int) -> ShoppingList:
|
||||
raise NotFoundError(f'List {id} does not exist')
|
||||
|
||||
for entry in sList.entries:
|
||||
if not entry.product.remember:
|
||||
session.delete(entry.product)
|
||||
if entry.crossedOff:
|
||||
session.delete(entry)
|
||||
|
||||
@@ -107,6 +117,8 @@ def list_deleteCrossedOff(session: Session, id: int) -> ShoppingList:
|
||||
def list_deleteItem(session: Session, listId: int, entryId: int):
|
||||
"""Delete an Entry from a Shopping List."""
|
||||
entry = list_entry_by_id(session, listId, entryId)
|
||||
if not entry.product.remember:
|
||||
session.delete(entry.product)
|
||||
|
||||
session.delete(entry)
|
||||
session.commit()
|
||||
@@ -117,8 +129,8 @@ def list_editItem(
|
||||
listId: int,
|
||||
entryId: int,
|
||||
*,
|
||||
quantity: Optional[str],
|
||||
notes: Optional[str],
|
||||
quantity: Optional[str] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> ListEntry:
|
||||
"""Edit an Entry on a Shopping List."""
|
||||
entry = list_entry_by_id(session, listId, entryId)
|
||||
@@ -132,7 +144,7 @@ def list_editItem(
|
||||
return entry
|
||||
|
||||
|
||||
def list_stores(session: Session, id: Optional[int]) -> List[str]:
|
||||
def list_stores(session: Session, id: Optional[int] = None) -> List[str]:
|
||||
"""Get a list of all Stores for the List.
|
||||
|
||||
This helper returns a list of all Stores for which the Products in the
|
||||
@@ -140,7 +152,7 @@ def list_stores(session: Session, id: Optional[int]) -> List[str]:
|
||||
Product has locations are returned.
|
||||
"""
|
||||
if id is None:
|
||||
return list(set([loc.store for loc in session.query(ProductLocation).all()]))
|
||||
return list({loc.store for loc in session.query(ProductLocation).all()})
|
||||
|
||||
sList = list_by_id(session, id)
|
||||
if not sList:
|
||||
@@ -162,8 +174,8 @@ def list_stores(session: Session, id: Optional[int]) -> List[str]:
|
||||
def list_update(
|
||||
session: Session,
|
||||
id: int,
|
||||
name: Union[str, None],
|
||||
notes: Union[str, None],
|
||||
name: Union[str, None] = None,
|
||||
notes: Union[str, None] = None,
|
||||
) -> ShoppingList:
|
||||
"""Update the Name and/or Notes of a Shopping List."""
|
||||
sList = list_by_id(session, id)
|
||||
@@ -214,7 +226,7 @@ def list_entry_set_crossedOff(session: Session, listId: int, entryId: int, cross
|
||||
|
||||
def products_all(session: Session) -> List[Product]:
|
||||
"""Return all Products."""
|
||||
return session.query(Product).all()
|
||||
return session.query(Product).filter(Product.remember == True).all() # noqa: E712
|
||||
|
||||
|
||||
def product_by_id(session: Session, id: int) -> Optional[Product]:
|
||||
@@ -226,11 +238,15 @@ def product_create(
|
||||
session: Session,
|
||||
name: str,
|
||||
*,
|
||||
category: Optional[str],
|
||||
notes: Optional[str],
|
||||
category: Optional[str] = '',
|
||||
remember: Optional[bool] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> Product:
|
||||
"""Create a new Product."""
|
||||
product = Product(name=name, category=category, notes=notes)
|
||||
if remember is not None:
|
||||
product.remember = remember
|
||||
|
||||
session.add(product)
|
||||
session.commit()
|
||||
|
||||
@@ -251,8 +267,8 @@ def product_update(
|
||||
session: Session,
|
||||
id: int,
|
||||
name: str,
|
||||
category: Optional[str],
|
||||
notes: Optional[str],
|
||||
category: Optional[str] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> Product:
|
||||
"""Update a Product."""
|
||||
product = product_by_id(session, id)
|
||||
@@ -275,8 +291,8 @@ def product_addLocation(
|
||||
id: int,
|
||||
store: str,
|
||||
*,
|
||||
aisle: Optional[str],
|
||||
bin: Optional[str]
|
||||
aisle: Optional[str] = None,
|
||||
bin: Optional[str] = None,
|
||||
) -> ProductLocation:
|
||||
"""Add a Store Location to a Product."""
|
||||
product = product_by_id(session, id)
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<main class="max-w-3xl mx-auto bg-white md:border-l md:border-r border-b md:rounded-b border-gray-300">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
<footer class="max-w-3xl mx-auto flex flex-col mt-1 px-2 text-xs text-gray-600">
|
||||
<footer class="max-w-3xl mx-auto flex flex-col mt-1 mb-24 px-2 text-xs text-gray-600">
|
||||
<p>Sigl | Simple Grocery List | Version {{ config['APP_VERSION'] }}</p>
|
||||
<p>Copyright ©2022 Asymworks, LLC. All Rights Reserved.</p>
|
||||
</footer>
|
||||
|
||||
@@ -6,17 +6,33 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
flash, jsonify, make_response, redirect, render_template, request, url_for
|
||||
flash,
|
||||
jsonify,
|
||||
make_response,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
|
||||
from sigl.exc import DomainError, Error, NotFoundError
|
||||
from sigl.database import db
|
||||
from sigl.domain.service import (
|
||||
list_entry_by_id, lists_all, list_by_id, list_create, list_delete,
|
||||
list_update, list_addItem, list_deleteItem, list_editItem, list_stores,
|
||||
list_deleteCrossedOff, list_entry_set_crossedOff,
|
||||
list_addItem,
|
||||
list_by_id,
|
||||
list_create,
|
||||
list_delete,
|
||||
list_deleteCrossedOff,
|
||||
list_deleteItem,
|
||||
list_editItem,
|
||||
list_entry_by_id,
|
||||
list_entry_set_crossedOff,
|
||||
list_stores,
|
||||
list_update,
|
||||
lists_all,
|
||||
products_all,
|
||||
)
|
||||
from sigl.exc import DomainError, Error, NotFoundError
|
||||
|
||||
__all__ = ('bp', )
|
||||
|
||||
@@ -58,13 +74,21 @@ def detail(id):
|
||||
if not sList:
|
||||
raise NotFoundError(f'List {id} not found')
|
||||
|
||||
sortBy = request.args.get('sort', 'none')
|
||||
sortStore = request.args.get('store', '')
|
||||
# Load sorting from request (or session)
|
||||
sSort = session.get(f'sorting-{id}', {})
|
||||
sortBy = request.args.get('sort', sSort.get('sort', 'none'))
|
||||
sortStore = request.args.get('store', sSort.get('store', ''))
|
||||
|
||||
if sortBy not in ('none', 'category', 'store'):
|
||||
flash(f'Invalid sorting mode {sortBy}', 'warning')
|
||||
sortBy = 'category'
|
||||
|
||||
# Store sorting back to the session
|
||||
session[f'sorting-{id}'] = {
|
||||
'sort': sortBy,
|
||||
'store': sortStore,
|
||||
}
|
||||
|
||||
groups = dict()
|
||||
for e in sList.entries:
|
||||
if sortBy == 'category':
|
||||
|
||||
@@ -4,23 +4,20 @@ Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
flash, redirect, render_template, request, url_for
|
||||
)
|
||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||
|
||||
from sigl.exc import Error, NotFoundError
|
||||
from sigl.database import db
|
||||
from sigl.domain.service import (
|
||||
list_stores,
|
||||
products_all,
|
||||
product_addLocation,
|
||||
product_by_id,
|
||||
product_create,
|
||||
product_delete,
|
||||
product_update,
|
||||
product_addLocation,
|
||||
product_removeLocation,
|
||||
product_update,
|
||||
products_all,
|
||||
)
|
||||
from sigl.exc import Error, NotFoundError
|
||||
|
||||
__all__ = ('bp', )
|
||||
|
||||
|
||||
@@ -6,5 +6,4 @@ Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
|
||||
from .factory import create_app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
@@ -128,6 +128,7 @@ def session(request, monkeypatch, app):
|
||||
monkeypatch.setattr(_db, 'session', session)
|
||||
|
||||
def teardown():
|
||||
if transaction.is_active:
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
session.remove()
|
||||
|
||||
@@ -76,3 +76,13 @@ def test_product_model_same_store_fails(session):
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
session.commit()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_product_model_remembers_by_default(session):
|
||||
"""Test that the Product defaults to remembering."""
|
||||
p = Product(name='Eggs', category='Dairy')
|
||||
session.add(p)
|
||||
session.commit()
|
||||
|
||||
assert p.remember is True
|
||||
|
||||
62
tests/test_21_product_service.py
Normal file
62
tests/test_21_product_service.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""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_by_id, product_create, products_all
|
||||
|
||||
# Always use 'app' fixture so ORM gets initialized
|
||||
pytestmark = pytest.mark.usefixtures('app')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_product_create_defaults(session):
|
||||
"""Test newly created Products have no Locations."""
|
||||
pc = product_create(session, 'Eggs', category='Dairy')
|
||||
p = product_by_id(session, pc.id)
|
||||
|
||||
assert p.name == 'Eggs'
|
||||
assert p.category == 'Dairy'
|
||||
assert p.remember is True
|
||||
assert not p.locations
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_product_create_without_category(session):
|
||||
"""Test that a Product can be created with a blank Category."""
|
||||
pc = product_create(session, 'Eggs')
|
||||
|
||||
assert pc.id is not None
|
||||
assert pc.name == 'Eggs'
|
||||
assert pc.category == ''
|
||||
assert pc.remember is True
|
||||
assert not pc.locations
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_product_create_forget(session):
|
||||
"""Test newly created Products can have remember as false."""
|
||||
pc = product_create(session, 'Eggs', category='Dairy', remember=False)
|
||||
p = product_by_id(session, pc.id)
|
||||
|
||||
assert p.name == 'Eggs'
|
||||
assert p.category == 'Dairy'
|
||||
assert p.remember is False
|
||||
assert not p.locations
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_product_all_items_skips_non_remembered(session):
|
||||
"""Test that querying all Product items skips non-remembered Products."""
|
||||
p1 = product_create(session, 'Apples')
|
||||
p2 = product_create(session, 'Bananas', remember=False)
|
||||
p3 = product_create(session, 'Carrots')
|
||||
|
||||
products = products_all(session)
|
||||
assert len(products) == 2
|
||||
assert p1 in products
|
||||
assert p3 in products
|
||||
assert p2 not in products
|
||||
107
tests/test_22_list_service.py
Normal file
107
tests/test_22_list_service.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""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 (
|
||||
list_addItem,
|
||||
list_by_id,
|
||||
list_create,
|
||||
list_deleteCrossedOff,
|
||||
list_entry_set_crossedOff,
|
||||
product_by_id,
|
||||
)
|
||||
|
||||
# Always use 'app' fixture so ORM gets initialized
|
||||
pytestmark = pytest.mark.usefixtures('app')
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_create_defaults(session):
|
||||
"""Test newly created Lists are empty."""
|
||||
lc = list_create(session, 'Test')
|
||||
list = list_by_id(session, lc.id)
|
||||
|
||||
assert list.name == 'Test'
|
||||
assert not list.entries
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_add_product_defaults(session):
|
||||
"""Test adding a Product to a List."""
|
||||
list = list_create(session, 'Test')
|
||||
entry = list_addItem(session, list.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(list.entries) == 1
|
||||
assert list.entries[0] == entry
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_add_product_no_remember(session):
|
||||
"""Test adding a Product to a List without remembering it."""
|
||||
list = list_create(session, 'Test')
|
||||
entry = list_addItem(
|
||||
session,
|
||||
list.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(list.entries) == 1
|
||||
assert list.entries[0] == entry
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_removes_product_with_remember(session):
|
||||
"""Test that checking off and deleting a remembered Product does not delete the Product Entry."""
|
||||
list = list_create(session, 'Test')
|
||||
entry = list_addItem(
|
||||
session,
|
||||
list.id,
|
||||
productName='Eggs',
|
||||
productCategory='Dairy',
|
||||
remember=True,
|
||||
)
|
||||
|
||||
pid = entry.product.id
|
||||
|
||||
list_entry_set_crossedOff(session, list.id, entry.id, True)
|
||||
list_deleteCrossedOff(session, list.id)
|
||||
|
||||
assert product_by_id(session, pid) is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_list_removes_product_no_remember(session):
|
||||
"""Test that checking off and deleting a non-remembered Product deletes the Product Entry also."""
|
||||
list = list_create(session, 'Test')
|
||||
entry = list_addItem(
|
||||
session,
|
||||
list.id,
|
||||
productName='Eggs',
|
||||
productCategory='Dairy',
|
||||
remember=False,
|
||||
)
|
||||
|
||||
pid = entry.product.id
|
||||
|
||||
list_entry_set_crossedOff(session, list.id, entry.id, True)
|
||||
list_deleteCrossedOff(session, list.id)
|
||||
|
||||
assert product_by_id(session, pid) is None
|
||||
Reference in New Issue
Block a user