mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-10 01:55:48 -04:00
770 lines
27 KiB
Python
770 lines
27 KiB
Python
"""Unit tests for the backend.routers.bookings module."""
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
from sqlalchemy.exc import NoResultFound
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from backend.db import get_session
|
|
from backend.dependencies.bookings import delete_booking_service
|
|
from backend.dependencies.bookings import get_booking_service
|
|
from backend.dependencies.bookings import get_bookings_for_month_service
|
|
from backend.dependencies.bookings import new_booking_service
|
|
from backend.dependencies.bookings import update_booking_service
|
|
from backend.dependencies.rooms import get_room_service
|
|
from backend.main import app
|
|
from backend.models import Booking
|
|
from backend.models import Room
|
|
from backend.routers.rooms import publish_room_availability_event
|
|
from backend.services.bookings import update_booking
|
|
from backend.types import BookingCreateData
|
|
from backend.types import BookingData
|
|
from backend.types import BookingUpdateData
|
|
|
|
|
|
# Useful constants for booking router tests
|
|
BOOKING_NOT_FOUND_ID = 999
|
|
BOOKING_DB_ERROR_ID = 1
|
|
BOOKING_NOT_FOUND_DETAIL = "Booking not found"
|
|
BOOKING_DB_ERROR_DETAIL = "Database error"
|
|
BOOKING_CONFLICT_DETAIL = (
|
|
"Booking times overlap with an existing booking for this room."
|
|
)
|
|
BOOKING_VALIDATION_MISSING_FIELD = "Invalid booking data: missing required field"
|
|
BOOKING_VALIDATION_WRONG_TYPE = "Invalid booking data: wrong type"
|
|
BOOKING_CONFLICT_STATUS = 409
|
|
BOOKING_SUCCESS_STATUS = 201
|
|
BOOKING_NOT_FOUND_STATUS = 404
|
|
BOOKING_DB_ERROR_STATUS = 500
|
|
BOOKING_GOOD_STATUS = 200
|
|
BOOKING_VALIDATION_ERROR_STATUS = 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_booking_success(
|
|
client: AsyncClient,
|
|
sample_booking: Booking,
|
|
updated_booking: Booking,
|
|
booking_update_data: BookingUpdateData,
|
|
) -> None:
|
|
"""Test successful update of a booking via PATCH /bookings/{booking_id} using fixtures.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_booking: The original booking to be updated.
|
|
updated_booking: The expected updated booking.
|
|
booking_update_data: The data to update the booking with.
|
|
|
|
Asserts:
|
|
- Response status code is BOOKING_GOOD_STATUS.
|
|
- Response JSON matches updated booking fields (id, room_id, start_time,
|
|
end_time, title, invitees).
|
|
"""
|
|
booking_id = sample_booking.id
|
|
update_data = booking_update_data
|
|
|
|
async def mock_update_booking(
|
|
session: AsyncSession,
|
|
booking_id: int,
|
|
event_publisher: Any = None,
|
|
**kwargs: Any,
|
|
) -> Booking:
|
|
return updated_booking
|
|
|
|
app.dependency_overrides[update_booking] = mock_update_booking
|
|
response = await client.patch(f"/bookings/{booking_id}", json=update_data)
|
|
assert response.status_code == BOOKING_GOOD_STATUS
|
|
actual = response.json()
|
|
actual.pop("room", None)
|
|
expected_start = updated_booking.start_time.isoformat().replace("+00:00", "")
|
|
expected_end = updated_booking.end_time.isoformat().replace("+00:00", "")
|
|
actual_start = actual["start_time"].replace("Z", "")
|
|
actual_end = actual["end_time"].replace("Z", "")
|
|
assert actual["id"] == booking_id
|
|
assert actual["room_id"] == updated_booking.room_id
|
|
assert actual_start == expected_start
|
|
assert actual_end == expected_end
|
|
assert actual["title"] == updated_booking.title
|
|
assert (
|
|
actual["invitees"] == updated_booking.invitees or actual["invitees"] is not None
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,status,detail,booking_id",
|
|
[
|
|
(
|
|
NoResultFound,
|
|
BOOKING_NOT_FOUND_STATUS,
|
|
BOOKING_NOT_FOUND_DETAIL,
|
|
BOOKING_NOT_FOUND_ID,
|
|
),
|
|
(
|
|
SQLAlchemyError,
|
|
BOOKING_DB_ERROR_STATUS,
|
|
BOOKING_DB_ERROR_DETAIL,
|
|
BOOKING_DB_ERROR_ID,
|
|
),
|
|
],
|
|
ids=["not_found", "db_error"],
|
|
)
|
|
async def test_update_booking_error_cases(
|
|
client: AsyncClient,
|
|
booking_update_data: BookingUpdateData,
|
|
exception: type[Exception],
|
|
status: int,
|
|
detail: str,
|
|
booking_id: int,
|
|
) -> None:
|
|
"""Test error cases for updating a booking via PATCH /bookings/{booking_id}.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
booking_update_data: The data to update the booking with.
|
|
exception: The exception to raise.
|
|
status: The expected HTTP status code.
|
|
detail: The expected error detail message.
|
|
booking_id: The ID of the booking to update.
|
|
|
|
Asserts:
|
|
- Response status code matches expected error status.
|
|
- Response JSON 'detail' matches expected error detail.
|
|
"""
|
|
|
|
async def mock_update_booking(
|
|
session: AsyncSession,
|
|
booking_id: int,
|
|
event_publisher: Any = None,
|
|
**kwargs: Any,
|
|
) -> Booking:
|
|
raise exception
|
|
|
|
async def mock_get_booking(session: AsyncSession, booking_id: int) -> Booking:
|
|
raise exception
|
|
|
|
app.dependency_overrides[update_booking_service] = lambda: mock_update_booking
|
|
app.dependency_overrides[get_booking_service] = lambda: mock_get_booking
|
|
|
|
async def mock_get_room_none(*a: Any, **kw: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
response = await client.patch(f"/bookings/{booking_id}", json=booking_update_data)
|
|
assert response.status_code == status
|
|
assert response.json()["detail"] == detail
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,status,detail,booking_id",
|
|
[
|
|
(
|
|
NoResultFound,
|
|
BOOKING_NOT_FOUND_STATUS,
|
|
BOOKING_NOT_FOUND_DETAIL,
|
|
BOOKING_NOT_FOUND_ID,
|
|
),
|
|
(
|
|
SQLAlchemyError,
|
|
BOOKING_DB_ERROR_STATUS,
|
|
BOOKING_DB_ERROR_DETAIL,
|
|
BOOKING_DB_ERROR_ID,
|
|
),
|
|
],
|
|
ids=["not_found", "db_error"],
|
|
)
|
|
async def test_delete_booking_error_cases(
|
|
client: AsyncClient,
|
|
exception: type[Exception],
|
|
status: int,
|
|
detail: str,
|
|
booking_id: int,
|
|
) -> None:
|
|
"""Test error cases for deleting a booking via DELETE /bookings/{booking_id}.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
exception: The exception to raise.
|
|
status: The expected HTTP status code.
|
|
detail: The expected error detail message.
|
|
booking_id: The ID of the booking to delete.
|
|
|
|
Asserts:
|
|
- Response status code matches expected error status.
|
|
- Response JSON 'detail' matches expected error detail.
|
|
"""
|
|
|
|
async def mock_delete_booking(
|
|
session: AsyncSession, booking_id: int, event_publisher: Any = None
|
|
) -> None:
|
|
raise exception
|
|
|
|
async def mock_get_booking(session: AsyncSession, booking_id: int) -> Booking:
|
|
raise exception
|
|
|
|
app.dependency_overrides[delete_booking_service] = lambda: mock_delete_booking
|
|
app.dependency_overrides[get_booking_service] = lambda: mock_get_booking
|
|
|
|
async def mock_get_room_none(*a: Any, **kw: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
response = await client.delete(f"/bookings/{booking_id}")
|
|
assert response.status_code == status
|
|
assert response.json()["detail"] == detail
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"endpoint,payload,expected_status,expected_detail",
|
|
[
|
|
(
|
|
"/bookings/",
|
|
"conflict_booking_data",
|
|
BOOKING_CONFLICT_STATUS,
|
|
BOOKING_CONFLICT_DETAIL,
|
|
),
|
|
],
|
|
)
|
|
async def test_booking_creation_conflict(
|
|
client: AsyncClient,
|
|
sample_room: Room,
|
|
sample_booking: Booking,
|
|
sample_bookings: list[Booking],
|
|
sample_booking_data: BookingCreateData,
|
|
conflict_booking_data: BookingCreateData,
|
|
endpoint: str,
|
|
payload: str,
|
|
expected_status: int,
|
|
expected_detail: str | None,
|
|
) -> None:
|
|
"""Test booking creation conflict scenario (POST).
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked Room object.
|
|
sample_booking: The mocked Booking object.
|
|
sample_bookings: The mocked list of Bookings.
|
|
sample_booking_data: The mocked booking data dictionary.
|
|
conflict_booking_data: The mocked conflicting booking data dictionary.
|
|
endpoint: The API endpoint to test.
|
|
payload: The request payload.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response JSON 'detail' matches expected_detail if provided.
|
|
"""
|
|
|
|
async def mock_new_booking(
|
|
session: AsyncSession, booking: Any, event_publisher: Any = None
|
|
) -> dict[str, Any]:
|
|
booking_start = (
|
|
booking.start_time.isoformat()
|
|
if hasattr(booking.start_time, "isoformat")
|
|
else str(booking.start_time)
|
|
)
|
|
if booking_start == sample_booking_data["start_time"]:
|
|
return sample_booking_data | {"id": sample_booking.id}
|
|
raise ValueError(BOOKING_CONFLICT_DETAIL)
|
|
|
|
async def noop(*args: Any, **kwargs: Any) -> None:
|
|
return None
|
|
|
|
dummy_session = AsyncMock(spec=AsyncSession)
|
|
dummy_session.add_all = noop
|
|
dummy_session.commit = noop
|
|
app.dependency_overrides[new_booking_service] = lambda: mock_new_booking
|
|
app.dependency_overrides[get_session] = lambda: dummy_session
|
|
app.dependency_overrides[get_room_service] = lambda: (
|
|
lambda *a: sample_room # pyright: ignore
|
|
)
|
|
app.dependency_overrides[publish_room_availability_event] = lambda *a: None
|
|
|
|
payload_data = locals()[payload]
|
|
response = await client.post(endpoint, json=payload_data)
|
|
assert response.status_code == expected_status
|
|
if expected_detail:
|
|
assert response.json()["detail"] == expected_detail
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"payload,expected_status,expected_detail",
|
|
[
|
|
(
|
|
"conflict_booking_update_data",
|
|
BOOKING_CONFLICT_STATUS,
|
|
BOOKING_CONFLICT_DETAIL,
|
|
),
|
|
],
|
|
)
|
|
async def test_booking_update_conflict(
|
|
client: AsyncClient,
|
|
sample_room: Room,
|
|
sample_bookings: list[Booking],
|
|
conflict_booking_update_data: BookingUpdateData,
|
|
payload: str,
|
|
expected_status: int,
|
|
expected_detail: str | None,
|
|
) -> None:
|
|
"""Test booking update conflict scenario (PATCH).
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked Room object.
|
|
sample_bookings: The mocked list of Bookings.
|
|
conflict_booking_update_data: The mocked conflicting booking update data dictionary.
|
|
payload: The request payload.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response JSON 'detail' matches expected_detail if provided.
|
|
"""
|
|
|
|
async def mock_update_booking(
|
|
session: AsyncSession,
|
|
booking_id: int,
|
|
event_publisher: Any = None,
|
|
**kwargs: Any,
|
|
) -> Booking:
|
|
raise ValueError(BOOKING_CONFLICT_DETAIL)
|
|
|
|
async def mock_get_booking(session: AsyncSession, booking_id: int) -> Booking:
|
|
return sample_bookings[booking_id]
|
|
|
|
async def noop(*args: Any, **kwargs: Any) -> None:
|
|
return None
|
|
|
|
dummy_session = AsyncMock(spec=AsyncSession)
|
|
dummy_session.add_all = noop
|
|
dummy_session.commit = noop
|
|
app.dependency_overrides[update_booking_service] = lambda: mock_update_booking
|
|
app.dependency_overrides[get_booking_service] = lambda: mock_get_booking
|
|
app.dependency_overrides[get_session] = lambda: dummy_session
|
|
app.dependency_overrides[get_room_service] = lambda: (
|
|
lambda *a: sample_room # pyright: ignore
|
|
)
|
|
app.dependency_overrides[publish_room_availability_event] = lambda *a: None
|
|
|
|
booking_id = sample_bookings[0].id
|
|
payload_data = locals()[payload]
|
|
response = await client.patch(f"/bookings/{booking_id}", json=payload_data)
|
|
assert response.status_code == expected_status
|
|
if expected_detail:
|
|
assert response.json()["detail"] == expected_detail
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"mock_get_booking, booking_id, expected_status, expected_detail",
|
|
[
|
|
("success", None, BOOKING_GOOD_STATUS, None),
|
|
("not_found", BOOKING_NOT_FOUND_ID, BOOKING_NOT_FOUND_STATUS, "not found"),
|
|
],
|
|
)
|
|
async def test_read_booking_parametrized(
|
|
client: AsyncClient,
|
|
sample_booking: Booking,
|
|
sample_room: Room,
|
|
mock_get_booking: str,
|
|
booking_id: int | None,
|
|
expected_status: int,
|
|
expected_detail: str | None,
|
|
) -> None:
|
|
"""Test reading a booking by ID using parametrization.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_booking: A sample booking to be returned on success.
|
|
sample_room: A sample room associated with the booking.
|
|
mock_get_booking: The scenario for mocking get_booking_service
|
|
("success" or "not_found").
|
|
booking_id: The ID of the booking to retrieve (used for not_found case).
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message, if any.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- On success, response JSON matches sample booking fields (id, room_id, invitees, room).
|
|
- On error, response JSON 'detail' matches expected_detail.
|
|
"""
|
|
if mock_get_booking == "success":
|
|
|
|
async def mock_get_booking_success(session: AsyncSession, bid: int) -> Booking:
|
|
return sample_booking
|
|
|
|
async def mock_get_room(*a: Any, **kw: Any) -> Room:
|
|
return sample_room
|
|
|
|
app.dependency_overrides[get_booking_service] = lambda: mock_get_booking_success
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room
|
|
bid = sample_booking.id
|
|
else:
|
|
|
|
async def mock_get_booking_not_found(
|
|
session: AsyncSession, bid: int
|
|
) -> Booking:
|
|
raise NoResultFound
|
|
|
|
async def mock_get_room_none(*a: Any, **kw: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_booking_service] = (
|
|
lambda: mock_get_booking_not_found
|
|
)
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
bid = booking_id if booking_id is not None else 0
|
|
response = await client.get(f"/bookings/{bid}")
|
|
assert response.status_code == expected_status
|
|
if expected_status == BOOKING_GOOD_STATUS:
|
|
data = response.json()
|
|
assert data["id"] == sample_booking.id
|
|
assert data["room_id"] == sample_booking.room_id
|
|
assert "invitees" in data
|
|
assert "room" in data
|
|
if expected_detail:
|
|
assert expected_detail in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"mock_case, expected_status, expected_detail",
|
|
[
|
|
("success", BOOKING_GOOD_STATUS, None),
|
|
("error", BOOKING_DB_ERROR_STATUS, "error"),
|
|
],
|
|
)
|
|
async def test_read_bookings_for_month_parametrized(
|
|
client: AsyncClient,
|
|
sample_bookings: list[Booking],
|
|
sample_room: Room,
|
|
mock_case: str,
|
|
expected_status: int,
|
|
expected_detail: str | None,
|
|
) -> None:
|
|
"""Test reading bookings for a specific month using parametrization.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_bookings: A list of sample bookings to be returned on success.
|
|
sample_room: A sample room associated with the bookings.
|
|
mock_case: The scenario for mocking get_bookings_for_month_service
|
|
("success" or "error").
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message, if any.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- On success, response JSON matches sample bookings for the month.
|
|
- On error, response JSON 'detail' matches expected_detail.
|
|
"""
|
|
month = datetime.now().strftime("%Y-%m")
|
|
if mock_case == "success":
|
|
|
|
async def mock_get_bookings_for_month_success(
|
|
session: AsyncSession, m: str
|
|
) -> list[Booking]:
|
|
return list(sample_bookings)
|
|
|
|
async def mock_get_room(*a: Any, **kw: Any) -> Room:
|
|
return sample_room
|
|
|
|
app.dependency_overrides[get_bookings_for_month_service] = (
|
|
lambda: mock_get_bookings_for_month_success
|
|
)
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room
|
|
else:
|
|
|
|
async def mock_get_bookings_for_month_error(
|
|
session: AsyncSession, m: str
|
|
) -> list[Booking]:
|
|
raise Exception("DB error")
|
|
|
|
async def mock_get_room_none(*a: Any, **kw: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_bookings_for_month_service] = (
|
|
lambda: mock_get_bookings_for_month_error
|
|
)
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
response = await client.get(f"/bookings/month/{month}")
|
|
assert response.status_code == expected_status
|
|
if expected_status == BOOKING_GOOD_STATUS:
|
|
data: list[BookingData] = response.json()
|
|
assert isinstance(data, list)
|
|
assert len(data) == len(sample_bookings)
|
|
for booking_json, booking in zip(data, sample_bookings, strict=True):
|
|
assert booking_json["id"] == booking.id
|
|
assert booking_json["room_id"] == booking.room_id
|
|
assert "invitees" in booking_json
|
|
assert "room" in booking_json
|
|
if expected_detail:
|
|
assert expected_detail in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"invalid_payload,expected_detail",
|
|
[
|
|
("sample_booking_data", BOOKING_VALIDATION_MISSING_FIELD),
|
|
("sample_booking_data", BOOKING_VALIDATION_WRONG_TYPE),
|
|
],
|
|
)
|
|
async def test_create_booking_validation_error_parametrized(
|
|
client: AsyncClient,
|
|
sample_room: Room,
|
|
sample_booking_data: BookingCreateData,
|
|
invalid_payload: str,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test booking creation with invalid payloads using parametrization.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: A sample room to be used for booking creation.
|
|
sample_booking_data: Valid booking creation data.
|
|
invalid_payload: The name of the variable containing the invalid payload data.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code is BOOKING_VALIDATION_ERROR_STATUS.
|
|
- Response JSON 'detail' contains expected_detail (case-insensitive).
|
|
"""
|
|
|
|
async def mock_new_booking(
|
|
session: AsyncSession, booking: Any, event_publisher: Any = None
|
|
) -> dict[str, Any]:
|
|
raise ValueError(expected_detail)
|
|
|
|
app.dependency_overrides[new_booking_service] = lambda: mock_new_booking
|
|
app.dependency_overrides[get_room_service] = lambda: (
|
|
lambda *a: sample_room # pyright: ignore
|
|
)
|
|
app.dependency_overrides[publish_room_availability_event] = lambda *a: None
|
|
payload_data = locals()[invalid_payload]
|
|
response = await client.post("/bookings/", json=payload_data)
|
|
assert response.status_code == BOOKING_VALIDATION_ERROR_STATUS
|
|
assert expected_detail.lower() in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(NoResultFound(), BOOKING_NOT_FOUND_STATUS, "Booking with id 42 not found"),
|
|
(SQLAlchemyError(), BOOKING_DB_ERROR_STATUS, BOOKING_DB_ERROR_DETAIL),
|
|
(
|
|
Exception("fail"),
|
|
BOOKING_DB_ERROR_STATUS,
|
|
"Unexpected error while fetching booking",
|
|
),
|
|
],
|
|
)
|
|
async def test_read_booking_error_branches(
|
|
client: AsyncClient,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test GET /bookings/{booking_id} error branches for full coverage.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
exception: The exception to raise in the dependency override.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response detail matches expected_detail.
|
|
"""
|
|
booking_id = 42
|
|
|
|
async def mock_get_booking(*args: Any, **kwargs: Any) -> Booking:
|
|
raise exception
|
|
|
|
app.dependency_overrides[get_booking_service] = lambda: (
|
|
mock_get_booking # pyright: ignore
|
|
)
|
|
app.dependency_overrides[get_room_service] = lambda: (
|
|
lambda *a, **kw: None # pyright: ignore
|
|
)
|
|
response = await client.get(f"/bookings/{booking_id}")
|
|
assert response.status_code == expected_status
|
|
if "unexpected error" in expected_detail.lower():
|
|
assert "an unexpected error occurred" in response.text.lower()
|
|
else:
|
|
assert expected_detail.lower() in response.text.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(ValueError("overlap error"), BOOKING_CONFLICT_STATUS, BOOKING_CONFLICT_DETAIL),
|
|
(ValueError("other error"), BOOKING_VALIDATION_ERROR_STATUS, "other error"),
|
|
(SQLAlchemyError(), BOOKING_DB_ERROR_STATUS, "Internal server error"),
|
|
(
|
|
Exception("fail"),
|
|
BOOKING_DB_ERROR_STATUS,
|
|
"Unexpected error while creating booking",
|
|
),
|
|
],
|
|
)
|
|
async def test_create_booking_error_branches(
|
|
client: AsyncClient,
|
|
booking_create: BookingCreateData,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test POST /bookings/ error branches for full coverage.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
booking_create: The booking data to post.
|
|
exception: The exception to raise in the dependency override.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response detail matches expected_detail.
|
|
"""
|
|
|
|
async def mock_new_booking(*args: Any, **kwargs: Any) -> Booking:
|
|
raise exception
|
|
|
|
app.dependency_overrides[new_booking_service] = lambda: mock_new_booking
|
|
|
|
def mock_get_room_none(*args: Any, **kwargs: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
if hasattr(booking_create, "model_dump"):
|
|
payload = booking_create.model_dump(mode="json") # type: ignore [attr-defined]
|
|
elif hasattr(booking_create, "dict"):
|
|
payload = booking_create.dict() # type: ignore [attr-defined]
|
|
else:
|
|
payload = dict(booking_create)
|
|
response = await client.post("/bookings/", json=payload) # pyright: ignore
|
|
assert response.status_code == expected_status
|
|
if "unexpected error" in expected_detail.lower():
|
|
assert "an unexpected error occurred" in response.text.lower()
|
|
else:
|
|
assert expected_detail.lower() in response.text.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(NoResultFound(), BOOKING_NOT_FOUND_STATUS, BOOKING_NOT_FOUND_DETAIL),
|
|
(SQLAlchemyError(), BOOKING_DB_ERROR_STATUS, "Database error"),
|
|
(ValueError("overlap error"), BOOKING_CONFLICT_STATUS, BOOKING_CONFLICT_DETAIL),
|
|
(ValueError("other error"), BOOKING_CONFLICT_STATUS, "other error"),
|
|
(
|
|
Exception("fail"),
|
|
BOOKING_DB_ERROR_STATUS,
|
|
"Unexpected error while updating booking",
|
|
),
|
|
],
|
|
)
|
|
async def test_update_booking_error_branches(
|
|
client: AsyncClient,
|
|
booking_update_data: BookingUpdateData,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test PATCH /bookings/{booking_id} error branches for full coverage.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
booking_update_data: The booking update data to patch.
|
|
exception: The exception to raise in the dependency override.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response detail matches expected_detail.
|
|
"""
|
|
booking_id = 42
|
|
|
|
async def mock_update_booking(*args: Any, **kwargs: Any) -> Booking:
|
|
raise exception
|
|
|
|
app.dependency_overrides[update_booking_service] = lambda: mock_update_booking
|
|
|
|
def mock_get_room_none(*args: Any, **kwargs: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[get_room_service] = lambda: mock_get_room_none
|
|
response = await client.patch(f"/bookings/{booking_id}", json=booking_update_data)
|
|
assert response.status_code == expected_status
|
|
if "unexpected error" in expected_detail.lower():
|
|
assert "an unexpected error occurred" in response.text.lower()
|
|
else:
|
|
assert expected_detail.lower() in response.text.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(NoResultFound(), BOOKING_NOT_FOUND_STATUS, BOOKING_NOT_FOUND_DETAIL),
|
|
(SQLAlchemyError(), BOOKING_DB_ERROR_STATUS, "Database error"),
|
|
(
|
|
ValueError("not found"),
|
|
BOOKING_NOT_FOUND_STATUS,
|
|
"Booking with id 42 not found",
|
|
),
|
|
(
|
|
Exception("fail"),
|
|
BOOKING_DB_ERROR_STATUS,
|
|
"Unexpected error while deleting booking",
|
|
),
|
|
],
|
|
)
|
|
async def test_delete_booking_error_branches(
|
|
client: AsyncClient,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test DELETE /bookings/{booking_id} error branches for full coverage.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
exception: The exception to raise in the dependency override.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response detail matches expected_detail.
|
|
"""
|
|
booking_id = 42
|
|
|
|
async def mock_delete_booking(*args: Any, **kwargs: Any) -> None:
|
|
raise exception
|
|
|
|
app.dependency_overrides[delete_booking_service] = lambda: mock_delete_booking
|
|
response = await client.delete(f"/bookings/{booking_id}")
|
|
assert response.status_code == expected_status
|
|
if "unexpected error" in expected_detail.lower():
|
|
assert "an unexpected error occurred" in response.text.lower()
|
|
else:
|
|
assert expected_detail.lower() in response.text.lower()
|