mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-10 01:55:48 -04:00
341 lines
11 KiB
Python
341 lines
11 KiB
Python
"""Unit tests for the backend.routers.rooms module."""
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
from sqlalchemy.exc import NoResultFound
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from backend.dependencies.rooms import delete_room_service
|
|
from backend.dependencies.rooms import get_rooms_service
|
|
from backend.dependencies.rooms import new_room_service
|
|
from backend.dependencies.rooms import update_room_service
|
|
from backend.main import app
|
|
from backend.models import Room
|
|
from backend.models import RoomList
|
|
from tests.types import RoomData
|
|
from tests.types import RoomUpdateData
|
|
|
|
|
|
# Useful constants for rooms router tests
|
|
ROOM_SUCCESS_STATUS = 200
|
|
ROOM_CREATED_STATUS = 201
|
|
ROOM_DELETED_STATUS = 204
|
|
ROOM_NOT_FOUND_STATUS = 404
|
|
BAD_REQUEST_STATUS = 400
|
|
INTERNAL_SERVER_ERROR_STATUS = 500
|
|
ROOM_DB_ERROR_DETAIL = "Database error"
|
|
ROOM_NOT_FOUND_DETAIL = "Room not found"
|
|
ROOM_UNEXPECTED_ERROR_DETAIL = "An unexpected error occurred"
|
|
ROOM_BAD_REQUEST_DETAIL = "bad request"
|
|
ROOM_NOT_FOUND_ID = 999
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("room_case", ["non_empty", "empty"])
|
|
async def test_get_rooms_parametrized_fixture(
|
|
client: AsyncClient,
|
|
sample_rooms: RoomList,
|
|
room_case: str,
|
|
) -> None:
|
|
"""Parametrized test for GET /rooms/ using fixture data only.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_rooms: The mocked list of rooms.
|
|
room_case: Indicates non-empty or empty test case.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_SUCCESS_STATUS.
|
|
- Returned rooms match expected fields (id, name, location, equipment, capacity).
|
|
"""
|
|
if room_case == "non_empty":
|
|
rooms = sample_rooms
|
|
else:
|
|
rooms = []
|
|
|
|
async def mock_get_rooms(*args: Any, **kwargs: Any) -> RoomList:
|
|
return rooms
|
|
|
|
app.dependency_overrides[get_rooms_service] = lambda: mock_get_rooms
|
|
response = await client.get("/rooms/")
|
|
assert response.status_code == ROOM_SUCCESS_STATUS
|
|
for returned_room, expected_room in zip(response.json(), rooms, strict=True):
|
|
assert returned_room["id"] == expected_room.id
|
|
assert returned_room["name"] == expected_room.name
|
|
assert returned_room["location"] == expected_room.location
|
|
assert returned_room["equipment"] == expected_room.equipment
|
|
assert returned_room["capacity"] == expected_room.capacity
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_new_room_success(
|
|
client: AsyncClient,
|
|
sample_room: Room,
|
|
sample_room_data: RoomData,
|
|
) -> None:
|
|
"""Test POST /rooms/ for successful creation.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_room: The mocked Room object.
|
|
sample_room_data: The mocked room data dictionary.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_CREATED_STATUS.
|
|
- Response JSON matches sample_room_data.
|
|
"""
|
|
|
|
async def mock_new_room(*args: Any, **kwargs: Any) -> Room:
|
|
return sample_room
|
|
|
|
app.dependency_overrides[new_room_service] = lambda: mock_new_room
|
|
response = await client.post("/rooms/", json=sample_room_data)
|
|
assert response.status_code == ROOM_CREATED_STATUS
|
|
assert response.json() == sample_room_data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_room_success(
|
|
client: AsyncClient,
|
|
sample_rooms: RoomList,
|
|
room_update_data: RoomData,
|
|
updated_room: Room,
|
|
) -> None:
|
|
"""Test PATCH /rooms/{room_id} for successful update.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_rooms: The mocked list of rooms.
|
|
room_update_data: The mocked room update data dictionary.
|
|
updated_room: The mocked updated Room object.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_SUCCESS_STATUS.
|
|
- Response JSON matches updated_room fields (id, name, location, equipment, capacity).
|
|
"""
|
|
room_id = sample_rooms[0].id
|
|
|
|
async def mock_get_rooms(*args: Any, **kwargs: Any) -> RoomList:
|
|
return sample_rooms
|
|
|
|
async def mock_update_room(*args: Any, **kwargs: Any) -> Room:
|
|
return updated_room
|
|
|
|
app.dependency_overrides[get_rooms_service] = lambda: mock_get_rooms
|
|
app.dependency_overrides[update_room_service] = lambda: mock_update_room
|
|
|
|
response = await client.patch(f"/rooms/{room_id}", json=room_update_data)
|
|
assert response.status_code == ROOM_SUCCESS_STATUS
|
|
actual = response.json()
|
|
# Remove 'bookings' if present for comparison
|
|
actual.pop("bookings", None)
|
|
# Compare all relevant fields
|
|
assert actual["id"] == updated_room.id
|
|
assert actual["name"] == updated_room.name
|
|
assert actual["location"] == updated_room.location
|
|
assert actual["equipment"] == updated_room.equipment
|
|
assert actual["capacity"] == updated_room.capacity
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_room_success(
|
|
client: AsyncClient,
|
|
) -> None:
|
|
"""Test DELETE /rooms/{room_id} for successful deletion.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_DELETED_STATUS.
|
|
- Response text is empty.
|
|
"""
|
|
room_id = 1
|
|
|
|
async def mock_delete_room(*args: Any, **kwargs: Any) -> None:
|
|
return None
|
|
|
|
app.dependency_overrides[delete_room_service] = lambda: mock_delete_room
|
|
response = await client.delete(f"/rooms/{room_id}")
|
|
assert response.status_code == ROOM_DELETED_STATUS
|
|
assert response.text == ""
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_room_not_found(
|
|
client: AsyncClient,
|
|
) -> None:
|
|
"""Test DELETE /rooms/{room_id} for not found case.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_NOT_FOUND_STATUS.
|
|
- Response JSON contains 'detail'.
|
|
"""
|
|
room_id = ROOM_NOT_FOUND_ID
|
|
|
|
async def mock_delete_room(*args: Any, **kwargs: Any) -> None:
|
|
raise ValueError()
|
|
|
|
app.dependency_overrides[delete_room_service] = lambda: mock_delete_room
|
|
response = await client.delete(f"/rooms/{room_id}")
|
|
assert response.status_code == ROOM_NOT_FOUND_STATUS
|
|
assert "detail" in response.json()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_room_availability(client: AsyncClient) -> None:
|
|
"""Test GET /rooms/availability/stream SSE endpoint for connection and response format.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
|
|
Asserts:
|
|
- Response status code is ROOM_SUCCESS_STATUS.
|
|
- Response content-type is 'text/event-stream'.
|
|
- Response body contains keep-alive or SSE data.
|
|
"""
|
|
response = await client.get("/rooms/availability/stream?test_mode=true")
|
|
assert response.status_code == ROOM_SUCCESS_STATUS
|
|
assert response.headers["content-type"].startswith("text/event-stream")
|
|
# Check for keep-alive or SSE data in the response
|
|
body = response.text
|
|
assert re.search(r"(: keep-alive|data:)", body)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(
|
|
ValueError(ROOM_BAD_REQUEST_DETAIL),
|
|
BAD_REQUEST_STATUS,
|
|
ROOM_BAD_REQUEST_DETAIL,
|
|
),
|
|
(NoResultFound(), ROOM_NOT_FOUND_STATUS, ROOM_NOT_FOUND_DETAIL),
|
|
(SQLAlchemyError(), INTERNAL_SERVER_ERROR_STATUS, ROOM_DB_ERROR_DETAIL),
|
|
(Exception("fail"), INTERNAL_SERVER_ERROR_STATUS, ROOM_UNEXPECTED_ERROR_DETAIL),
|
|
],
|
|
)
|
|
async def test_update_room_error_cases_post(
|
|
client: AsyncClient,
|
|
sample_rooms: RoomList,
|
|
room_update_data: RoomUpdateData,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test PATCH /rooms/{room_id} error scenarios.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_rooms: The mocked list of rooms.
|
|
room_update_data: The mocked room update data dictionary.
|
|
exception: The exception to raise in the dependency.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response JSON 'detail' contains expected_detail.
|
|
"""
|
|
room_id = sample_rooms[0].id
|
|
|
|
async def mock_update_room(*args: Any, **kwargs: Any) -> Room:
|
|
raise exception
|
|
|
|
app.dependency_overrides[update_room_service] = lambda: mock_update_room
|
|
response = await client.patch(f"/rooms/{room_id}", json=room_update_data)
|
|
assert response.status_code == expected_status
|
|
assert expected_detail.lower() in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(NoResultFound(), ROOM_NOT_FOUND_STATUS, ROOM_NOT_FOUND_DETAIL),
|
|
(SQLAlchemyError(), INTERNAL_SERVER_ERROR_STATUS, ROOM_DB_ERROR_DETAIL),
|
|
(Exception("fail"), INTERNAL_SERVER_ERROR_STATUS, ROOM_UNEXPECTED_ERROR_DETAIL),
|
|
],
|
|
)
|
|
async def test_update_room_error_cases(
|
|
client: AsyncClient,
|
|
sample_rooms: RoomList,
|
|
room_update_data: RoomData,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test POST /rooms/ error scenarios.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
sample_rooms: The mocked list of rooms.
|
|
room_update_data: The mocked room update data dictionary.
|
|
exception: The exception to raise in the dependency.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response JSON 'detail' contains expected_detail.
|
|
"""
|
|
room_id = sample_rooms[0].id
|
|
|
|
async def mock_update_room(*args: Any, **kwargs: Any) -> Room:
|
|
raise exception
|
|
|
|
app.dependency_overrides[update_room_service] = lambda: mock_update_room
|
|
response = await client.patch(f"/rooms/{room_id}", json=room_update_data)
|
|
assert response.status_code == expected_status
|
|
assert expected_detail.lower() in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"exception,expected_status,expected_detail",
|
|
[
|
|
(
|
|
ValueError(ROOM_NOT_FOUND_DETAIL),
|
|
ROOM_NOT_FOUND_STATUS,
|
|
ROOM_NOT_FOUND_DETAIL,
|
|
),
|
|
(NoResultFound(), ROOM_NOT_FOUND_STATUS, ROOM_NOT_FOUND_DETAIL),
|
|
(SQLAlchemyError(), INTERNAL_SERVER_ERROR_STATUS, ROOM_DB_ERROR_DETAIL),
|
|
(Exception("fail"), INTERNAL_SERVER_ERROR_STATUS, ROOM_UNEXPECTED_ERROR_DETAIL),
|
|
],
|
|
)
|
|
async def test_delete_room_error_cases(
|
|
client: AsyncClient,
|
|
exception: Exception,
|
|
expected_status: int,
|
|
expected_detail: str,
|
|
) -> None:
|
|
"""Test DELETE /rooms/{room_id} error scenarios.
|
|
|
|
Args:
|
|
client: The FastAPI test client for making HTTP requests.
|
|
exception: The exception to raise in the dependency.
|
|
expected_status: The expected HTTP status code.
|
|
expected_detail: The expected error detail message.
|
|
|
|
Asserts:
|
|
- Response status code matches expected_status.
|
|
- Response JSON 'detail' contains expected_detail.
|
|
"""
|
|
room_id = ROOM_NOT_FOUND_ID
|
|
|
|
async def mock_delete_room(*args: Any, **kwargs: Any) -> None:
|
|
raise exception
|
|
|
|
app.dependency_overrides[delete_room_service] = lambda: mock_delete_room
|
|
response = await client.delete(f"/rooms/{room_id}")
|
|
assert response.status_code == expected_status
|
|
assert expected_detail.lower() in response.json()["detail"].lower()
|