Compare commits

...

2 commits

Author SHA1 Message Date
21aa6ea7c3 Merge pull request 'fix(models): restore check_in/check_out on AvailabilityQuery' (#20) from fix/availability-query-fields into main
Reviewed-on: #20
2026-09-21 09:03:31 +00:00
64607a00bc fix(models): restore check_in/check_out on AvailabilityQuery
v0.4.0's merge of public_booking_dict landed inside AvailabilityQuery and
left the two date fields orphaned after the helper's return, so
POST /api/v1/availability raised AttributeError on q.check_in (500) on
every install of that version. The RPC door reads the raw body and was
unaffected, which is why nothing noticed.

Put the helper next to public_room_dict and add a regression test that
constructs the query model and drives the endpoint through the
services layer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:42:43 +02:00
2 changed files with 60 additions and 4 deletions

View file

@ -206,8 +206,6 @@ def public_room_dict(room: Room) -> dict:
return d return d
class AvailabilityQuery(BaseModel):
room_id: str
def public_booking_dict(booking: "Booking") -> dict: def public_booking_dict(booking: "Booking") -> dict:
"""A Booking as public JSON for the guest who holds its id — lifecycle + """A Booking as public JSON for the guest who holds its id — lifecycle +
money + dates only. Strips the guest's own identity/contact (so the id money + dates only. Strips the guest's own identity/contact (so the id
@ -227,6 +225,8 @@ def public_booking_dict(booking: "Booking") -> dict:
return d return d
class AvailabilityQuery(BaseModel):
room_id: str
check_in: str # YYYY-MM-DD inclusive check_in: str # YYYY-MM-DD inclusive
check_out: str # YYYY-MM-DD exclusive check_out: str # YYYY-MM-DD exclusive

View file

@ -0,0 +1,56 @@
"""Guest availability endpoint. Regression guard for v0.4.0, where a bad merge
left AvailabilityQuery with only `room_id` and POST /availability 500'd on
`q.check_in` — the RPC door reads the raw body, so only HTTP was broken and no
test exercised it."""
import asyncio
import pytest
from fastapi import HTTPException
from pydantic import ValidationError
from .. import services, views_api
from ..models import AvailabilityQuery, AvailabilityResult
def test_availability_query_requires_both_dates():
q = AvailabilityQuery(room_id="r", check_in="2026-10-01", check_out="2026-10-03")
assert (q.room_id, q.check_in, q.check_out) == ("r", "2026-10-01", "2026-10-03")
assert set(AvailabilityQuery.__fields__) == {"room_id", "check_in", "check_out"}
with pytest.raises(ValidationError):
AvailabilityQuery(room_id="r") # type: ignore[call-arg]
def test_availability_endpoint_forwards_all_three_fields(monkeypatch):
seen: list[tuple] = []
async def fake(room_id, check_in, check_out):
seen.append((room_id, check_in, check_out))
return AvailabilityResult(
room_id=room_id,
check_in=check_in,
check_out=check_out,
available=True,
nights=2,
)
monkeypatch.setattr(services, "get_availability", fake)
q = AvailabilityQuery(room_id="r", check_in="2026-10-01", check_out="2026-10-03")
out = asyncio.run(views_api.api_check_availability(q))
assert seen == [("r", "2026-10-01", "2026-10-03")]
assert out.available and out.nights == 2
@pytest.mark.parametrize(
("exc", "status"),
[(services.NotFound("Room not found"), 404), (ValueError("bad dates"), 400)],
)
def test_availability_endpoint_maps_service_errors(monkeypatch, exc, status):
async def fake(*_):
raise exc
monkeypatch.setattr(services, "get_availability", fake)
q = AvailabilityQuery(room_id="r", check_in="2026-10-03", check_out="2026-10-01")
with pytest.raises(HTTPException) as e:
asyncio.run(views_api.api_check_availability(q))
assert e.value.status_code == status