Added tests for the invitees router.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-08-25 21:38:31 -04:00
parent 519974a07f
commit dc44225477
10 changed files with 454 additions and 282 deletions

View File

@@ -1,4 +1,4 @@
"""SQL Database services for managing bookings."""
"""SQL Database services for managing invitees."""
import logging
from typing import cast

View File

@@ -1,16 +1,24 @@
"""Test configuration and fixtures for the backend tests."""
import logging
from collections.abc import Generator
from datetime import datetime
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import AsyncSession
from backend.main import app
type MockLogger = MagicMock
from backend.models import Booking
from backend.models import Invitee
from backend.models import Room
from backend.models import User
from backend.services.bookings import BookingList
from backend.services.rooms import RoomList
from backend.services.users import UserList
@pytest.fixture
@@ -23,3 +31,95 @@ def async_session() -> AsyncSession:
def client() -> TestClient:
"""Fixture to provide a FastAPI TestClient for testing endpoints."""
return TestClient(app)
@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.
Provide the path via `request.param` when using the fixture.
"""
# Default path if none provided, or use the parameterized path
module_path = getattr(request, "param", "backend") + ".logger"
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch(module_path, mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_users() -> UserList:
"""Fixture to provide sample User objects for testing."""
return [
User(email="user1@example.com", name="User One"),
User(email="user2@example.com", name="User Two"),
]
@pytest.fixture
def sample_rooms() -> RoomList:
"""Fixture to provide sample Room objects for testing."""
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:
"""Fixture to provide a sample Room object for testing."""
return sample_rooms[0]
@pytest.fixture
def sample_bookings(sample_room: Room) -> BookingList:
"""Fixture to provide sample Booking objects for testing."""
booking1 = Booking(
room_id=1,
start_time=datetime.fromisoformat("2025-08-25T10:00:00"),
end_time=datetime.fromisoformat("2025-08-25T11:00:00"),
)
booking1.id = 1 # Manually set ID for testing purposes
booking1.room = sample_room # Attach room relationship
booking2 = Booking(
room_id=1,
start_time=datetime.fromisoformat("2025-08-25T12:00:00"),
end_time=datetime.fromisoformat("2025-08-25T13:00: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:
"""Fixture to provide a sample Booking object for testing."""
return sample_bookings[0]
@pytest.fixture
def sample_invitees(sample_booking: Booking, sample_users: UserList) -> list[Invitee]:
"""Fixture to provide sample Invitee objects for testing."""
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]

View File

@@ -1,7 +1,5 @@
"""Unit tests for the backend.routers.bookings module."""
import logging
from collections.abc import Generator
from datetime import datetime
from typing import Any
from unittest.mock import ANY
@@ -18,52 +16,11 @@ from backend.models import Booking
from backend.models import Room
from backend.services.bookings import BookingList
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the bookings service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.routers.bookings.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_room() -> Room:
"""Fixture to provide a sample Room object for testing."""
return Room(
id=1,
name="Test Room",
location="Building A",
equipment="Projector, Whiteboard",
capacity=10,
)
@pytest.fixture
def sample_bookings(sample_room: Room) -> BookingList:
"""Fixture to provide sample Booking objects for testing."""
booking1 = Booking(
room_id=1,
start_time=datetime.fromisoformat("2025-08-25T10:00:00"),
end_time=datetime.fromisoformat("2025-08-25T11:00:00"),
)
booking1.id = 1 # Manually set ID for testing purposes
booking1.room = sample_room # Attach room relationship
booking2 = Booking(
room_id=1,
start_time=datetime.fromisoformat("2025-08-25T12:00:00"),
end_time=datetime.fromisoformat("2025-08-25T13:00:00"),
)
booking2.id = 2 # Manually set ID for testing purposes
booking2.room = sample_room # Attach room relationship
return [booking1, booking2]
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_bookings_for_room_success(
client: TestClient, sample_bookings: BookingList, mock_logger: MockLogger
client: TestClient, sample_bookings: BookingList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all bookings for a room via GET /bookings/room/{room_id}.
@@ -96,8 +53,9 @@ async def test_get_bookings_for_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_bookings_for_room_empty(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test retrieval of bookings when none exist for a room via GET /bookings/room/{room_id}.
@@ -117,8 +75,9 @@ async def test_get_bookings_for_room_empty(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_bookings_for_room_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /bookings/room/{room_id}.
@@ -138,8 +97,9 @@ async def test_get_bookings_for_room_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_booking_success(
client: TestClient, sample_room: Room, mock_logger: MockLogger
client: TestClient, sample_room: Room, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of a booking by ID via GET /bookings/{booking_id}.
@@ -171,8 +131,9 @@ async def test_get_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_booking_not_found(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent booking in GET /bookings/{booking_id}.
@@ -192,8 +153,9 @@ async def test_get_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_get_booking_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /bookings/{booking_id}.
@@ -213,8 +175,9 @@ async def test_get_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_create_booking_success(
client: TestClient, sample_room: Room, mock_logger: MockLogger
client: TestClient, sample_room: Room, mock_logger: MagicMock
) -> None:
"""Test successful creation of a new booking via POST /bookings/.
@@ -246,8 +209,9 @@ async def test_create_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_create_booking_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in POST /bookings/.
@@ -271,8 +235,9 @@ async def test_create_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_update_booking_success(
client: TestClient, sample_room: Room, mock_logger: MockLogger
client: TestClient, sample_room: Room, mock_logger: MagicMock
) -> None:
"""Test successful update of a booking via PUT /bookings/{booking_id}.
@@ -307,8 +272,9 @@ async def test_update_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_update_booking_not_found(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent booking in PUT /bookings/{booking_id}.
@@ -335,8 +301,9 @@ async def test_update_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_update_booking_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in PUT /bookings/{booking_id}.
@@ -363,8 +330,9 @@ async def test_update_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_delete_booking_success(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test successful deletion of a booking via DELETE /bookings/{booking_id}.
@@ -382,8 +350,9 @@ async def test_delete_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_delete_booking_not_found(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent booking in DELETE /bookings/{booking_id}.
@@ -403,8 +372,9 @@ async def test_delete_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.bookings"], indirect=True)
async def test_delete_booking_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in DELETE /bookings/{booking_id}.

View File

@@ -0,0 +1,210 @@
"""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 fastapi.testclient import TestClient
from sqlalchemy.exc import SQLAlchemyError
from backend.models import Booking
from backend.models import Invitee
from backend.services.invitees import UserList
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_get_invitees_for_booking_success(
client: TestClient, sample_users: UserList, 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.
"""
booking_id = 1
with patch(
"backend.routers.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.return_value = sample_users
response = client.get(f"/invitees/booking/{booking_id}")
assert response.status_code == 200
assert response.json() == [
{"email": "user1@example.com", "name": "User One"},
{"email": "user2@example.com", "name": "User Two"},
]
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_get_invitees_for_booking_empty(
client: TestClient, 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.
"""
booking_id = 1
with patch(
"backend.routers.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.return_value = []
response = 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_get_invitees_for_booking_database_error(
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /invitees/booking/{booking_id}.
Verifies that the endpoint returns a 500 status on database failure.
"""
booking_id = 1
with patch(
"backend.routers.invitees.get_invitees_for_booking", new=AsyncMock()
) as mock_get_invitees:
mock_get_invitees.side_effect = SQLAlchemyError()
response = client.get(f"/invitees/booking/{booking_id}")
assert response.status_code == 500
assert "detail" in 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: TestClient,
sample_booking: Booking,
sample_users: UserList,
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.
"""
booking_id = 1
invitee_data: dict[str, Any] = {
"booking_id": booking_id,
"user_email": "user1@example.com",
}
created_invitee = Invitee(**invitee_data)
created_invitee.id = 1
created_invitee.booking = sample_booking
created_invitee.user = sample_users[0]
with patch(
"backend.routers.invitees.add_invitee_to_booking", new=AsyncMock()
) as mock_add_invitee:
mock_add_invitee.return_value = created_invitee
response = client.post(f"/invitees/booking/{booking_id}", json=invitee_data)
assert response.status_code == 201
assert response.json() == {
"id": 1,
"booking_id": booking_id,
"user_email": "user1@example.com",
"booking": {
"id": 1,
"room_id": 1,
"start_time": "2025-08-25T10:00:00",
"end_time": "2025-08-25T11:00:00",
},
"user": {
"email": "user1@example.com",
"name": "User One",
},
}
mock_add_invitee.assert_called_once_with(
ANY, booking_id, invitee_data["user_email"]
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_add_invitee_database_error(
client: TestClient, 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.
"""
booking_id = 1
invitee_data: dict[str, Any] = {
"booking_id": booking_id,
"user_email": "user1@example.com",
}
with patch(
"backend.routers.invitees.add_invitee_to_booking", new=AsyncMock()
) as mock_add_invitee:
mock_add_invitee.side_effect = SQLAlchemyError()
response = client.post(f"/invitees/booking/{booking_id}", json=invitee_data)
assert response.status_code == 400
assert "detail" in response.json()
mock_add_invitee.assert_called_once_with(
ANY, booking_id, invitee_data["user_email"]
)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_remove_invitee_success(
client: TestClient, 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.
"""
booking_id = 1
user_email = "user1@example.com"
with patch(
"backend.routers.invitees.remove_invitee_from_booking", new=AsyncMock()
) as mock_remove_invitee:
response = client.delete(f"/invitees/booking/{booking_id}/user/{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: TestClient, 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.
"""
booking_id = 1
user_email = "user1@example.com"
with patch(
"backend.routers.invitees.remove_invitee_from_booking", new=AsyncMock()
) as mock_remove_invitee:
mock_remove_invitee.side_effect = SQLAlchemyError()
response = client.delete(f"/invitees/booking/{booking_id}/user/{user_email}")
assert response.status_code == 500
assert "detail" in response.json()
mock_remove_invitee.assert_called_once_with(ANY, booking_id, user_email)

View File

@@ -1,7 +1,5 @@
"""Unit tests for the backend.routers.rooms module."""
import logging
from collections.abc import Generator
from typing import Any
from unittest.mock import ANY
from unittest.mock import AsyncMock
@@ -15,41 +13,11 @@ from sqlalchemy.exc import NoResultFound
from backend.models import Room
from backend.services.rooms import RoomList
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the rooms service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.routers.rooms.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_rooms() -> RoomList:
"""Fixture to provide sample Room objects for testing."""
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.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_get_rooms_success(
client: TestClient, sample_rooms: RoomList, mock_logger: MockLogger
client: TestClient, sample_rooms: RoomList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all rooms via GET /rooms/.
@@ -81,7 +49,8 @@ async def test_get_rooms_success(
@pytest.mark.asyncio
async def test_get_rooms_empty(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_get_rooms_empty(client: TestClient, mock_logger: MagicMock) -> None:
"""Test retrieval of rooms when none exist via GET /rooms/.
Verifies that the endpoint returns an empty list with a 200 status.
@@ -97,7 +66,8 @@ async def test_get_rooms_empty(client: TestClient, mock_logger: MockLogger) -> N
@pytest.mark.asyncio
async def test_get_room_success(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_get_room_success(client: TestClient, mock_logger: MagicMock) -> None:
"""Test successful retrieval of a room by ID via GET /rooms/{room_id}.
Verifies that the endpoint returns the correct room with a 200 status.
@@ -127,7 +97,8 @@ async def test_get_room_success(client: TestClient, mock_logger: MockLogger) ->
@pytest.mark.asyncio
async def test_get_room_not_found(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_get_room_not_found(client: TestClient, mock_logger: MagicMock) -> None:
"""Test handling of non-existent room in GET /rooms/{room_id}.
Verifies that the endpoint returns a 404 status when the room is not found.
@@ -144,7 +115,8 @@ async def test_get_room_not_found(client: TestClient, mock_logger: MockLogger) -
@pytest.mark.asyncio
async def test_new_room_success(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_new_room_success(client: TestClient, mock_logger: MagicMock) -> None:
"""Test successful creation of a new room via POST /rooms/.
Verifies that the endpoint returns the created room with a 201 status.
@@ -168,7 +140,8 @@ async def test_new_room_success(client: TestClient, mock_logger: MockLogger) ->
@pytest.mark.asyncio
async def test_update_room_success(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_update_room_success(client: TestClient, mock_logger: MagicMock) -> None:
"""Test successful update of a room via PUT /rooms/{room_id}.
Verifies that the endpoint returns the updated room with a 200 status.
@@ -200,8 +173,9 @@ async def test_update_room_success(client: TestClient, mock_logger: MockLogger)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_update_room_not_found(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent room in PUT /rooms/{room_id}.
@@ -227,7 +201,8 @@ async def test_update_room_not_found(
@pytest.mark.asyncio
async def test_delete_room_success(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_delete_room_success(client: TestClient, mock_logger: MagicMock) -> None:
"""Test successful deletion of a room via DELETE /rooms/{room_id}.
Verifies that the endpoint returns a 204 status on successful deletion.
@@ -244,8 +219,9 @@ async def test_delete_room_success(client: TestClient, mock_logger: MockLogger)
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.rooms"], indirect=True)
async def test_delete_room_not_found(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent room in DELETE /rooms/{room_id}.

View File

@@ -1,7 +1,5 @@
"""Unit tests for the backend.routers.users module."""
import logging
from collections.abc import Generator
from unittest.mock import ANY
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
@@ -15,29 +13,11 @@ from sqlalchemy.exc import SQLAlchemyError
from backend.models import User
from backend.services.users import UserList
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the users service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.services.users.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_users() -> UserList:
"""Fixture to provide sample User objects for testing."""
return [
User(email="user1@example.com", name="User One"),
User(email="user2@example.com", name="User Two"),
]
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
async def test_read_users_success(
client: TestClient, sample_users: UserList, mock_logger: MockLogger
client: TestClient, sample_users: UserList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all users via GET /users/.
@@ -57,7 +37,8 @@ async def test_read_users_success(
@pytest.mark.asyncio
async def test_read_users_empty(client: TestClient, mock_logger: MockLogger) -> None:
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
async def test_read_users_empty(client: TestClient, mock_logger: MagicMock) -> None:
"""Test retrieval of users when none exist via GET /users/.
Verifies that the endpoint returns an empty list with a 200 status.
@@ -73,8 +54,9 @@ async def test_read_users_empty(client: TestClient, mock_logger: MockLogger) ->
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
async def test_read_users_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /users/.
@@ -91,6 +73,7 @@ async def test_read_users_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
@pytest.mark.parametrize(
"email, expected_name",
[
@@ -100,7 +83,7 @@ async def test_read_users_database_error(
ids=["user1", "user2"],
)
async def test_read_user_success(
client: TestClient, email: str, expected_name: str, mock_logger: MockLogger
client: TestClient, email: str, expected_name: str, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of a user by email via GET /users/{email}.
@@ -118,13 +101,14 @@ async def test_read_user_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
@pytest.mark.parametrize(
"email",
["nonexistent@example.com", "invalid@domain.com"],
ids=["nonexistent_email", "invalid_email"],
)
async def test_read_user_not_found(
client: TestClient, email: str, mock_logger: MockLogger
client: TestClient, email: str, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent user in GET /users/{email}.
@@ -141,8 +125,9 @@ async def test_read_user_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.routers.users"], indirect=True)
async def test_read_user_database_error(
client: TestClient, mock_logger: MockLogger
client: TestClient, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in GET /users/{email}.

View File

@@ -1,12 +1,9 @@
"""Unit tests for the backend.services.bookings module."""
import logging
from collections.abc import Generator
from datetime import datetime
from typing import Any
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy import delete
@@ -24,38 +21,11 @@ from backend.services.bookings import get_bookings_for_room
from backend.services.bookings import new_booking
from backend.services.bookings import update_booking
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the bookings service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.services.bookings.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_bookings() -> BookingList:
"""Fixture to provide sample Booking objects for testing."""
booking1 = Booking(
room_id=1,
start_time=datetime(2025, 8, 24, 10, 0),
end_time=datetime(2025, 8, 24, 12, 0),
)
booking1.id = 1
booking2 = Booking(
room_id=1,
start_time=datetime(2025, 8, 24, 13, 0),
end_time=datetime(2025, 8, 24, 15, 0),
)
booking2.id = 2
return [booking1, booking2]
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_get_bookings_for_room_success(
async_session: AsyncSession, sample_bookings: BookingList, mock_logger: MockLogger
async_session: AsyncSession, sample_bookings: BookingList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all bookings for a room.
@@ -80,8 +50,9 @@ async def test_get_bookings_for_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_get_bookings_for_room_empty(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test retrieval of bookings when none exist for the room.
@@ -104,8 +75,9 @@ async def test_get_bookings_for_room_empty(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_get_bookings_for_room_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_bookings_for_room.
@@ -120,6 +92,7 @@ async def test_get_bookings_for_room_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
@pytest.mark.parametrize(
"booking_id, expected_room_id",
[
@@ -132,7 +105,7 @@ async def test_get_booking_success(
async_session: AsyncSession,
booking_id: int,
expected_room_id: int,
mock_logger: MockLogger,
mock_logger: MagicMock,
) -> None:
"""Test successful retrieval of a booking by ID.
@@ -157,13 +130,14 @@ async def test_get_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
@pytest.mark.parametrize(
"booking_id",
[999, -1],
ids=["nonexistent_id", "invalid_id"],
)
async def test_get_booking_not_found(
async_session: AsyncSession, mock_logger: MockLogger, booking_id: int
async_session: AsyncSession, mock_logger: MagicMock, booking_id: int
) -> None:
"""Test handling of non-existent booking in get_booking.
@@ -180,8 +154,9 @@ async def test_get_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_get_booking_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_booking.
@@ -199,8 +174,9 @@ async def test_get_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_new_booking_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful creation of a new booking.
@@ -223,8 +199,9 @@ async def test_new_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_new_booking_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in new_booking.
@@ -248,8 +225,9 @@ async def test_new_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_update_booking_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful update of a booking.
@@ -284,8 +262,9 @@ async def test_update_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_update_booking_not_found(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent booking in update_booking.
@@ -311,8 +290,9 @@ async def test_update_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_update_booking_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in update_booking.
@@ -337,8 +317,9 @@ async def test_update_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_delete_booking_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful deletion of a booking.
@@ -359,8 +340,9 @@ async def test_delete_booking_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_delete_booking_not_found(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent booking in delete_booking.
@@ -381,8 +363,9 @@ async def test_delete_booking_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
async def test_delete_booking_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in delete_booking.

View File

@@ -1,10 +1,7 @@
"""Unit tests for the backend.services.invitees module."""
import logging
from collections.abc import Generator
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy import delete
@@ -13,35 +10,16 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Invitee
from backend.models import User
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
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the invitees service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.services.invitees.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_invitees() -> UserList:
"""Fixture to provide sample User objects for testing invitees."""
return [
User(email="user1@example.com", name="User One"),
User(email="user2@example.com", name="User Two"),
]
@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: MockLogger
async_session: AsyncSession, sample_invitees: UserList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all invitees for a booking.
@@ -66,8 +44,9 @@ async def test_get_invitees_for_booking_success(
@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: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test retrieval of invitees when none exist for the booking.
@@ -90,8 +69,9 @@ async def test_get_invitees_for_booking_empty(
@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: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_invitees_for_booking.
@@ -106,6 +86,7 @@ async def test_get_invitees_for_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
@pytest.mark.parametrize(
"booking_id, user_email",
[
@@ -118,7 +99,7 @@ async def test_add_invitee_to_booking_success(
async_session: AsyncSession,
booking_id: int,
user_email: str,
mock_logger: MockLogger,
mock_logger: MagicMock,
) -> None:
"""Test successful addition of an invitee to a booking.
@@ -141,8 +122,9 @@ async def test_add_invitee_to_booking_success(
@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: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in add_invitee_to_booking.
@@ -163,6 +145,7 @@ async def test_add_invitee_to_booking_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.invitees"], indirect=True)
@pytest.mark.parametrize(
"booking_id, user_email",
[
@@ -175,7 +158,7 @@ async def test_remove_invitee_from_booking_success(
async_session: AsyncSession,
booking_id: int,
user_email: str,
mock_logger: MockLogger,
mock_logger: MagicMock,
) -> None:
"""Test successful removal of an invitee from a booking.
@@ -197,8 +180,9 @@ async def test_remove_invitee_from_booking_success(
@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: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in remove_invitee_from_booking.

View File

@@ -1,11 +1,8 @@
"""Unit tests for the backend.services.rooms module."""
import logging
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy import delete
@@ -23,41 +20,11 @@ from backend.services.rooms import get_rooms
from backend.services.rooms import new_room
from backend.services.rooms import update_room
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the rooms service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.services.rooms.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_rooms() -> RoomList:
"""Fixture to provide sample Room objects for testing."""
return [
Room(
id=1,
name="Room A",
location="Building 1",
equipment="Projector",
capacity=10,
),
Room(
id=2,
name="Room B",
location="Building 2",
equipment="Whiteboard",
capacity=20,
),
]
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_get_rooms_success(
async_session: AsyncSession, sample_rooms: RoomList, mock_logger: MockLogger
async_session: AsyncSession, sample_rooms: RoomList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all rooms from the database.
@@ -79,8 +46,9 @@ async def test_get_rooms_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_get_rooms_empty(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test retrieval of rooms when the database is empty.
@@ -100,8 +68,9 @@ async def test_get_rooms_empty(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_get_rooms_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_rooms.
@@ -115,6 +84,7 @@ async def test_get_rooms_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
@pytest.mark.parametrize(
"room_id, expected_name",
[
@@ -127,7 +97,7 @@ async def test_get_room_success(
async_session: AsyncSession,
room_id: int,
expected_name: str,
mock_logger: MockLogger,
mock_logger: MagicMock,
) -> None:
"""Test successful retrieval of a room by ID.
@@ -153,13 +123,14 @@ async def test_get_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
@pytest.mark.parametrize(
"room_id",
[999, -1],
ids=["nonexistent_id", "invalid_id"],
)
async def test_get_room_not_found(
async_session: AsyncSession, mock_logger: MockLogger, room_id: int
async_session: AsyncSession, mock_logger: MagicMock, room_id: int
) -> None:
"""Test handling of non-existent room in get_room.
@@ -176,8 +147,9 @@ async def test_get_room_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_get_room_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_room.
@@ -195,8 +167,9 @@ async def test_get_room_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_new_room_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful creation of a new room.
@@ -216,8 +189,9 @@ async def test_new_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_new_room_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in new_room.
@@ -239,8 +213,9 @@ async def test_new_room_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_update_room_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful update of a room.
@@ -275,8 +250,9 @@ async def test_update_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_update_room_not_found(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent room in update_room.
@@ -303,8 +279,9 @@ async def test_update_room_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_update_room_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in update_room.
@@ -330,8 +307,9 @@ async def test_update_room_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_delete_room_success(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test successful deletion of a room.
@@ -352,8 +330,9 @@ async def test_delete_room_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_delete_room_not_found(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of non-existent room in delete_room.
@@ -374,8 +353,9 @@ async def test_delete_room_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.rooms"], indirect=True)
async def test_delete_room_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in delete_room.

View File

@@ -1,10 +1,7 @@
"""Unit tests for the backend.services.users module."""
import logging
from collections.abc import Generator
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy import select
@@ -17,29 +14,11 @@ from backend.services.users import UserList
from backend.services.users import get_user
from backend.services.users import get_users
from ..conftest import MockLogger
@pytest.fixture(autouse=True)
def mock_logger() -> Generator[MockLogger, None, None]:
"""Fixture to mock the logger used in the users service."""
mock_logger_instance = MagicMock(spec=logging.Logger)
with patch("backend.services.users.logger", mock_logger_instance):
yield mock_logger_instance
@pytest.fixture
def sample_users() -> UserList:
"""Fixture to provide sample User objects for testing."""
return [
User(email="user1@example.com", name="User One"),
User(email="user2@example.com", name="User Two"),
]
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
async def test_get_users_success(
async_session: AsyncSession, sample_users: UserList, mock_logger: MockLogger
async_session: AsyncSession, sample_users: UserList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all users from the database.
@@ -61,8 +40,9 @@ async def test_get_users_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
async def test_get_users_empty(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test retrieval of users when the database is empty.
@@ -82,8 +62,9 @@ async def test_get_users_empty(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
async def test_get_users_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_users.
@@ -97,6 +78,7 @@ async def test_get_users_database_error(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
@pytest.mark.parametrize(
"email, expected_name",
[
@@ -106,7 +88,7 @@ async def test_get_users_database_error(
ids=["user1", "user2"],
)
async def test_get_user_success(
async_session: AsyncSession, email: str, expected_name: str, mock_logger: MockLogger
async_session: AsyncSession, email: str, expected_name: str, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of a user by email.
@@ -126,6 +108,7 @@ async def test_get_user_success(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
@pytest.mark.parametrize(
"email",
[
@@ -135,7 +118,7 @@ async def test_get_user_success(
ids=["nonexistent_email", "invalid_email"],
)
async def test_get_user_not_found(
async_session: AsyncSession, mock_logger: MockLogger, email: str
async_session: AsyncSession, mock_logger: MagicMock, email: str
) -> None:
"""Test handling of non-existent user in get_user.
@@ -152,8 +135,9 @@ async def test_get_user_not_found(
@pytest.mark.asyncio
@pytest.mark.parametrize("mock_logger", ["backend.services.users"], indirect=True)
async def test_get_user_database_error(
async_session: AsyncSession, mock_logger: MockLogger
async_session: AsyncSession, mock_logger: MagicMock
) -> None:
"""Test handling of database errors in get_user.