diff --git a/copanier/models.py b/copanier/models.py index 0f8a948..2ebd84d 100644 --- a/copanier/models.py +++ b/copanier/models.py @@ -119,21 +119,24 @@ class Groups(PersistedBase): __lock__ = threading.Lock() groups: Dict[str, Group] + @classmethod + def get_path(cls): + return cls.get_root() / "groups.yml" + @classmethod def load(cls): - path = cls.get_root() / "groups.yml" + path = cls.get_path() if path.exists(): data = yaml.safe_load(path.read_text()) data = {k: v for k, v in data.items() if k in cls.__dataclass_fields__} else: data = {"groups": {}} groups = cls(**data) - groups.path = path return groups def persist(self): with self.__lock__: - self.path.write_text(self.dump()) + self.get_path().write_text(self.dump()) def add_group(self, group): assert group.id not in self.groups, "Un groupe avec ce nom existe déjà." diff --git a/copanier/templates/delivery/ajust_product.html b/copanier/templates/delivery/adjust_product.html similarity index 100% rename from copanier/templates/delivery/ajust_product.html rename to copanier/templates/delivery/adjust_product.html diff --git a/requirements-dev.txt b/requirements-dev.txt index d50e41f..3081d15 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,5 +4,5 @@ pytest-asyncio usine pyquery pytest-watch -python-emails -Weasyprint \ No newline at end of file +#python-emails +Weasyprint diff --git a/tests/conftest.py b/tests/conftest.py index ba2a064..6435df6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ from roll.testing import Client as BaseClient from copanier import app as copanier_app from copanier import config as kconfig from copanier.utils import create_token -from copanier.models import Delivery, Person, Product +from copanier.models import Delivery, Person, Product, Producer, Groups, Group def pytest_configure(config): @@ -25,7 +25,7 @@ def pytest_runtest_setup(item): class Client(BaseClient): - content_type = 'application/x-www-form-urlencoded; charset=utf-8' + content_type = "application/x-www-form-urlencoded; charset=utf-8" headers = {} async def request( @@ -67,12 +67,44 @@ def app(): # Requested by Roll testing utilities. @pytest.fixture def delivery(): return Delivery( - name="Andines", + name="CRAC d'automne", contact="mister@me.me", from_date=datetime.now() + timedelta(days=10), to_date=datetime.now() + timedelta(days=10), order_before=datetime.now() + timedelta(days=7), - products=[Product(name="Lait", ref="123", price=1.5)], + products=[ + Product(name="Lait", producer="ferme-du-coin", ref="lait", price=1.5,) + ], + producers={"ferme-du-coin": Producer(name="Ferme du coin", id="ferme-du-coin")}, + ) + + +@pytest.fixture +def groups(): + fractal_brocolis = Group( + id="fractal-brocolis", name="The Fractal Brocolis", members=["foo@bar.org"] + ) + groups = Groups({"fractal-brocolis": fractal_brocolis}) + groups.persist() + return groups + + +@pytest.fixture +def yaourt(): + return Product( + ref="yaourt", + unit="pot 125ml", + name="Yaourt", + price="3.5", + packing=4, + producer="ferme-du-coin", + ) + + +@pytest.fixture +def fromage(): + return Product( + ref="fromage", name="Fromage", price="9.2", producer="ferme-du-coin", ) diff --git a/tests/test_imports.py b/tests/test_imports.py deleted file mode 100644 index e6ecd0e..0000000 --- a/tests/test_imports.py +++ /dev/null @@ -1,56 +0,0 @@ -from io import BytesIO - -import pytest -from openpyxl import Workbook - -from copanier import imports -from copanier.models import Product, Delivery - - -@pytest.fixture -def workbook(): - def _(rows, headers=["ref", "name", "price"]): - wb = Workbook() - ws = wb.active - ws.append(headers) - for row in rows: - ws.append(row) - return wb - - return _ - - -def test_mandatory_headers_with_xlsx(delivery, workbook): - with pytest.raises(ValueError): - imports.products_and_producers_from_xlsx( - delivery, - workbook([("123", "Chocolat", "2.3")], headers=["ref", "nom", "prix"]), - ) - - -def test_bad_xlsx_file(delivery, workbook): - with pytest.raises(ValueError): - imports.products_and_producers_from_xlsx(delivery, BytesIO(b"pouet")) - - -def test_simple_xlsx_import(delivery, workbook): - delivery.persist() - assert delivery.products == [Product(ref="123", name="Lait", price=1.5)] - imports.products_and_producers_from_xlsx( - delivery, workbook([("123", "Lait cru", 1.3)]) - ) - assert Delivery.load(delivery.id).products == [ - Product(ref="123", name="Lait cru", price=1.3) - ] - - -def test_simple_xlsx_import_invalid_price(delivery, workbook): - delivery.persist() - assert delivery.products == [Product(ref="123", name="Lait", price=1.5)] - with pytest.raises(ValueError): - imports.products_and_producers_from_xlsx( - delivery, workbook([("123", "Lait cru", "invalid")]) - ) - assert Delivery.load(delivery.id).products == [ - Product(ref="123", name="Lait", price=1.5) - ] diff --git a/tests/test_models.py b/tests/test_models.py index 98334d7..60d5f9b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -3,7 +3,15 @@ from datetime import datetime, timedelta import pytest from copanier import config -from copanier.models import Delivery, Product, Person, Order, ProductOrder, Groups, Group +from copanier.models import ( + Delivery, + Product, + Person, + Order, + ProductOrder, + Groups, + Group, +) now = datetime.now @@ -108,11 +116,11 @@ def test_order_has_adjustments(): def test_order_total(delivery): delivery.products = [Product(name="Lait", ref="123", price=1.5)] order = Order() - assert order.total(delivery.products) == 0 + assert order.total(delivery.products, delivery) == 0 order.products["123"] = ProductOrder(wanted=2) - assert order.total(delivery.products) == 3 + assert order.total(delivery.products, delivery) == 3 order.products["unknown"] = ProductOrder(wanted=2) - assert order.total(delivery.products) == 3 + assert order.total(delivery.products, delivery) == 3 def test_can_persist_delivery(delivery): @@ -176,22 +184,22 @@ def test_archive_delivery(delivery): def test_group_management(): - ndp = Group(id='nid-de-poules', name='Nid de poules', members=['someone@domain.tld']) - assert ndp.id == 'nid-de-poules' - assert ndp.name == 'Nid de poules' + ndp = Group( + id="nid-de-poules", name="Nid de poules", members=["someone@domain.tld"] + ) + assert ndp.id == "nid-de-poules" + assert ndp.name == "Nid de poules" assert len(ndp.members) == 1 groups = Groups.load() groups.persist() groups.add_group(ndp) - groups.add_user('simon@tld', ndp.id) - assert 'simon@tld' in groups.groups[ndp.id].members + groups.add_user("simon@tld", ndp.id) + assert "simon@tld" in groups.groups[ndp.id].members - ladouce = Group(id='la-douce', name='La douce', members=[]) + ladouce = Group(id="la-douce", name="La douce", members=[]) groups.add_group(ladouce) - groups.add_user('simon@tld', ladouce.id) - assert 'simon@tld' in groups.groups[ladouce.id].members - assert 'simon@tld' not in groups.groups[ndp.id].members + groups.add_user("simon@tld", ladouce.id) + assert "simon@tld" in groups.groups[ladouce.id].members + assert "simon@tld" not in groups.groups[ndp.id].members - - \ No newline at end of file diff --git a/tests/test_reports.py b/tests/test_reports.py index f906354..7e57e52 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -6,20 +6,18 @@ from copanier import reports from copanier.models import Order, Product, ProductOrder -def test_summary_report(delivery): +def test_summary_report(delivery, yaourt, fromage): delivery.products[0].packing = 6 - delivery.products.append( - Product(ref="456", name="yaourt", price="3.5", packing=4, unit="pot 125ml") - ) - delivery.products.append(Product(ref="789", name="fromage", price="9.2")) - delivery.orders["foo@bar.org"] = Order( - products={"123": ProductOrder(wanted=1), "456": ProductOrder(wanted=4)} + delivery.products.append(yaourt) + delivery.products.append(fromage) + delivery.orders["fractals-brocoli"] = Order( + products={"lait": ProductOrder(wanted=1), "yaourt": ProductOrder(wanted=4)} ) delivery.persist() wb = load_workbook(filename=BytesIO(reports.summary(delivery))) assert list(wb.active.values) == [ ("ref", "produit", "prix unitaire", "quantité commandée", "unité", "total"), - ("123", "Lait", 1.5, 1, None, 1.5), - ("456", "yaourt (pot 125ml)", 3.5, 4, "pot 125ml", 14), + ("lait", "Lait", 1.5, 1, None, 1.5), + ("yaourt", "Yaourt", 3.5, 4, "pot 125ml", 14), (None, None, None, None, "Total", 15.5), ] diff --git a/tests/test_views.py b/tests/test_views.py index a7a4464..2cf5d8f 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -10,12 +10,21 @@ from copanier.models import Delivery, Order, ProductOrder, Product pytestmark = pytest.mark.asyncio -async def test_empty_home(client): +async def test_home_redirects_to_group_if_needed(client): + client.login(email="new@example.org") + resp = await client.get("/") + assert resp.status == 302 + assert resp.headers["Location"] == "/groupes" + + +async def test_empty_home(client, delivery, groups): + groups.persist() resp = await client.get("/") assert resp.status == 200 -async def test_home_should_list_active_delivery(client, delivery): +async def test_home_should_list_active_delivery(client, delivery, groups): + groups.persist() delivery.persist() resp = await client.get("/") assert resp.status == 200 @@ -55,12 +64,12 @@ async def test_create_delivery(client): async def test_place_order_with_session(client, delivery): delivery.persist() - body = {"wanted:123": "3"} + body = {"wanted:lait": "3"} resp = await client.post(f"/distribution/{delivery.id}/commander", body=body) assert resp.status == 302 delivery = Delivery.load(id=delivery.id) - assert delivery.orders["foo@bar.org"] - assert delivery.orders["foo@bar.org"].products["123"].wanted == 3 + assert "fractal-brocolis" in delivery.orders.keys() + assert delivery.orders["fractal-brocolis"].products["lait"].wanted == 3 async def test_place_empty_order(client, delivery): @@ -72,7 +81,9 @@ async def test_place_empty_order(client, delivery): async def test_place_empty_order_should_delete_previous(client, delivery): - delivery.orders["foo@bar.org"] = Order(products={"123": ProductOrder(wanted=1)}) + delivery.orders["fractal-brocolis"] = Order( + products={"lait": ProductOrder(wanted=1)} + ) delivery.persist() resp = await client.post(f"/distribution/{delivery.id}/commander", body={}) assert resp.status == 302 @@ -82,89 +93,66 @@ async def test_place_empty_order_should_delete_previous(client, delivery): async def test_place_order_with_empty_string(client, delivery): delivery.persist() - body = {"wanted:123": ""} # User deleted the field value. + body = {"wanted:lait": ""} # User deleted the field value. resp = await client.post(f"/distribution/{delivery.id}/commander", body=body) assert resp.status == 302 delivery = Delivery.load(id=delivery.id) assert not delivery.orders -async def test_get_place_order_with_closed_delivery(client, delivery, monkeypatch): +async def test_get_place_order_if_not_adjustable(client, delivery, monkeypatch): monkeypatch.setattr("copanier.config.STAFF", ["someone@else.org"]) delivery.order_before = datetime.now() - timedelta(days=1) - delivery.orders["foo@bar.org"] = Order(products={"123": ProductOrder(wanted=1)}) + delivery.orders["fractal-brocolis"] = Order( + products={"lait": ProductOrder(wanted=1)} + ) delivery.persist() assert delivery.status == delivery.CLOSED resp = await client.get(f"/distribution/{delivery.id}/commander") doc = pq(resp.body) - assert doc('[name="wanted:123"]').attr("readonly") - assert not doc('[name="adjustment:123"]') + assert doc('[name="wanted:lait"]').attr("readonly") + assert not doc('[name="adjustment:lait"]') assert not doc('input[type="submit"]') -async def test_get_place_order_with_adjustment_status(client, delivery): +async def test_get_place_order_with_adjustment_status(client, delivery, yaourt, fromage): resp = await client.get(f"/distribution/{delivery.id}/commander") doc = pq(resp.body) - assert not doc('[name="wanted:123"]').attr("readonly") - assert not doc('[name="adjustment:123"]') + assert not doc('[name="wanted:lait"]').attr("readonly") + assert not doc('[name="adjustment:lait"]') delivery.order_before = datetime.now() - timedelta(days=1) delivery.products[0].packing = 6 - delivery.products.append(Product(ref="456", name="yaourt", price="3.5", packing=4)) - delivery.products.append(Product(ref="789", name="fromage", price="9.2")) - delivery.orders["foo@bar.org"] = Order( - products={"123": ProductOrder(wanted=1), "456": ProductOrder(wanted=4)} + delivery.products.append(yaourt) + delivery.products.append(fromage) + delivery.orders["fractal-brocolis"] = Order( + products={ + "lait": ProductOrder(wanted=1), + "yaourt": ProductOrder(wanted=4) + } ) delivery.persist() assert delivery.status == delivery.ADJUSTMENT resp = await client.get(f"/distribution/{delivery.id}/commander") doc = pq(resp.body) - assert doc('[name="wanted:123"]').attr("readonly") - assert doc('[name="adjustment:123"]') - assert not doc('[name="adjustment:123"]').attr("readonly") - assert doc('[name="adjustment:123"]').attr("min") == "-1" - assert doc('[name="wanted:456"]').attr("readonly") - assert doc('[name="adjustment:456"]') + assert doc('[name="wanted:lait"]').attr("readonly") + assert doc('[name="adjustment:lait"]') + assert not doc('[name="adjustment:lait"]').attr("readonly") + assert doc('[name="adjustment:lait"]').attr("min") == "-1" + assert doc('[name="wanted:yaourt"]').attr("readonly") + assert doc('[name="adjustment:yaourt"]') # Already adjusted. - assert doc('[name="adjustment:456"]').attr("readonly") - assert doc('[name="adjustment:789"]') + assert doc('[name="adjustment:yaourt"]').attr("readonly") + assert doc('[name="adjustment:fromage"]') # Needs no adjustment. - assert doc('[name="adjustment:789"]').attr("readonly") + assert doc('[name="adjustment:fromage"]').attr("readonly") assert doc('input[type="submit"]') -async def test_get_place_order_with_closed_delivery_but_adjustments(client, delivery): - delivery.order_before = datetime.now() - timedelta(days=1) - delivery.orders["foo@bar.org"] = Order( - products={"123": ProductOrder(wanted=1, adjustment=1)} - ) - delivery.persist() - assert delivery.status == delivery.CLOSED - resp = await client.get(f"/distribution/{delivery.id}/commander") - doc = pq(resp.body) - assert doc('[name="wanted:123"]').attr("readonly") - assert doc('[name="adjustment:123"]') - - -async def test_get_place_order_with_closed_delivery_but_force(client, delivery): - delivery.order_before = datetime.now() - timedelta(days=1) - delivery.orders["foo@bar.org"] = Order(products={"123": ProductOrder(wanted=1)}) - delivery.persist() - assert delivery.status == delivery.CLOSED - resp = await client.get(f"/distribution/{delivery.id}/commander") - doc = pq(resp.body) - assert doc('[name="wanted:123"]').attr("readonly") is not None - assert not doc('[name="adjustment:123"]') - resp = await client.get(f"/distribution/{delivery.id}/commander?adjust") - doc = pq(resp.body) - assert doc('[name="wanted:123"]').attr("readonly") is not None - assert doc('[name="adjustment:123"]') - - async def test_cannot_place_order_on_closed_delivery(client, delivery, monkeypatch): monkeypatch.setattr("copanier.config.STAFF", ["someone@else.org"]) delivery.order_before = datetime.now() - timedelta(days=1) delivery.persist() - body = {"wanted:123": "3"} + body = {"wanted:lait": "3"} resp = await client.post(f"/distribution/{delivery.id}/commander", body=body) assert resp.status == 302 delivery = Delivery.load(id=delivery.id) @@ -174,45 +162,45 @@ async def test_cannot_place_order_on_closed_delivery(client, delivery, monkeypat async def test_get_adjust_product(client, delivery): delivery.order_before = datetime.now() - timedelta(days=1) delivery.products[0].packing = 6 - delivery.orders["foo@bar.org"] = Order( - products={"123": ProductOrder(wanted=2, adjustment=1)} + delivery.orders["fractal-brocolis"] = Order( + products={"lait": ProductOrder(wanted=2, adjustment=1)} ) delivery.persist() assert delivery.status == delivery.ADJUSTMENT - resp = await client.get(f"/distribution/{delivery.id}/ajuster/123") + resp = await client.get(f"/distribution/{delivery.id}/ajuster/lait") doc = pq(resp.body) - assert doc('[name="foo@bar.org"]') - assert doc('[name="foo@bar.org"]').attr("value") == "1" + assert doc('[name="fractal-brocolis"]') + assert doc('[name="fractal-brocolis"]').attr("value") == "1" async def test_post_adjust_product(client, delivery): delivery.order_before = datetime.now() - timedelta(days=1) delivery.products[0].packing = 6 - delivery.orders["foo@bar.org"] = Order(products={"123": ProductOrder(wanted=2)}) + delivery.orders["fractal-brocolis"] = Order(products={"lait": ProductOrder(wanted=2)}) delivery.persist() assert delivery.status == delivery.ADJUSTMENT - body = {"foo@bar.org": "1"} - resp = await client.post(f"/distribution/{delivery.id}/ajuster/123", body=body) + body = {"fractal-brocolis": "1"} + resp = await client.post(f"/distribution/{delivery.id}/ajuster/lait", body=body) assert resp.status == 302 delivery = Delivery.load(id=delivery.id) - assert delivery.orders["foo@bar.org"].products["123"].wanted == 2 - assert delivery.orders["foo@bar.org"].products["123"].adjustment == 1 + assert delivery.orders["fractal-brocolis"].products["lait"].wanted == 2 + assert delivery.orders["fractal-brocolis"].products["lait"].adjustment == 1 async def test_only_staff_can_adjust_product(client, delivery, monkeypatch): delivery.order_before = datetime.now() - timedelta(days=1) delivery.products[0].packing = 6 - delivery.orders["foo@bar.org"] = Order(products={"123": ProductOrder(wanted=2)}) + delivery.orders["fractal-brocolis"] = Order(products={"lait": ProductOrder(wanted=2)}) delivery.persist() monkeypatch.setattr("copanier.config.STAFF", ["someone@else.org"]) - resp = await client.get(f"/distribution/{delivery.id}/ajuster/123") + resp = await client.get(f"/distribution/{delivery.id}/ajuster/lait") assert resp.status == 302 - body = {"foo@bar.org": "1"} - resp = await client.post(f"/distribution/{delivery.id}/ajuster/123", body=body) + body = {"fractal-brocolis": "1"} + resp = await client.post(f"/distribution/{delivery.id}/ajuster/lait", body=body) assert resp.status == 302 delivery = Delivery.load(id=delivery.id) - assert delivery.orders["foo@bar.org"].products["123"].wanted == 2 - assert delivery.orders["foo@bar.org"].products["123"].adjustment == 0 + assert delivery.orders["fractal-brocolis"].products["lait"].wanted == 2 + assert delivery.orders["fractal-brocolis"].products["lait"].adjustment == 0 async def test_export_products(client, delivery): @@ -221,15 +209,15 @@ async def test_export_products(client, delivery): wb = load_workbook(filename=BytesIO(resp.body)) assert list(wb.active.values) == [ ( - "name", - "ref", - "price", - "unit", - "description", - "url", - "img", - "packing", - "producer", + 'name', + 'ref', + 'price', + 'last_update', + 'unit', + 'description', + 'packing', + 'producer', + 'rupture' ), - ("Lait", "123", 1.5, None, None, None, None, None, None), + ("Lait", "lait", 1.5, delivery.products[0].last_update, None, None, None, "ferme-du-coin", None), ]