# Automated tests A repo-committed `pytest` suite lives under `tests/`. It replaces the ad-hoc "write a one-off script to a scratch temp dir, run it by hand" verification method used throughout this project's development — every test here follows the same pattern those scripts used (a fresh temp SQLite DB, `create_app()`, `db.create_all()` + `migrate()` + `seed()`, monkeypatching a module's `send_message` / `fiscalize_in_background` / `get_invoice_status` attribute directly instead of pulling in a mocking framework), just organized into real fixtures and committed so it runs in CI / before every deploy instead of living only in someone's memory. ## Running it ```bash pip install -r requirements-dev.txt pytest ``` `pytest.ini` at the repo root points `pytest` at `tests/` and needs no extra flags — just run it from the repo root (or the worktree root). Each test gets its own fresh temp SQLite database file (created and torn down per test in `tests/conftest.py`'s `app` fixture) — slower than a single shared DB, but immune to one test's leftover state (an order, a lockout, a supplier) leaking into another. Module-level shared state used intentionally in production for cross-request coordination (login lockouts, the error-alert cooldown, the Checkbox token cache) is also explicitly reset between tests — see `_reset_module_level_state()` in `conftest.py`. ## What's covered | Module | Test file | What's tested | | --- | --- | --- | | `bober_bbq/payments/monobank.py` | `tests/test_payments.py` | `claim_order_paid()` atomicity (first caller wins, second loses); the Monobank webhook, checkout poll, and reconciliation job never double-fire paid-order side effects for the same invoice | | `bober_bbq/utils/checkbox_prro.py` | `tests/test_fiscalization_alerts.py` | `_mark_error()` alerts every owner, records `fiscal_status`/`fiscal_error`, and respects `error_alerts`' per-(exception, source) cooldown | | `bober_bbq/utils/login_throttle.py` + `/admin/login` | `tests/test_login_throttle.py` | Lockout after `MAX_ATTEMPTS` wrong passwords from one IP (even the correct password is then rejected), an unaffected sibling IP, lockout expiry, and clearing the failure counter on success | | `run_web._warn_on_insecure_defaults` | `tests/test_startup_checks.py` | Alerts the owner when `SECRET_KEY`/`FLASK_ADMIN_PASSWORD` are still the hardcoded defaults; stays silent once overridden | | `bober_bbq/utils/supplier_import.py` | `tests/test_supplier_import_security.py` | `_resolve_upload_path()` rejects absolute paths (both slash styles), `../`/`..\` traversal, a wrong-shape-but-real-extension token, and an empty string — all with `FileNotFoundError`, none touching a file outside the upload temp dir; a legitimate token still round-trips | | `bober_bbq/admin/suppliers.py` | `tests/test_inventory_suppliers.py` | `_apply_stock_topup()` credits stock, auto-assigns `Ingredient.supplier_id` only when unset (never overwrites an existing link), and leaves `StockMovement.note` exactly as typed (regression test for a real bug where it was getting overwritten with the supplier's name) | | `bober_bbq/api/checkout.py` | `tests/test_checkout.py` | Delivery and pickup order creation via the checkout API produces an `Order` row with sane fields (type, totals, address/pickup fields, linked items) and clears the cart; an empty cart is rejected | ## What's explicitly NOT covered yet - **The React webapp** (`webapp/`) has no automated tests at all (no unit tests, no component tests, no E2E). It's tested manually in the browser. - **The Telegram bot's conversation flows** (`bot/`) — aiogram handlers, FSM states, inline keyboards — aren't covered beyond what the checkout HTTP API itself exercises (`bot/services/order_service.py` and `bot/services/cart_service.py`, indirectly, via `tests/test_checkout.py`). A real bot conversation test would need an aiogram test harness/fake `Bot` transport, which doesn't exist yet. - **Checkbox (ПРРО) fiscalization's happy path** — `create_receipt()` actually calling out to Checkbox's API (signin, shift open/close, receipt creation, PDF polling) isn't exercised; only the error-handling side (`_mark_error` / alerting) is. It's all `requests` calls to a real third-party API with no local fake/sandbox available. - **Monobank invoice creation** (`create_invoice`, `verify_token`, webhook-signature verification against a real Monobank public key) — only the *webhook handler's* payment-status logic is tested, with signature verification monkeypatched out. Testing the actual HTTP calls would need a fake Monobank server. - **Remote backups' actual cloud calls** (`bober_bbq/utils/backup_remote.py`) — `tests/test_backup_remote.py` covers the env/path construction per storage type and the rotation logic with `subprocess.run` mocked, but never shells out to a real `rclone` binary or a real cloud provider. - **The scheduled jobs' timing/APScheduler wiring itself** in `run_web.py` (only the job *functions* they call are tested directly, e.g. `reconcile_pending_payments()`). - **Admin panel pages beyond `/admin/login`** — the rest of the Jinja admin UI (order management, menu editing, reports, settings screens) has no route-level tests here; `tests/test_inventory_suppliers.py` calls `_apply_stock_topup()` directly rather than through its HTTP route for simplicity.