"""Unit tests for the backend.services.invitees module.""" from unittest.mock import AsyncMock from unittest.mock import MagicMock 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.services.invitees import UserList 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: UserList, 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. """ booking_id = 1 mock_scalars_result = AsyncMock() mock_scalars_result.all = MagicMock(return_value=sample_invitees) mock_scalars = AsyncMock(return_value=mock_scalars_result) async_session.scalars = mock_scalars result: UserList = await get_invitees_for_booking(async_session, booking_id) assert isinstance(result, list) assert len(result) == 2 assert result == sample_invitees async_session.scalars.assert_called_once() assert async_session.scalars.call_args.args[0].compare( select(Invitee.user).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. """ booking_id = 1 mock_scalars_result = AsyncMock() mock_scalars_result.all = MagicMock(return_value=[]) mock_scalars = AsyncMock(return_value=mock_scalars_result) async_session.scalars = mock_scalars result: UserList = await get_invitees_for_booking(async_session, booking_id) assert isinstance(result, list) assert len(result) == 0 async_session.scalars.assert_called_once() assert async_session.scalars.call_args.args[0].compare( select(Invitee.user).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. """ booking_id = 1 async_session.scalars = AsyncMock(side_effect=SQLAlchemyError("Database error")) with pytest.raises(SQLAlchemyError): await get_invitees_for_booking(async_session, booking_id) async_session.scalars.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_add_invitee_to_booking_success( async_session: AsyncSession, booking_id: int, user_email: str, 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. """ invitee = Invitee(booking_id=booking_id, user_email=user_email) async_session.add = MagicMock() async_session.commit = AsyncMock() result: Invitee = await add_invitee_to_booking( async_session, booking_id, user_email ) assert result == invitee assert result.booking_id == booking_id assert result.user_email == user_email async_session.add.assert_called_once_with(invitee) async_session.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. """ booking_id = 1 user_email = "user1@example.com" invitee = Invitee(booking_id=booking_id, user_email=user_email) async_session.add = MagicMock() async_session.commit = AsyncMock(side_effect=SQLAlchemyError("Database error")) async_session.rollback = AsyncMock() with pytest.raises(SQLAlchemyError): await add_invitee_to_booking(async_session, booking_id, user_email) async_session.add.assert_called_once_with(invitee) async_session.commit.assert_called_once() async_session.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. """ mock_execute_result = MagicMock(rowcount=1) async_session.execute = AsyncMock(return_value=mock_execute_result) async_session.commit = AsyncMock() 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. """ booking_id = 1 user_email = "user1@example.com" async_session.execute = AsyncMock(side_effect=SQLAlchemyError("Database error")) async_session.rollback = AsyncMock() 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()