Testing
Philosophy: most tests are pure Python over shaped inputs (no DB, no HTTP), a small number are DB-backed, and one end-to-end script drives a full India run.
The layers
| Layer | Speed | Location |
|---|---|---|
| Pure-logic pytest | sub-second per file | tests/test_*.py (most) |
| DB-backed pytest | seconds | tests that touch SessionLocal |
| e2e | 10-20s | scripts/e2e_india.py |
Run everything
cd apps/ledger
./.venv/Scripts/python.exe -m pytest tests/ -q
Run just the India suite
./.venv/Scripts/python.exe -m pytest \
tests/test_india_capital_gains.py tests/test_india_lrs_tax.py \
tests/test_india_fx_margin.py tests/test_india_remittance_exceptions.py \
tests/test_viewtrade_report_importer.py tests/test_india_nav.py \
tests/test_india_holdings_recon.py tests/test_india_schedule_fa.py \
tests/test_india_settlement_audit.py tests/test_india_broker_fees.py \
tests/test_india_webhooks.py tests/test_glomopay.py \
tests/test_india_reconciliation.py tests/test_provisioning.py -q
Run the e2e
python -m scripts.e2e_india
Drives: provisioning upsert → viewtrade sync → glomopay sync → reconciliation
matched + mismatch cases → idempotency. Against a real ledger_ind DB (local).
Windows asyncpg flakiness
DB-backed pytest can flake on Windows due to asyncpg event-loop churn. The e2e script uses a different runtime pattern and is reliable there.
Adding a test
Every service module has a test_*.py. The pattern:
- Extract a PURE function (dict → summary) alongside the DB layer.
- Test the pure function with named-tuple inputs.
- If you need DB coverage, add an e2e case in
scripts/e2e_india.py.
CI
Runs the full pytest on push. If you need to skip a slow test in CI, mark it
with @pytest.mark.slow and configure CI to skip that marker.
Coverage
Not enforced as a gate. pytest --cov=ledger tests/ for a local report.