bober-bbq-bot/tests/test_inventory_suppliers.py
byrsapty 8bba2873b5 Add repo-committed pytest test suite
Converts the ad-hoc scratch-script verification pattern used throughout
this session's development (temp SQLite DB + create_app() + monkeypatched
send_message/fiscalize_in_background/get_invoice_status, print-based
check() assertions) into a real, repo-committed pytest suite under tests/.

Covers: claim_order_paid() atomicity, cross-path payment race dedup
(webhook/checkout-poll/reconciliation all observing the same paid
invoice), fiscalization error alerting + cooldown, admin login
brute-force lockout, the insecure-defaults startup check, supplier-import
path-traversal protection, basic checkout smoke, and the supplier
restock -> stock/supplier-link bug fixed earlier this session.

Adds requirements-dev.txt (pytest only, keeping the existing
monkeypatch-only style rather than a new mocking framework), pytest.ini
so `pytest` runs from the repo root with no flags, and docs/TESTING.md
covering how to run it and what's explicitly not covered yet (the React
webapp, bot conversation flows beyond the checkout API, live
Checkbox/Monobank/Google Drive integrations).

38 tests, all passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:32:09 +03:00

124 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""bober_bbq.admin.suppliers._apply_stock_topup(): restocking via a
supplier delivery must credit Ingredient.current_stock, auto-assign
Ingredient.supplier_id only when it was previously unset (never overwrite
an existing link), and leave StockMovement.note exactly as the admin
typed it — NOT overwritten with the supplier's name, which was a real bug
fixed earlier this session (the note field duplicated information already
available via the ingredient's own supplier link, surfaced separately in
inventory reports).
"""
from bober_bbq.admin.suppliers import _apply_stock_topup
from bober_bbq.extensions import db
from bober_bbq.models import Ingredient, StockMovement, Supplier, SupplierDelivery
def _make_supplier(name):
supplier = Supplier(name=name)
db.session.add(supplier)
db.session.commit()
return supplier
def test_topup_credits_stock_and_assigns_unset_supplier(app):
with app.app_context():
supplier = _make_supplier("Постачальник Ковбаси")
ingredient = Ingredient(name="Ковбаса", unit="кг", current_stock=5, supplier_id=None)
db.session.add(ingredient)
db.session.commit()
delivery = SupplierDelivery(
supplier_id=supplier.id,
ingredient_id=ingredient.id,
product_name="Ковбаса",
quantity=10,
delivery_sum=200,
note="Партія trohy volog",
)
db.session.add(delivery)
db.session.commit()
_apply_stock_topup(delivery)
assert ingredient.current_stock == 15.0
assert ingredient.supplier_id == supplier.id, "an unset supplier link must be auto-assigned from the delivery"
def test_topup_never_overwrites_an_existing_supplier_link(app):
with app.app_context():
original_supplier = _make_supplier("Оригінальний постачальник")
other_supplier = _make_supplier("Інший постачальник")
ingredient = Ingredient(name="Сіль", unit="кг", current_stock=1, supplier_id=original_supplier.id)
db.session.add(ingredient)
db.session.commit()
delivery = SupplierDelivery(
supplier_id=other_supplier.id,
ingredient_id=ingredient.id,
product_name="Сіль",
quantity=5,
delivery_sum=50,
)
db.session.add(delivery)
db.session.commit()
_apply_stock_topup(delivery)
assert ingredient.current_stock == 6.0
assert ingredient.supplier_id == original_supplier.id, "an already-set supplier link must survive a topup from a different supplier"
def test_topup_stock_movement_note_is_exactly_what_the_admin_typed(app):
"""Regression test for a real production bug: the StockMovement.note
created by a restock was being overwritten with the supplier's name
instead of preserving the admin's own free-text note."""
with app.app_context():
supplier = _make_supplier("Постачальник Пряме Поповнення")
ingredient = Ingredient(name="Кетчуп", unit="кг", current_stock=5, supplier_id=supplier.id)
db.session.add(ingredient)
db.session.commit()
admin_note = "накладна №НК-9, часткова оплата готівкою"
delivery = SupplierDelivery(
supplier_id=supplier.id,
ingredient_id=ingredient.id,
product_name="Кетчуп",
quantity=10,
delivery_sum=200,
reference="НК-9",
note=admin_note,
)
db.session.add(delivery)
db.session.commit()
_apply_stock_topup(delivery)
movement = StockMovement.query.filter_by(ingredient_id=ingredient.id, reason="restock").first()
assert movement is not None
assert movement.note == admin_note, "the movement note must be exactly what the admin typed, not the supplier's name"
assert supplier.name not in (movement.note or ""), "the supplier name must not be injected into the note"
def test_topup_with_no_note_leaves_movement_note_blank(app):
with app.app_context():
supplier = _make_supplier("Постачальник Без Нотатки")
ingredient = Ingredient(name="Цукор", unit="кг", current_stock=0, supplier_id=None)
db.session.add(ingredient)
db.session.commit()
delivery = SupplierDelivery(
supplier_id=supplier.id,
ingredient_id=ingredient.id,
product_name="Цукор",
quantity=20,
delivery_sum=400,
note=None,
)
db.session.add(delivery)
db.session.commit()
_apply_stock_topup(delivery)
movement = StockMovement.query.filter_by(ingredient_id=ingredient.id, reason="restock").first()
assert movement is not None
assert movement.note is None