mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-06 22:28:25 -04:00
457 lines
16 KiB
Python
457 lines
16 KiB
Python
"""Unit tests for the backend.routers.bookings module."""
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from unittest.mock import ANY
|
|
from unittest.mock import AsyncMock
|
|
from unittest.mock import MagicMock
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
from sqlalchemy.exc import NoResultFound
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from backend.models import Booking
|
|
from backend.models import BookingList
|
|
from backend.models import Room
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_bookings_for_room_success(
|
|
client: AsyncClient, sample_bookings: BookingList, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful retrieval of all bookings for a room via GET /bookings/room/{room_id}.
|
|
|
|
Verifies that the endpoint returns the expected list of bookings with a 200 status.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_bookings: The mocked list of bookings to return.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
room_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.get_bookings_for_room", new=AsyncMock()
|
|
) as mock_get_bookings:
|
|
mock_get_bookings.return_value = sample_bookings
|
|
|
|
response = await client.get(f"/bookings/room/{room_id}")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == [
|
|
{
|
|
"id": booking.id,
|
|
"room_id": booking.room_id,
|
|
"start_time": booking.start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
|
"end_time": booking.end_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
|
"title": booking.title,
|
|
}
|
|
for booking in sample_bookings
|
|
]
|
|
mock_get_bookings.assert_called_once_with(ANY, room_id, None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_bookings_for_room_empty(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test retrieval of bookings when none exist for a room via GET /bookings/room/{room_id}.
|
|
|
|
Verifies that the endpoint returns an empty list with a 200 status.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
room_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.get_bookings_for_room", new=AsyncMock()
|
|
) as mock_get_bookings:
|
|
mock_get_bookings.return_value = []
|
|
|
|
response = await client.get(f"/bookings/room/{room_id}")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == []
|
|
mock_get_bookings.assert_called_once_with(ANY, room_id, None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_bookings_for_room_database_error(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in GET /bookings/room/{room_id}.
|
|
|
|
Verifies that the endpoint returns a 500 status on database failure.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
room_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.get_bookings_for_room", new=AsyncMock()
|
|
) as mock_get_bookings:
|
|
mock_get_bookings.side_effect = SQLAlchemyError()
|
|
|
|
response = await client.get(f"/bookings/room/{room_id}")
|
|
|
|
assert response.status_code == 500
|
|
assert "detail" in response.json()
|
|
mock_get_bookings.assert_called_once_with(ANY, room_id, None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_booking_success(
|
|
client: AsyncClient, sample_room: Room, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful retrieval of a booking by ID via GET /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns the correct booking with a 200 status.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked room object.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
booking = Booking(
|
|
room_id=1,
|
|
start_time=datetime.fromisoformat("2025-08-25T10:00:00"),
|
|
end_time=datetime.fromisoformat("2025-08-25T11:00:00"),
|
|
)
|
|
booking.id = booking_id
|
|
booking.room = sample_room # Attach room relationship
|
|
with patch(
|
|
"backend.routers.bookings.get_booking", new=AsyncMock()
|
|
) as mock_get_booking:
|
|
mock_get_booking.return_value = booking
|
|
|
|
response = await client.get(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {
|
|
"id": booking_id,
|
|
"room_id": 1,
|
|
"start_time": "2025-08-25T10:00:00",
|
|
"end_time": "2025-08-25T11:00:00",
|
|
"title": None,
|
|
}
|
|
mock_get_booking.assert_called_once_with(ANY, booking_id)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_booking_not_found(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of non-existent booking in GET /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 404 status when the booking is not found.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 999
|
|
with patch(
|
|
"backend.routers.bookings.get_booking", new=AsyncMock()
|
|
) as mock_get_booking:
|
|
mock_get_booking.side_effect = NoResultFound()
|
|
|
|
response = await client.get(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 404
|
|
assert "detail" in response.json()
|
|
mock_get_booking.assert_called_once_with(ANY, booking_id)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_get_booking_database_error(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in GET /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 500 status on database failure.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.get_booking", new=AsyncMock()
|
|
) as mock_get_booking:
|
|
mock_get_booking.side_effect = SQLAlchemyError()
|
|
|
|
response = await client.get(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 500
|
|
assert "detail" in response.json()
|
|
mock_get_booking.assert_called_once_with(ANY, booking_id)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_create_booking_success(
|
|
client: AsyncClient, sample_room: Room, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful creation of a new booking via POST /bookings/.
|
|
|
|
Verifies that the endpoint returns the created booking with a 201 status.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked room object.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_data: dict[str, Any] = {
|
|
"room_id": 1,
|
|
"start_time": "2025-08-25T10:00:00",
|
|
"end_time": "2025-08-25T11:00:00",
|
|
"title": "Test Booking Title",
|
|
}
|
|
created_booking = Booking(**booking_data)
|
|
created_booking.id = 3
|
|
created_booking.room = sample_room # Attach room relationship
|
|
with patch(
|
|
"backend.routers.bookings.new_booking", new=AsyncMock()
|
|
) as mock_new_booking:
|
|
mock_new_booking.return_value = created_booking
|
|
|
|
response = await client.post("/bookings/", json=booking_data)
|
|
|
|
assert response.status_code == 201
|
|
assert response.json() == {
|
|
"id": 3,
|
|
"room_id": 1,
|
|
"start_time": "2025-08-25T10:00:00",
|
|
"end_time": "2025-08-25T11:00:00",
|
|
"title": "Test Booking Title",
|
|
}
|
|
mock_new_booking.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_create_booking_database_error(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in POST /bookings/.
|
|
|
|
Verifies that the endpoint returns a 500 status on database failure.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_data: dict[str, Any] = {
|
|
"room_id": 1,
|
|
"start_time": "2025-08-25T10:00:00",
|
|
"end_time": "2025-08-25T11:00:00",
|
|
}
|
|
with patch(
|
|
"backend.routers.bookings.new_booking", new=AsyncMock()
|
|
) as mock_new_booking:
|
|
mock_new_booking.side_effect = SQLAlchemyError()
|
|
|
|
response = await client.post("/bookings/", json=booking_data)
|
|
|
|
assert response.status_code == 500
|
|
assert "detail" in response.json()
|
|
mock_new_booking.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_update_booking_success(
|
|
client: AsyncClient, sample_room: Room, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful update of a booking via PUT /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns the updated booking with a 200 status.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked room object.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
update_data: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": "2025-08-25T14:00:00",
|
|
"end_time": "2025-08-25T15:00:00",
|
|
"title": "Updated Test Booking Title",
|
|
}
|
|
updated_booking = Booking(**update_data)
|
|
updated_booking.id = booking_id
|
|
updated_booking.room = sample_room # Attach room relationship
|
|
with patch(
|
|
"backend.routers.bookings.update_booking", new=AsyncMock()
|
|
) as mock_update_booking:
|
|
mock_update_booking.return_value = updated_booking
|
|
|
|
response = await client.put(f"/bookings/{booking_id}", json=update_data)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {
|
|
"id": booking_id,
|
|
"room_id": 2,
|
|
"start_time": "2025-08-25T14:00:00",
|
|
"end_time": "2025-08-25T15:00:00",
|
|
"title": "Updated Test Booking Title",
|
|
}
|
|
update_data["start_time"] = datetime.fromisoformat(update_data["start_time"])
|
|
update_data["end_time"] = datetime.fromisoformat(update_data["end_time"])
|
|
mock_update_booking.assert_called_once_with(ANY, booking_id, ANY, **update_data)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_update_booking_not_found(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of non-existent booking in PUT /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 404 status when the booking is not found.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 999
|
|
update_data: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": "2025-08-25T14:00:00",
|
|
"end_time": "2025-08-25T15:00:00",
|
|
"title": "Updated Test Booking Title",
|
|
}
|
|
with patch(
|
|
"backend.routers.bookings.update_booking", new=AsyncMock()
|
|
) as mock_update_booking:
|
|
mock_update_booking.side_effect = NoResultFound()
|
|
|
|
response = await client.put(f"/bookings/{booking_id}", json=update_data)
|
|
|
|
assert response.status_code == 404
|
|
assert "detail" in response.json()
|
|
update_data["start_time"] = datetime.fromisoformat(update_data["start_time"])
|
|
update_data["end_time"] = datetime.fromisoformat(update_data["end_time"])
|
|
mock_update_booking.assert_called_once_with(ANY, booking_id, ANY, **update_data)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_update_booking_database_error(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in PUT /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 500 status on database failure.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
update_data: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": "2025-08-25T14:00:00",
|
|
"end_time": "2025-08-25T15:00:00",
|
|
"title": "Updated Test Booking Title",
|
|
}
|
|
with patch(
|
|
"backend.routers.bookings.update_booking", new=AsyncMock()
|
|
) as mock_update_booking:
|
|
mock_update_booking.side_effect = SQLAlchemyError()
|
|
|
|
response = await client.put(f"/bookings/{booking_id}", json=update_data)
|
|
|
|
assert response.status_code == 500
|
|
assert "detail" in response.json()
|
|
update_data["start_time"] = datetime.fromisoformat(update_data["start_time"])
|
|
update_data["end_time"] = datetime.fromisoformat(update_data["end_time"])
|
|
mock_update_booking.assert_called_once_with(ANY, booking_id, ANY, **update_data)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_delete_booking_success(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful deletion of a booking via DELETE /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 204 status on successful deletion.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.delete_booking", new=AsyncMock()
|
|
) as mock_delete_booking:
|
|
response = await client.delete(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 204
|
|
assert response.text == ""
|
|
mock_delete_booking.assert_called_once_with(ANY, booking_id, ANY)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_delete_booking_not_found(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of non-existent booking in DELETE /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 404 status when the booking is not found.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 999
|
|
with patch(
|
|
"backend.routers.bookings.delete_booking", new=AsyncMock()
|
|
) as mock_delete_booking:
|
|
mock_delete_booking.side_effect = NoResultFound()
|
|
|
|
response = await client.delete(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 404
|
|
assert "detail" in response.json()
|
|
mock_delete_booking.assert_called_once_with(ANY, booking_id, ANY)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
|
|
async def test_delete_booking_database_error(
|
|
client: AsyncClient, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in DELETE /bookings/{booking_id}.
|
|
|
|
Verifies that the endpoint returns a 500 status on database failure.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
mock_logger: The mocked logger instance.
|
|
"""
|
|
booking_id = 1
|
|
with patch(
|
|
"backend.routers.bookings.delete_booking", new=AsyncMock()
|
|
) as mock_delete_booking:
|
|
mock_delete_booking.side_effect = SQLAlchemyError()
|
|
|
|
response = await client.delete(f"/bookings/{booking_id}")
|
|
|
|
assert response.status_code == 500
|
|
assert "detail" in response.json()
|
|
mock_delete_booking.assert_called_once_with(ANY, booking_id, ANY)
|