Chatelet is multi-tenant: any LNbits user can host rooms. What an operator decides for all their rooms now lives in chatelet.operator_settings, keyed by user id and created lazily (m003, which also indexes bookings by guest): check-in/out times, cancellation policy, and accept_fiat. Guests see it: the public room view (both doors) gains house_rules and payment_methods, and the kind:30402 listing carries payment_methods, checkin_time and checkout_time tags so a generic Nostr client can render the right pay buttons and rules without our RPC. The check-in DM reads the room owner's rules instead of the instance row. Card is offered only when the operator opted in, the room is fiat-priced, and LNbits core has a fiat provider for that user — resolved through settings.get_fiat_providers_for_user(owner), the one seam lnbits#67's per-user Stripe credentials will plug into; chatelet never sees creds. Operator endpoints: GET/PUT /api/v1/operator (admin key → wallet user) and RPC twins chatelet_operator_get/update (AUTH_WALLET); saving re-publishes the owner's active listings. Admin UI moves the house-rule inputs into a per-operator card with the card toggle and a provider hint. The old house-rule columns on settings stay for old rows but are no longer read. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
"""Pytest configuration + factories for the chatelet test suite.
|
|
|
|
Following the spirekeeper pattern: unit tests isolate the function under
|
|
test by monkeypatching the crud / lnbits calls it makes and driving async
|
|
functions with `asyncio.run(...)`, so no live LNbits DB or wallet is needed.
|
|
The `loguru_capture` fixture is here for the same reason spirekeeper needs
|
|
it — loguru binds sys.stderr at import, before capsys wraps it.
|
|
"""
|
|
|
|
from typing import Generator, List
|
|
|
|
import pytest
|
|
from loguru import logger
|
|
|
|
from ..models import (
|
|
BookingRequestData,
|
|
CreateRoomData,
|
|
Room,
|
|
RoomStatus,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_room_locks():
|
|
"""Each test drives its own `asyncio.run` loop, but `services._room_locks`
|
|
is module-level — a lock created in one test's loop would raise
|
|
'bound to a different event loop' when reused in the next. Clear the
|
|
registry around every test so locks are recreated in the active loop.
|
|
(Production is unaffected: LNbits runs one long-lived loop.)"""
|
|
from .. import services
|
|
|
|
services._room_locks.clear()
|
|
yield
|
|
services._room_locks.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def loguru_capture() -> Generator[List[str], None, None]:
|
|
captured: List[str] = []
|
|
handler_id = logger.add(
|
|
captured.append, level="WARNING", format="{level} {message}"
|
|
)
|
|
yield captured
|
|
logger.remove(handler_id)
|
|
|
|
|
|
# --- factories -------------------------------------------------------------
|
|
|
|
|
|
def make_room(
|
|
room_id: str = "room1",
|
|
*,
|
|
wallet: str = "wallet1",
|
|
currency: str = "sat", # "sat" so pricing skips the FX call in tests
|
|
price: float = 100.0,
|
|
min_nights: int = 1,
|
|
max_guests: int = 2,
|
|
status: RoomStatus = RoomStatus.active,
|
|
) -> Room:
|
|
return Room(
|
|
**CreateRoomData(
|
|
wallet=wallet,
|
|
title="Tower Room",
|
|
price_amount=price,
|
|
price_currency=currency,
|
|
min_nights=min_nights,
|
|
max_guests=max_guests,
|
|
).dict(),
|
|
id=room_id,
|
|
status=status,
|
|
)
|
|
|
|
|
|
def make_request(
|
|
room_id: str = "room1",
|
|
*,
|
|
guest: str = "npub_guest",
|
|
check_in: str = "2026-08-01",
|
|
check_out: str = "2026-08-04",
|
|
num_guests: int = 1,
|
|
) -> BookingRequestData:
|
|
return BookingRequestData(
|
|
room_id=room_id,
|
|
guest_pubkey=guest,
|
|
check_in=check_in,
|
|
check_out=check_out,
|
|
num_guests=num_guests,
|
|
)
|
|
|
|
|
|
def patch_owner(monkeypatch, *, user_id="u1", accept_fiat=False, providers=()):
|
|
"""Route the public room view away from the core DB: rooms belong to
|
|
`user_id`, whose operator settings are fresh defaults (+ accept_fiat) and
|
|
who has `providers` enabled in LNbits core."""
|
|
from types import SimpleNamespace
|
|
|
|
from .. import crud, services
|
|
from ..models import OperatorSettings
|
|
|
|
async def get_wallet(_wallet_id):
|
|
return SimpleNamespace(user=user_id)
|
|
|
|
async def ops(uid):
|
|
return OperatorSettings(user_id=uid, accept_fiat=accept_fiat)
|
|
|
|
monkeypatch.setattr(services, "get_wallet", get_wallet)
|
|
monkeypatch.setattr(crud, "get_or_create_operator_settings", ops)
|
|
monkeypatch.setattr(services, "fiat_providers_for_user", lambda _uid: list(providers))
|