bober-bbq-bot/docs/TESTING.md
byrsapty 375022e739 Add backup_remote.py and wire up the rest of the rclone rework
Continuation of the previous commit (gdrive_backup.py removal landed
separately by accident) — adds the new module itself, the admin
routes/template using it, generalized Setting keys, updated docs/
provisioning notes, and tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 22:57:59 +03:00

5.3 KiB

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

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 pathcreate_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.