Files
conference-room-booking-system/backend/tests/services/test_invitees.py
Cliff Hill 1d29e0bbd1 Fixing stuff.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
2025-08-27 12:16:24 -04:00

268 lines
9.8 KiB
Python

"""Unit tests for the backend.services.invitees module."""
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy import delete
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Invitee
from backend.models import InviteeList
from backend.services.invitees import add_invitee_to_booking
from backend.services.invitees import get_invitees_for_booking
from backend.services.invitees import remove_invitee_from_booking
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_get_invitees_for_booking_success(
async_session: AsyncSession, sample_invitees: InviteeList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all invitees for a booking.
Verifies that get_invitees_for_booking returns the expected list of users and constructs
the correct SQLAlchemy query.
Args:
async_session: The asynchronous database session.
sample_invitees: The mocked list of invitees to return.
mock_logger: The mocked logger instance.
"""
booking_id = 1
mock_scalars_result = AsyncMock()
mock_scalars_result.all = MagicMock(return_value=sample_invitees)
mock_scalars = AsyncMock(return_value=mock_scalars_result)
with patch.object(async_session, "scalars", mock_scalars):
result = await get_invitees_for_booking(async_session, booking_id)
assert isinstance(result, list)
assert len(result) == 2
assert result == sample_invitees
mock_scalars.assert_called_once()
assert mock_scalars.call_args.args[0].compare(
select(Invitee).where(Invitee.booking_id == booking_id)
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_get_invitees_for_booking_empty(
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test retrieval of invitees when none exist for the booking.
Verifies that get_invitees_for_booking returns an empty list when no invitees are found.
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
"""
booking_id = 1
mock_scalars_result = AsyncMock()
mock_scalars_result.all = AsyncMock(return_value=[])
mock_scalars = AsyncMock(return_value=mock_scalars_result)
with patch.object(async_session, "scalars", mock_scalars):
result = await get_invitees_for_booking(async_session, booking_id)
assert isinstance(result, list)
assert len(result) == 0
mock_scalars.assert_called_once()
assert mock_scalars.call_args.args[0].compare(
select(Invitee).where(Invitee.booking_id == booking_id)
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_get_invitees_for_booking_database_error(
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_invitees_for_booking.
Verifies that get_invitees_for_booking raises an exception on database failure.
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
"""
booking_id = 1
mock_scalars = AsyncMock(side_effect=SQLAlchemyError("Database error"))
with patch.object(async_session, "scalars", mock_scalars):
with pytest.raises(SQLAlchemyError):
await get_invitees_for_booking(async_session, booking_id)
mock_scalars.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_add_invitee_to_booking_success(
async_session: AsyncSession,
sample_invitee: Invitee,
mock_logger: MagicMock,
) -> None:
"""Test successful addition of an invitee to a booking.
Verifies that add_invitee_to_booking adds the invitee, commits the session,
and returns the invitee.
Args:
async_session: The asynchronous database session.
sample_invitee: The mocked invitee object.
mock_logger: The mocked logger instance.
"""
mock_scalars_result = AsyncMock()
mock_scalars_result.first = MagicMock(return_value=None)
mock_scalars_result.all = MagicMock(return_value=[])
with (
patch.object(async_session, "add", new_callable=MagicMock) as mock_add,
patch.object(async_session, "commit", new_callable=AsyncMock) as mock_commit,
patch.object(
async_session, "scalars", AsyncMock(return_value=mock_scalars_result)
),
patch(
"backend.services.bookings.get_booking",
new=AsyncMock(return_value=MagicMock(room_id=1)),
),
patch(
"backend.services.rooms.get_room",
new=AsyncMock(return_value=MagicMock(capacity=10)),
),
patch(
"backend.services.invitees.get_invitees_for_booking",
new=AsyncMock(return_value=[]),
),
):
result = await add_invitee_to_booking(
async_session, sample_invitee.booking_id, sample_invitee.user_email
)
assert result.booking_id == sample_invitee.booking_id
assert result.user_email == sample_invitee.user_email
mock_add.assert_called_once()
mock_commit.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_add_invitee_to_booking_database_error(
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in add_invitee_to_booking.
Verifies that add_invitee_to_booking rolls back the session on database failure.
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
"""
booking_id = 1
user_email = "user1@example.com"
invitee = Invitee(booking_id=booking_id, user_email=user_email)
mock_scalars_result = AsyncMock()
mock_scalars_result.first = MagicMock(return_value=None)
mock_scalars_result.all = MagicMock(return_value=[])
with (
patch.object(async_session, "add", MagicMock()) as mock_add,
patch.object(
async_session,
"commit",
AsyncMock(side_effect=SQLAlchemyError("Database error")),
) as mock_commit,
patch.object(async_session, "rollback", AsyncMock()) as mock_rollback,
patch.object(
async_session, "scalars", AsyncMock(return_value=mock_scalars_result)
),
patch(
"backend.services.bookings.get_booking",
new=AsyncMock(return_value=MagicMock(room_id=1)),
),
patch(
"backend.services.rooms.get_room",
new=AsyncMock(return_value=MagicMock(capacity=10)),
),
patch(
"backend.services.invitees.get_invitees_for_booking",
new=AsyncMock(return_value=[]),
),
):
with pytest.raises(SQLAlchemyError):
await add_invitee_to_booking(async_session, booking_id, user_email)
mock_add.assert_called_once_with(invitee)
mock_commit.assert_called_once()
mock_rollback.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
@pytest.mark.parametrize(
"booking_id, user_email",
[
(1, "user1@example.com"),
(2, "user2@example.com"),
],
ids=["invitee1", "invitee2"],
)
async def test_remove_invitee_from_booking_success(
async_session: AsyncSession,
booking_id: int,
user_email: str,
mock_logger: MagicMock,
) -> None:
"""Test successful removal of an invitee from a booking.
Verifies that remove_invitee_from_booking deletes the invitee and commits the session.
Args:
async_session: The asynchronous database session.
booking_id: The ID of the booking to remove the invitee from.
user_email: The email of the user to remove as an invitee.
mock_logger: The mocked logger instance.
"""
mock_execute_result = MagicMock(rowcount=1)
async_session.execute = AsyncMock( # type: ignore [method-assign]
return_value=mock_execute_result
)
async_session.commit = AsyncMock() # type: ignore [method-assign]
await remove_invitee_from_booking(async_session, booking_id, user_email)
async_session.execute.assert_called_once()
assert async_session.execute.call_args.args[0].compare(
delete(Invitee).where(
Invitee.booking_id == booking_id, Invitee.user_email == user_email
)
)
async_session.commit.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
async def test_remove_invitee_from_booking_database_error(
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in remove_invitee_from_booking.
Verifies that remove_invitee_from_booking rolls back the session on database failure.
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
"""
booking_id = 1
user_email = "user1@example.com"
async_session.execute = AsyncMock( # type: ignore [method-assign]
side_effect=SQLAlchemyError("Database error")
)
async_session.rollback = AsyncMock() # type: ignore [method-assign]
with pytest.raises(SQLAlchemyError):
await remove_invitee_from_booking(async_session, booking_id, user_email)
async_session.execute.assert_called_once()
assert async_session.execute.call_args.args[0].compare(
delete(Invitee).where(
Invitee.booking_id == booking_id, Invitee.user_email == user_email
)
)
async_session.rollback.assert_called_once()