mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-05 19:08:26 -04:00
385 lines
13 KiB
Python
385 lines
13 KiB
Python
"""Unit tests for the backend.services.bookings module."""
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from sqlalchemy import delete
|
|
from sqlalchemy import select
|
|
from sqlalchemy import update
|
|
from sqlalchemy.exc import NoResultFound
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from backend.models import Booking
|
|
from backend.services.bookings import BookingList
|
|
from backend.services.bookings import delete_booking
|
|
from backend.services.bookings import get_booking
|
|
from backend.services.bookings import get_bookings_for_room
|
|
from backend.services.bookings import new_booking
|
|
from backend.services.bookings import update_booking
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test successful retrieval of all bookings for a room.
|
|
|
|
Verifies that get_bookings_for_room returns the expected list of bookings and constructs
|
|
the correct SQLAlchemy query.
|
|
"""
|
|
room_id = 1
|
|
mock_scalars_result = AsyncMock()
|
|
mock_scalars_result.all = MagicMock(return_value=sample_bookings)
|
|
mock_scalars = AsyncMock(return_value=mock_scalars_result)
|
|
async_session.scalars = mock_scalars
|
|
|
|
result: BookingList = await get_bookings_for_room(async_session, room_id)
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) == 2
|
|
assert result == sample_bookings
|
|
async_session.scalars.assert_called_once()
|
|
assert async_session.scalars.call_args.args[0].compare(
|
|
select(Booking).where(Booking.room_id == room_id)
|
|
)
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test retrieval of bookings when none exist for the room.
|
|
|
|
Verifies that get_bookings_for_room returns an empty list when no bookings are found.
|
|
"""
|
|
room_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: BookingList = await get_bookings_for_room(async_session, room_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(Booking).where(Booking.room_id == room_id)
|
|
)
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in get_bookings_for_room.
|
|
|
|
Verifies that get_bookings_for_room raises an exception on database failure.
|
|
"""
|
|
room_id = 1
|
|
async_session.scalars = AsyncMock(side_effect=SQLAlchemyError("Database error"))
|
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
await get_bookings_for_room(async_session, room_id)
|
|
async_session.scalars.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
|
|
@pytest.mark.parametrize(
|
|
"booking_id, expected_room_id",
|
|
[
|
|
(1, 1),
|
|
(2, 2),
|
|
],
|
|
ids=["booking1", "booking2"],
|
|
)
|
|
async def test_get_booking_success(
|
|
async_session: AsyncSession,
|
|
booking_id: int,
|
|
expected_room_id: int,
|
|
mock_logger: MagicMock,
|
|
) -> None:
|
|
"""Test successful retrieval of a booking by ID.
|
|
|
|
Verifies that get_booking returns the correct booking and constructs the correct query.
|
|
"""
|
|
booking = Booking(
|
|
room_id=expected_room_id,
|
|
start_time=datetime(2025, 8, 24, 10, 0),
|
|
end_time=datetime(2025, 8, 24, 12, 0),
|
|
)
|
|
booking.id = booking_id
|
|
async_session.scalar = AsyncMock(return_value=booking)
|
|
|
|
result: Booking = await get_booking(async_session, booking_id)
|
|
|
|
assert result.id == booking_id
|
|
assert result.room_id == expected_room_id
|
|
async_session.scalar.assert_called_once()
|
|
assert async_session.scalar.call_args.args[0].compare(
|
|
select(Booking).where(Booking.id == booking_id)
|
|
)
|
|
|
|
|
|
@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: MagicMock, booking_id: int
|
|
) -> None:
|
|
"""Test handling of non-existent booking in get_booking.
|
|
|
|
Verifies that get_booking raises NoResultFound when the booking is not found.
|
|
"""
|
|
async_session.scalar = AsyncMock(return_value=None)
|
|
|
|
with pytest.raises(NoResultFound):
|
|
await get_booking(async_session, booking_id)
|
|
async_session.scalar.assert_called_once()
|
|
assert async_session.scalar.call_args.args[0].compare(
|
|
select(Booking).where(Booking.id == booking_id)
|
|
)
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in get_booking.
|
|
|
|
Verifies that get_booking raises an exception on database failure.
|
|
"""
|
|
booking_id = 1
|
|
async_session.scalar = AsyncMock(side_effect=SQLAlchemyError("Database error"))
|
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
await get_booking(async_session, booking_id)
|
|
async_session.scalar.assert_called_once()
|
|
assert async_session.scalar.call_args.args[0].compare(
|
|
select(Booking).where(Booking.id == booking_id)
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
|
|
async def test_new_booking_success(
|
|
async_session: AsyncSession, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful creation of a new booking.
|
|
|
|
Verifies that new_booking adds the booking, commits the session, and returns the booking.
|
|
"""
|
|
booking = Booking(
|
|
room_id=1,
|
|
start_time=datetime(2025, 8, 24, 10, 0),
|
|
end_time=datetime(2025, 8, 24, 12, 0),
|
|
)
|
|
booking.id = 1
|
|
async_session.add = MagicMock()
|
|
async_session.commit = AsyncMock()
|
|
|
|
result: Booking = await new_booking(async_session, booking)
|
|
|
|
assert result == booking
|
|
async_session.add.assert_called_once_with(booking)
|
|
async_session.commit.assert_called_once()
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in new_booking.
|
|
|
|
Verifies that new_booking rolls back the session on database failure.
|
|
"""
|
|
booking = Booking(
|
|
room_id=1,
|
|
start_time=datetime(2025, 8, 24, 10, 0),
|
|
end_time=datetime(2025, 8, 24, 12, 0),
|
|
)
|
|
booking.id = 1
|
|
async_session.add = MagicMock()
|
|
async_session.commit = AsyncMock(side_effect=SQLAlchemyError("Database error"))
|
|
async_session.rollback = AsyncMock()
|
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
await new_booking(async_session, booking)
|
|
async_session.add.assert_called_once_with(booking)
|
|
async_session.commit.assert_called_once()
|
|
async_session.rollback.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
|
|
async def test_update_booking_success(
|
|
async_session: AsyncSession, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful update of a booking.
|
|
|
|
Verifies that update_booking updates the booking, commits the session, and
|
|
returns the updated booking.
|
|
"""
|
|
booking_id = 1
|
|
update_params: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": datetime(2025, 8, 24, 13, 0),
|
|
"end_time": datetime(2025, 8, 24, 15, 0),
|
|
}
|
|
mock_execute_result = MagicMock(rowcount=1)
|
|
async_session.execute = AsyncMock(return_value=mock_execute_result)
|
|
async_session.commit = AsyncMock()
|
|
updated_booking = Booking(**update_params)
|
|
updated_booking.id = booking_id
|
|
async_session.scalar = AsyncMock(return_value=updated_booking)
|
|
|
|
result: Booking = await update_booking(async_session, booking_id, **update_params)
|
|
|
|
assert result == updated_booking
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
update(Booking).where(Booking.id == booking_id).values(**update_params)
|
|
)
|
|
async_session.commit.assert_called_once()
|
|
async_session.scalar.assert_called_once()
|
|
assert async_session.scalar.call_args.args[0].compare(
|
|
select(Booking).where(Booking.id == booking_id)
|
|
)
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of non-existent booking in update_booking.
|
|
|
|
Verifies that update_booking raises ValueError when the booking is not found.
|
|
"""
|
|
booking_id = 999
|
|
update_params: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": datetime(2025, 8, 24, 13, 0),
|
|
"end_time": datetime(2025, 8, 24, 15, 0),
|
|
}
|
|
mock_execute_result = MagicMock(rowcount=0)
|
|
async_session.execute = AsyncMock(return_value=mock_execute_result)
|
|
async_session.rollback = AsyncMock()
|
|
|
|
with pytest.raises(ValueError):
|
|
await update_booking(async_session, booking_id, **update_params)
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
update(Booking).where(Booking.id == booking_id).values(**update_params)
|
|
)
|
|
async_session.rollback.assert_called_once()
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in update_booking.
|
|
|
|
Verifies that update_booking rolls back the session on database failure.
|
|
"""
|
|
booking_id = 1
|
|
update_params: dict[str, Any] = {
|
|
"room_id": 2,
|
|
"start_time": datetime(2025, 8, 24, 13, 0),
|
|
"end_time": datetime(2025, 8, 24, 15, 0),
|
|
}
|
|
async_session.execute = AsyncMock(side_effect=SQLAlchemyError("Database error"))
|
|
async_session.rollback = AsyncMock()
|
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
await update_booking(async_session, booking_id, **update_params)
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
update(Booking).where(Booking.id == booking_id).values(**update_params)
|
|
)
|
|
async_session.rollback.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("mock_logger", ["backend.services.bookings"], indirect=True)
|
|
async def test_delete_booking_success(
|
|
async_session: AsyncSession, mock_logger: MagicMock
|
|
) -> None:
|
|
"""Test successful deletion of a booking.
|
|
|
|
Verifies that delete_booking deletes the booking and commits the session.
|
|
"""
|
|
booking_id = 1
|
|
mock_execute_result = MagicMock(rowcount=1)
|
|
async_session.execute = AsyncMock(return_value=mock_execute_result)
|
|
async_session.commit = AsyncMock()
|
|
|
|
await delete_booking(async_session, booking_id)
|
|
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
delete(Booking).where(Booking.id == booking_id)
|
|
)
|
|
async_session.commit.assert_called_once()
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of non-existent booking in delete_booking.
|
|
|
|
Verifies that delete_booking raises ValueError when the booking is not found.
|
|
"""
|
|
booking_id = 999
|
|
mock_execute_result = MagicMock(rowcount=0)
|
|
async_session.execute = AsyncMock(return_value=mock_execute_result)
|
|
async_session.rollback = AsyncMock()
|
|
|
|
with pytest.raises(ValueError):
|
|
await delete_booking(async_session, booking_id)
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
delete(Booking).where(Booking.id == booking_id)
|
|
)
|
|
async_session.rollback.assert_called_once()
|
|
|
|
|
|
@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: MagicMock
|
|
) -> None:
|
|
"""Test handling of database errors in delete_booking.
|
|
|
|
Verifies that delete_booking rolls back the session on database failure.
|
|
"""
|
|
booking_id = 1
|
|
async_session.execute = AsyncMock(side_effect=SQLAlchemyError("Database error"))
|
|
async_session.rollback = AsyncMock()
|
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
await delete_booking(async_session, booking_id)
|
|
async_session.execute.assert_called_once()
|
|
assert async_session.execute.call_args.args[0].compare(
|
|
delete(Booking).where(Booking.id == booking_id)
|
|
)
|
|
async_session.rollback.assert_called_once()
|