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

265 lines
7.5 KiB
Python
Raw Normal View History

"""Test configuration and fixtures for the backend tests."""
2025-08-25 21:38:31 -04:00
import logging
2025-08-27 15:51:05 -04:00
from collections.abc import AsyncGenerator
2025-08-25 21:38:31 -04:00
from collections.abc import Generator
from datetime import datetime
2025-08-27 11:17:36 -04:00
from datetime import timedelta
from datetime import timezone
2025-09-03 15:37:25 -04:00
from pathlib import Path
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
2025-08-25 21:38:31 -04:00
from unittest.mock import patch
import pytest
2025-08-27 15:51:05 -04:00
import pytest_asyncio
2025-09-03 15:37:25 -04:00
from dotenv import load_dotenv
2025-09-03 17:47:01 -04:00
from fastapi.responses import JSONResponse
2025-08-27 15:51:05 -04:00
from httpx import ASGITransport
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
2025-09-03 15:37:25 -04:00
# Ensure .env is loaded from base or parent directory before any project
# imports
def _load_env_from_base_or_parent() -> None:
2025-09-03 15:37:25 -04:00
base = Path(__file__).parent.parent # project root
env_path = base / ".env"
if env_path.exists():
load_dotenv(dotenv_path=env_path)
else:
parent_env = base.parent / ".env"
if parent_env.exists():
load_dotenv(dotenv_path=parent_env)
_load_env_from_base_or_parent()
# Project imports must be placed after this:
from backend.main import app # noqa: E402
from backend.models import Booking # noqa: E402
from backend.models import BookingList # noqa: E402
from backend.models import Invitee # noqa: E402
from backend.models import Room # noqa: E402
from backend.models import RoomList # noqa: E402
from backend.models import User # noqa: E402
from backend.models import UserList # noqa: E402
@pytest.fixture
def async_session() -> AsyncSession:
2025-08-26 15:47:08 -04:00
"""Fixture to provide a mocked AsyncSession for database interactions.
Returns:
A mocked AsyncSession object for database interactions.
"""
return AsyncMock(spec=AsyncSession)
2025-08-24 21:20:24 -04:00
2025-08-27 15:51:05 -04:00
@pytest_asyncio.fixture
async def client() -> AsyncGenerator[AsyncClient, None]:
"""Fixture to provide an AsyncClient for async endpoint testing.
2025-08-26 15:47:08 -04:00
2025-08-27 15:51:05 -04:00
Yields:
An HTTPX AsyncClient instance for making async HTTP requests.
2025-08-26 15:47:08 -04:00
"""
2025-08-27 15:51:05 -04:00
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac
2025-08-25 21:38:31 -04:00
@pytest.fixture
def mock_logger(request: pytest.FixtureRequest) -> Generator[MagicMock, None, None]:
"""Fixture to mock a logger for a specified module path.
Args:
request: Pytest request object, used to pass the module path for patching.
2025-08-26 15:47:08 -04:00
Yields:
2025-09-03 17:47:01 -04:00
MagicMock: A mocked logger instance for the specified module path.
Raises:
ValueError: If the module path parameter is not provided.
2025-08-26 15:47:08 -04:00
Usage:
To use this fixture in a test module, parameterize it with the module path
2025-08-27 15:51:05 -04:00
where the logger is defined.
2025-08-25 21:38:31 -04:00
"""
2025-09-03 17:47:01 -04:00
# Require explicit module path for logger
if not hasattr(request, "param"):
raise ValueError(
"mock_logger fixture requires a module path parameter, e.g."
" 'backend.routers.invitees'"
)
module_path = request.param + ".logger"
2025-08-25 21:38:31 -04:00
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch(module_path, mock_logger_instance):
yield mock_logger_instance
2025-08-27 15:51:05 -04:00
@pytest.fixture
def event_publisher() -> AsyncMock:
"""Fixture for an AsyncMock event publisher for event publishing tests.
Returns:
An async mock event publisher callable.
"""
return AsyncMock()
2025-08-25 21:38:31 -04:00
@pytest.fixture
def sample_users() -> UserList:
2025-08-26 15:47:08 -04:00
"""Fixture to provide sample User objects for testing.
Returns:
A list of sample User objects.
"""
2025-08-25 21:38:31 -04:00
return [
User(email="user1@example.com", name="User One"),
User(email="user2@example.com", name="User Two"),
]
@pytest.fixture
def sample_rooms() -> RoomList:
2025-08-26 15:47:08 -04:00
"""Fixture to provide sample Room objects for testing.
Returns:
A list of sample Room objects.
"""
2025-08-25 21:38:31 -04:00
return [
Room(
id=1,
name="Room 1",
location="Building A",
equipment="Projector",
capacity=10,
),
Room(
id=2,
name="Room 2",
location="Building B",
equipment="Whiteboard",
capacity=15,
),
]
@pytest.fixture
def sample_room(sample_rooms: RoomList) -> Room:
2025-08-26 15:47:08 -04:00
"""Fixture to provide a sample Room object for testing.
Args:
sample_rooms: The list of sample Room objects.
Returns:
A single sample Room object.
"""
2025-08-25 21:38:31 -04:00
return sample_rooms[0]
@pytest.fixture
def sample_bookings(sample_room: Room) -> BookingList:
2025-08-26 15:47:08 -04:00
"""Fixture to provide sample Booking objects for testing.
Args:
sample_room: The sample Room object to attach to bookings.
Returns:
A list of sample Booking objects with attached room relationships.
"""
2025-08-25 21:38:31 -04:00
booking1 = Booking(
room_id=1,
2025-08-27 11:17:36 -04:00
start_time=datetime.now(timezone.utc) + timedelta(hours=3),
end_time=datetime.now(timezone.utc) + timedelta(hours=4),
2025-08-27 17:56:42 -04:00
title="Sample Booking 1",
2025-08-25 21:38:31 -04:00
)
booking1.id = 1 # Manually set ID for testing purposes
booking1.room = sample_room # Attach room relationship
booking2 = Booking(
room_id=1,
2025-08-27 11:17:36 -04:00
start_time=datetime.now(timezone.utc) + timedelta(hours=12),
end_time=datetime.now(timezone.utc) + timedelta(hours=13),
2025-08-27 17:56:42 -04:00
title="Sample Booking 2",
2025-08-25 21:38:31 -04:00
)
booking2.id = 2 # Manually set ID for testing purposes
booking2.room = sample_room # Attach room relationship
return [booking1, booking2]
@pytest.fixture
def sample_booking(sample_bookings: BookingList) -> Booking:
2025-08-26 15:47:08 -04:00
"""Fixture to provide a sample Booking object for testing.
Args:
sample_bookings: The list of sample Booking objects.
Returns:
A single sample Booking object.
"""
2025-08-25 21:38:31 -04:00
return sample_bookings[0]
@pytest.fixture
def sample_invitees(sample_booking: Booking, sample_users: UserList) -> list[Invitee]:
2025-08-26 15:47:08 -04:00
"""Fixture to provide sample Invitee objects for testing.
Args:
sample_booking: The sample Booking object to attach to invitees.
sample_users: The list of sample User objects to attach to invitees.
Returns:
A list of sample Invitee objects with attached booking and user relationships.
"""
2025-08-25 21:38:31 -04:00
invitee1 = Invitee(booking_id=1, user_email="user1@example.com")
invitee1.id = 1
invitee1.booking = sample_booking
invitee1.user = sample_users[0]
invitee2 = Invitee(booking_id=1, user_email="user2@example.com")
invitee2.id = 2
invitee2.booking = sample_booking
invitee2.user = sample_users[1]
return [invitee1, invitee2]
2025-08-26 17:15:56 -04:00
@pytest.fixture
def sample_invitee(sample_invitees: list[Invitee]) -> Invitee:
"""Fixture to provide a sample Invitee object for testing.
Args:
sample_invitees: The list of sample Invitee objects.
Returns:
A single sample Invitee object.
"""
return sample_invitees[0]
2025-09-03 17:47:01 -04:00
@pytest.fixture(autouse=True)
def override_exception_handler() -> Generator[None, None, None]:
"""Override the default exception handler for testing.
Yields:
None
"""
from backend.main import app
# Save the original handler
original_handler = app.exception_handlers.get(Exception)
from fastapi import Request
async def test_exception_handler(request: Request, exc: Exception) -> JSONResponse:
return JSONResponse(
status_code=500, content={"detail": "Internal server error"}
)
app.add_exception_handler(Exception, test_exception_handler)
yield
# Restore the original handler
if original_handler is not None:
app.add_exception_handler(Exception, original_handler)
else:
del app.exception_handlers[Exception]