Files
conference-room-booking-system/backend/tests/routers/test_invitees.py
2025-08-27 15:51:05 -04:00

226 lines
8.1 KiB
Python

"""Unit tests for the backend.routers.invitees module."""
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 SQLAlchemyError
from backend.models import Booking
from backend.models import Invitee
from backend.models import InviteeList
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_get_invitees_for_booking_success(
client: AsyncClient,
sample_booking: Booking,
sample_invitees: InviteeList,
mock_logger: MagicMock,
) -> None:
"""Test successful retrieval of all invitees for a booking.
Via GET /invitees/booking/{booking_id}. Verifies that the endpoint returns the
expected list of users with a 200 status.
Args:
client: The FastAPI test client for making HTTP requests.
sample_booking: The mocked booking object.
sample_invitees: The mocked list of invitees to return.
mock_logger: The mocked logger instance.
"""
with patch(
"backend.routers.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.return_value = sample_invitees
response = await client.get(f"/invitees/booking/{sample_booking.id}")
assert response.status_code == 200
assert response.json() == [
{
"id": invitee.id,
"booking_id": invitee.booking_id,
"user_email": invitee.user_email,
"user": {"name": invitee.user.name, "email": invitee.user.email},
}
for invitee in sample_invitees
]
mock_get_invitees.assert_called_once_with(ANY, sample_booking.id)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_get_invitees_for_booking_empty(
client: AsyncClient, mock_logger: MagicMock
) -> None:
"""Test retrieval of invitees when none exist for a booking.
Via GET /invitees/booking/{booking_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.
"""
booking_id = 1
with patch(
"backend.routers.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.return_value = []
response = await client.get(f"/invitees/booking/{booking_id}")
assert response.status_code == 200
assert response.json() == []
mock_get_invitees.assert_called_once_with(ANY, booking_id)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_get_invitees_for_booking_database_error(
client: AsyncClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /invitees/booking/{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.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.side_effect = SQLAlchemyError()
response = await client.get(f"/invitees/booking/{booking_id}")
assert response.status_code == 500
assert "detail" in response.json()
mock_get_invitees.assert_called_once_with(ANY, booking_id)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_add_invitee_success(
client: AsyncClient,
sample_invitee: Invitee,
mock_logger: MagicMock,
) -> None:
"""Test successful addition of an invitee to a booking.
Via POST /invitees/booking/{booking_id}. Verifies that the endpoint returns the
created invitee with a 201 status.
Args:
client: The FastAPI test client for making HTTP requests.
sample_invitee: The mocked invitee object.
mock_logger: The mocked logger instance.
"""
invitee_data: dict[str, Any] = {
"booking_id": sample_invitee.booking_id,
"user_email": sample_invitee.user_email,
}
with patch(
"backend.routers.invitees.add_invitee_to_booking", new=AsyncMock()
) as mock_add_invitee:
mock_add_invitee.return_value = sample_invitee
response = await client.post(
f"/invitees/booking/{sample_invitee.booking_id}", json=invitee_data
)
assert response.status_code == 201
result = response.json()
assert result["booking_id"] == sample_invitee.booking_id
assert result["user_email"] == sample_invitee.user_email
mock_add_invitee.assert_called_once_with(
ANY, sample_invitee.booking_id, sample_invitee.user_email
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_add_invitee_database_error(
client: AsyncClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in POST /invitees/booking/{booking_id}.
Verifies that the endpoint returns a 400 status on database failure.
Args:
client: The FastAPI test client for making HTTP requests.
mock_logger: The mocked logger instance.
"""
booking_id = 1
invitee_data: dict[str, Any] = {
"booking_id": booking_id,
"user_email": "user1@example.com",
}
with patch(
"backend.routers.invitees.add_invitee_to_booking", new=AsyncMock()
) as mock_add_invitee:
mock_add_invitee.side_effect = SQLAlchemyError()
response = await client.post(
f"/invitees/booking/{booking_id}", json=invitee_data
)
assert response.status_code == 400
assert "detail" in response.json()
mock_add_invitee.assert_called_once_with(
ANY, booking_id, invitee_data["user_email"]
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_remove_invitee_success(
client: AsyncClient, mock_logger: MagicMock
) -> None:
"""Test successful removal of an invitee from a booking.
Via DELETE /invitees/booking/{booking_id}/user/{user_email}. 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
user_email = "user1@example.com"
with patch(
"backend.routers.invitees.remove_invitee_from_booking", new=AsyncMock()
) as mock_remove_invitee:
response = await client.delete(
f"/invitees/booking/{booking_id}/user/{user_email}"
)
assert response.status_code == 204
assert response.text == ""
mock_remove_invitee.assert_called_once_with(ANY, booking_id, user_email)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_remove_invitee_database_error(
client: AsyncClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors for delete.
Via DELETE /invitees/booking/{booking_id}/user/{user_email}. 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
user_email = "user1@example.com"
with patch(
"backend.routers.invitees.remove_invitee_from_booking", new=AsyncMock()
) as mock_remove_invitee:
mock_remove_invitee.side_effect = SQLAlchemyError()
response = await client.delete(
f"/invitees/booking/{booking_id}/user/{user_email}"
)
assert response.status_code == 500
assert "detail" in response.json()
mock_remove_invitee.assert_called_once_with(ANY, booking_id, user_email)