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>
75 lines
5.3 KiB
Markdown
75 lines
5.3 KiB
Markdown
# 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.
|