Files
conference-room-booking-system/backend/tests/services/test_invitees.py
T

280 lines
10 KiB
Python
Raw Normal View History

2025-08-24 16:27:18 -04:00
"""Unit tests for the backend.services.invitees module."""
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
2025-08-27 11:17:36 -04:00
from unittest.mock import patch
2025-08-24 16:27:18 -04:00
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
2025-08-26 17:15:56 -04:00
from backend.models import InviteeList
2025-09-03 14:58:58 -04:00
from backend.schemas.invitees import InviteeResponse
from backend.schemas.users import UserResponse
2025-08-24 16:27:18 -04:00
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
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_get_invitees_for_booking_success(
2025-08-26 17:15:56 -04:00
async_session: AsyncSession, sample_invitees: InviteeList, mock_logger: MagicMock
2025-08-24 16:27:18 -04:00
) -> 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.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
sample_invitees: The mocked list of invitees to return.
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
booking_id = 1
mock_scalars_result = AsyncMock()
mock_scalars_result.all = MagicMock(return_value=sample_invitees)
mock_scalars = AsyncMock(return_value=mock_scalars_result)
2025-08-27 12:16:24 -04:00
with patch.object(async_session, "scalars", mock_scalars):
result = await get_invitees_for_booking(async_session, booking_id)
2025-08-24 16:27:18 -04:00
assert isinstance(result, list)
assert len(result) == 2
2025-09-03 14:58:58 -04:00
# Convert sample_invitees (Invitee) to InviteeResponse for comparison
expected = [
InviteeResponse.model_validate(
{
**inv.__dict__,
"user": UserResponse.model_validate(inv.user) if inv.user else None,
},
)
for inv in sample_invitees
]
assert result == expected
2025-08-27 12:16:24 -04:00
mock_scalars.assert_called_once()
2025-09-03 18:16:52 -04:00
# Compare SQL string representations for reliability
actual_sql = str(mock_scalars.call_args.args[0])
expected_sql = str(select(Invitee).where(Invitee.booking_id == booking_id))
assert actual_sql == expected_sql
2025-08-24 16:27:18 -04:00
@pytest.mark.asyncio
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_get_invitees_for_booking_empty(
2025-08-25 21:38:31 -04:00
async_session: AsyncSession, mock_logger: MagicMock
2025-08-24 16:27:18 -04:00
) -> 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.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
booking_id = 1
mock_scalars_result = AsyncMock()
2025-08-27 12:16:24 -04:00
mock_scalars_result.all = AsyncMock(return_value=[])
2025-08-24 16:27:18 -04:00
mock_scalars = AsyncMock(return_value=mock_scalars_result)
2025-08-27 12:16:24 -04:00
with patch.object(async_session, "scalars", mock_scalars):
result = await get_invitees_for_booking(async_session, booking_id)
2025-08-24 16:27:18 -04:00
assert isinstance(result, list)
assert len(result) == 0
2025-08-27 12:16:24 -04:00
mock_scalars.assert_called_once()
2025-09-03 18:16:52 -04:00
# Compare SQL string representations for reliability
actual_sql = str(mock_scalars.call_args.args[0])
expected_sql = str(select(Invitee).where(Invitee.booking_id == booking_id))
assert actual_sql == expected_sql
2025-08-24 16:27:18 -04:00
@pytest.mark.asyncio
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_get_invitees_for_booking_database_error(
2025-08-25 21:38:31 -04:00
async_session: AsyncSession, mock_logger: MagicMock
2025-08-24 16:27:18 -04:00
) -> None:
"""Test handling of database errors in get_invitees_for_booking.
Verifies that get_invitees_for_booking raises an exception on database failure.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
booking_id = 1
2025-08-27 12:16:24 -04:00
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()
2025-08-24 16:27:18 -04:00
@pytest.mark.asyncio
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_add_invitee_to_booking_success(
async_session: AsyncSession,
2025-08-26 17:15:56 -04:00
sample_invitee: Invitee,
2025-08-25 21:38:31 -04:00
mock_logger: MagicMock,
2025-08-24 16:27:18 -04:00
) -> 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.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
2025-08-26 17:15:56 -04:00
sample_invitee: The mocked invitee object.
2025-08-26 15:47:08 -04:00
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
2025-09-03 18:16:52 -04:00
mock_execute_result = MagicMock()
mock_execute_result.scalar_one = MagicMock(return_value=0)
2025-08-27 12:16:24 -04:00
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(
2025-09-03 18:16:52 -04:00
async_session, "execute", AsyncMock(return_value=mock_execute_result)
2025-08-27 12:16:24 -04:00
),
patch(
"backend.services.bookings.get_booking",
new=AsyncMock(return_value=MagicMock(room_id=1)),
),
patch(
2025-08-27 11:17:36 -04:00
"backend.services.rooms.get_room",
new=AsyncMock(return_value=MagicMock(capacity=10)),
2025-08-27 12:16:24 -04:00
),
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()
2025-08-24 16:27:18 -04:00
@pytest.mark.asyncio
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_add_invitee_to_booking_database_error(
2025-08-25 21:38:31 -04:00
async_session: AsyncSession, mock_logger: MagicMock
2025-08-24 16:27:18 -04:00
) -> None:
"""Test handling of database errors in add_invitee_to_booking.
Verifies that add_invitee_to_booking rolls back the session on database failure.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
booking_id = 1
user_email = "user1@example.com"
invitee = Invitee(booking_id=booking_id, user_email=user_email)
2025-09-03 18:16:52 -04:00
mock_execute_result = MagicMock()
mock_execute_result.scalar_one = MagicMock(return_value=0)
2025-08-27 12:16:24 -04:00
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(
2025-09-03 18:16:52 -04:00
async_session, "execute", AsyncMock(return_value=mock_execute_result)
2025-08-27 12:16:24 -04:00
),
patch(
"backend.services.bookings.get_booking",
new=AsyncMock(return_value=MagicMock(room_id=1)),
),
patch(
2025-08-27 11:17:36 -04:00
"backend.services.rooms.get_room",
new=AsyncMock(return_value=MagicMock(capacity=10)),
2025-08-27 12:16:24 -04:00
),
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()
2025-08-24 16:27:18 -04:00
@pytest.mark.asyncio
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
@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,
2025-08-25 21:38:31 -04:00
mock_logger: MagicMock,
2025-08-24 16:27:18 -04:00
) -> None:
"""Test successful removal of an invitee from a booking.
Verifies that remove_invitee_from_booking deletes the invitee and commits the session.
2025-08-26 15:47:08 -04:00
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.
2025-08-24 16:27:18 -04:00
"""
mock_execute_result = MagicMock(rowcount=1)
2025-08-26 10:06:15 -04:00
async_session.execute = AsyncMock( # type: ignore [method-assign]
return_value=mock_execute_result
)
async_session.commit = AsyncMock() # type: ignore [method-assign]
2025-08-24 16:27:18 -04:00
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
2025-08-25 21:38:31 -04:00
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
2025-08-24 16:27:18 -04:00
async def test_remove_invitee_from_booking_database_error(
2025-08-25 21:38:31 -04:00
async_session: AsyncSession, mock_logger: MagicMock
2025-08-24 16:27:18 -04:00
) -> None:
"""Test handling of database errors in remove_invitee_from_booking.
Verifies that remove_invitee_from_booking rolls back the session on database failure.
2025-08-26 15:47:08 -04:00
Args:
async_session: The asynchronous database session.
mock_logger: The mocked logger instance.
2025-08-24 16:27:18 -04:00
"""
booking_id = 1
user_email = "user1@example.com"
2025-08-26 10:06:15 -04:00
async_session.execute = AsyncMock( # type: ignore [method-assign]
side_effect=SQLAlchemyError("Database error")
)
async_session.rollback = AsyncMock() # type: ignore [method-assign]
2025-08-24 16:27:18 -04:00
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()