test: add test harness + suite (incl. #4 concurrency regression) #10

Merged
padreug merged 2 commits from feat/tests-harness into main 2026-07-19 15:49:25 +00:00
Owner

Stands up a tests/ harness for chatelet and covers the money paths, including a real regression for the #4 atomic-hold fix.

What's here

  • Tooling (pyproject.toml, Makefile) — poetry/pytest/ruff/black/mypy config mirroring the aio lint pipeline (spirekeeper template). make test, make check.
  • 16 tests run under the LNbits pytest env, spirekeeper-style (monkeypatch crud/create_invoice, drive async with asyncio.run — no live DB or wallet):
    • test_availability — half-open overlap semantics (back-to-back stays don't collide), is_available blocking on held/confirmed bookings + manual blocks, cancelled bookings freeing dates.
    • test_booking_flow — canonical amount_sat, held → awaiting_payment, min-nights guard, and hold-release (declined) on InvoiceError.
    • test_atomic_hold — the #4 regression: two concurrent same-date requests → exactly one hold + one Unavailable; two non-overlapping requests on the same room both succeed.

The concurrency test genuinely guards the fix

The fakes await asyncio.sleep between reading availability and committing the hold, opening the exact race window the per-room lock closes. Verified locally:

  • With the lock: 16 passed.
  • Neutering the lock (fresh un-contended lock per call): the same-dates test fails with two BookingQuotes (double-booking) — confirming the test fails without the fix.

Notes

  • Ran locally via the dev LNbits .venv by symlinking the extension into lnbits/extensions/ (removed after). CI/make test runs it in the same env.
  • Added an autouse _reset_room_locks fixture: each test uses its own asyncio.run loop, and the module-level lock registry would otherwise carry a lock bound to a closed loop. Production is unaffected (one long-lived loop).

Why PR

Consistency with the repo's flow; handing off merge to you via the Forgejo UI.

🤖 Generated with Claude Code

Stands up a `tests/` harness for chatelet and covers the money paths, including a real regression for the #4 atomic-hold fix. ## What's here - **Tooling** (`pyproject.toml`, `Makefile`) — poetry/pytest/ruff/black/mypy config mirroring the aio lint pipeline (spirekeeper template). `make test`, `make check`. - **16 tests** run under the LNbits pytest env, spirekeeper-style (monkeypatch `crud`/`create_invoice`, drive async with `asyncio.run` — no live DB or wallet): - `test_availability` — half-open overlap semantics (back-to-back stays don't collide), `is_available` blocking on held/confirmed bookings + manual blocks, cancelled bookings freeing dates. - `test_booking_flow` — canonical `amount_sat`, `held → awaiting_payment`, min-nights guard, and hold-release (`declined`) on `InvoiceError`. - `test_atomic_hold` — the **#4 regression**: two concurrent same-date requests → exactly one hold + one `Unavailable`; two non-overlapping requests on the same room both succeed. ## The concurrency test genuinely guards the fix The fakes `await asyncio.sleep` between reading availability and committing the hold, opening the exact race window the per-room lock closes. Verified locally: - **With** the lock: `16 passed`. - **Neutering** the lock (fresh un-contended lock per call): the same-dates test **fails** with two `BookingQuote`s (double-booking) — confirming the test fails without the fix. ## Notes - Ran locally via the dev LNbits `.venv` by symlinking the extension into `lnbits/extensions/` (removed after). CI/`make test` runs it in the same env. - Added an autouse `_reset_room_locks` fixture: each test uses its own `asyncio.run` loop, and the module-level lock registry would otherwise carry a lock bound to a closed loop. Production is unaffected (one long-lived loop). ## Why PR Consistency with the repo's flow; handing off merge to you via the Forgejo UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Poetry/pytest/ruff/black/mypy config mirroring the aio extension lint
pipeline (spirekeeper template). `make test` runs pytest; `make check`
runs the linters. Enables a tests/ suite for the extension.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
16 tests, run under the LNbits pytest env (spirekeeper pattern: monkeypatch
crud/invoice, drive async via asyncio.run — no live DB/wallet):

- test_availability: half-open overlap semantics (back-to-back stays OK),
  and is_available blocking on held/confirmed bookings + manual blocks.
- test_booking_flow: canonical amount_sat, awaiting_payment transition,
  min-nights guard, and hold-release (declined) on InvoiceError.
- test_atomic_hold: the #4 regression — two concurrent same-date requests
  yield exactly one hold + one conflict; non-overlapping both succeed. The
  fakes yield mid-check to open the race window, so the test fails without
  the per-room lock (verified by neutering it) and passes with it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
padreug deleted branch feat/tests-harness 2026-07-19 15:49:26 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/chatelet!10
No description provided.