"""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 Invitee @pytest.mark.asyncio @pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True) async def test_get_invitees_for_booking_success( client: AsyncClient, sample_invitee: Invitee, 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_invitee: The mocked invitee to return. mock_logger: The mocked logger instance. """ invitee_data: dict[str, Any] = { "user_email": sample_invitee.user_email, } with patch( "backend.routers.invitees._try_create_invitee", new=AsyncMock() ) as mock_create_invitee, patch( "backend.routers.invitees._fetch_user_data", new=AsyncMock() ) as mock_fetch_user_data: mock_create_invitee.return_value = sample_invitee mock_fetch_user_data.return_value = sample_invitee.user response = await client.post( f"/invitees/booking/{sample_invitee.booking_id}", json=invitee_data ) if response.status_code != 201: print("TEST FAILURE RESPONSE:", response.json()) assert response.status_code == 201 result = response.json() assert result["booking_id"] == sample_invitee.booking_id assert result["user_email"] == sample_invitee.user_email assert result["user"] == { "email": sample_invitee.user.email, "name": sample_invitee.user.name, } mock_create_invitee.assert_called_once_with(ANY, sample_invitee.booking_id, ANY) mock_fetch_user_data.assert_called_once_with(ANY, sample_invitee.user_email) @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(return_value=[]), ) as mock_get_invitees: 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_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 (AsyncClient): The FastAPI test client for making HTTP requests. sample_invitee (Invitee): The mocked invitee object. mock_logger (MagicMock): The mocked logger instance. """ with patch( "backend.routers.invitees._try_create_invitee", new=AsyncMock() ) as mock_create_invitee, patch( "backend.routers.invitees._fetch_user_data", new=AsyncMock() ) as mock_fetch_user_data: mock_create_invitee.return_value = sample_invitee mock_fetch_user_data.return_value = sample_invitee.user response = await client.post( f"/invitees/booking/{sample_invitee.booking_id}", json={"user_email": sample_invitee.user_email}, ) if response.status_code != 201: print("TEST FAILURE RESPONSE:", response.json()) assert response.status_code == 201 result = response.json() assert result["booking_id"] == sample_invitee.booking_id assert result["user_email"] == sample_invitee.user_email assert result["user"] == { "email": sample_invitee.user.email, "name": sample_invitee.user.name, } mock_create_invitee.assert_called_once_with(ANY, sample_invitee.booking_id, ANY) mock_fetch_user_data.assert_called_once_with(ANY, 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._try_create_invitee", new=AsyncMock() ) as mock_create_invitee: mock_create_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_create_invitee.assert_called_once_with(ANY, booking_id, ANY) @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(return_value=None), ) as mock_remove_invitee: response = await client.delete(f"/invitees/booking/{booking_id}/{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(side_effect=SQLAlchemyError()), ) as mock_remove_invitee: response = await client.delete(f"/invitees/booking/{booking_id}/{user_email}") assert response.status_code == 500 assert "detail" in response.json() mock_remove_invitee.assert_called_once_with(ANY, booking_id, user_email)