libra/tests/README.md
Padreug 16ae6c2000 docs(tests): record known-good lnbits/dev invocation + env gotchas
The suite targets the lnbits dev worktree (needs lnbits.core.signers)
and trips on three non-obvious environment requirements, each of which
cost a failed run today: LNBITS_EXTENSIONS_PATH is the parent of an
extensions/ dir, the data folder must be a fresh temp dir per run, and
lnbits dev mandates LNBITS_KEY_MASTER at boot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 20:39:14 +02:00

82 lines
4.3 KiB
Markdown

# Libra extension tests
Integration tests covering the user- and admin-facing flows of the libra extension. Tests run against a real `fava` subprocess and a full LNbits app so they catch behaviour that mocks would miss (BQL semantics, Beancount arithmetic, multi-currency aggregation, HTTP boundary).
## Layout
- `conftest.py` — session-scoped Fava subprocess + LNbits app + user/wallet fixtures.
- `helpers.py` — high-level wrappers for the common API flows (`post_expense`, `settle_receivable`, `approve_manual_payment_request`, …). One per intention, so test bodies read as sequences of actions rather than HTTP calls.
- `test_smoke.py` — single end-to-end test; run first to validate the harness.
- `test_<area>_api.py` — per-flow coverage (entries, balances, settlement, manual payment requests, lightning, reconciliation, settings/auth, void/reject).
- `test_unit.py` — pure functions (`beancount_format`, `account_utils`, `core/validation`); no harness.
## Prerequisites
The harness requires `fava` on PATH. On NixOS:
```bash
nix-shell -p python3Packages.fava
```
Inside the regtest container `fava` is already provisioned.
## Running
The suite targets the **`lnbits/dev` worktree** (`~/dev/lnbits/dev`) — it
relies on dev-branch modules (`lnbits.core.signers`, the bunker work) that
`main` doesn't carry. A known-good invocation from scratch:
```bash
# One-time: build a venv with lnbits (dev) + test deps + fava
nix-shell -p uv --run "uv venv /tmp/libra-test-venv --python 3.12 && \
uv pip install --python /tmp/libra-test-venv/bin/python \
-e ~/dev/lnbits/dev pytest asgi-lifespan fava"
# Run (each invocation gets a fresh data folder — REQUIRED, see gotchas)
cd ~/dev/lnbits/dev && \
env LNBITS_KEY_MASTER=$(openssl rand -hex 32) \
LNBITS_DATA_FOLDER=$(mktemp -d -t libra-test-data-XXXX) \
LNBITS_EXTENSIONS_PATH=$HOME/dev/shared \
PYTHONPATH=$HOME/dev/shared/extensions:. \
PATH=/tmp/libra-test-venv/bin:$PATH \
/tmp/libra-test-venv/bin/pytest ~/dev/shared/extensions/libra/tests -q
```
```bash
# Smoke test only (validate the harness before running everything)
... pytest path/to/libra/tests/test_smoke.py
# One area
... pytest path/to/libra/tests/test_balances_api.py
# Single test, verbose
... pytest path/to/libra/tests/test_balances_api.py::test_mixed_income_expense_nets_correctly -v
```
### Environment gotchas (each cost a failed run on 2026-06-12)
- **`LNBITS_EXTENSIONS_PATH` is the *parent* of an `extensions/` dir** —
lnbits scans `{path}/extensions/` (`lnbits/app.py`,
`build_all_installed_extensions_list`). For extensions at
`~/dev/shared/extensions/libra`, pass `~/dev/shared`. Pointing it at
`~/dev/shared/extensions` makes libra invisible: zero extensions install,
migrations never run, and every test errors with
`no such table: extension_settings`.
- **Set `LNBITS_DATA_FOLDER` to a fresh temp dir explicitly.** The
conftest's `os.environ.setdefault` redirect is not always effective;
reusing a previous run's database fails `first_install` with
"Username already exists" during app-fixture setup.
- **`LNBITS_KEY_MASTER` (32-byte hex) is mandatory on lnbits dev** — the
signer migration aborts startup without it (issue lnbits#9
encrypt-at-rest). Any random value is fine for tests.
- **lnbits `main` does not work**: extensions importing
`lnbits.core.signers` fail to load, and libra's app fixture errors.
The Fava subprocess starts once per session (~1-2s) and is shared across tests; each test creates its own LNbits user so the shared ledger doesn't cause inter-test interference.
## Conventions
- **Tests assert intent, not shape.** Use the helpers in `helpers.py` for the request and assert on the *meaning* of the response (balance values, account names, settlement state), not on incidental keys in the JSON. This keeps tests resilient to non-behavioural API tweaks.
- **Currency-handling assertions use `pytest.approx`** for `Decimal`/`float` tolerance.
- **One canonical happy path per flow, plus boundary cases that matter** (voided entries excluded, pending entries excluded, cross-user isolation, auth gate rejection). Don't over-matrix.
- **Each test creates its own users** via the function-scoped `libra_user` / `libra_user_b` fixtures. The ledger is session-shared and accumulates entries; test isolation comes from unique user IDs, not ledger resets.