From 64607a00bce5bda57e23b6f95be4fc3bfa358a78 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 16 Sep 2026 11:42:43 +0200 Subject: [PATCH] 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 --- models.py | 8 ++--- tests/test_availability_endpoint.py | 56 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/test_availability_endpoint.py diff --git a/models.py b/models.py index be6f22d..77d4537 100644 --- a/models.py +++ b/models.py @@ -206,8 +206,6 @@ def public_room_dict(room: Room) -> dict: return d -class AvailabilityQuery(BaseModel): - room_id: str def public_booking_dict(booking: "Booking") -> dict: """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 @@ -227,8 +225,10 @@ def public_booking_dict(booking: "Booking") -> dict: return d - check_in: str # YYYY-MM-DD inclusive - check_out: str # YYYY-MM-DD exclusive +class AvailabilityQuery(BaseModel): + room_id: str + check_in: str # YYYY-MM-DD inclusive + check_out: str # YYYY-MM-DD exclusive class AvailabilityResult(BaseModel): diff --git a/tests/test_availability_endpoint.py b/tests/test_availability_endpoint.py new file mode 100644 index 0000000..9f6cbc1 --- /dev/null +++ b/tests/test_availability_endpoint.py @@ -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 -- 2.55.0