Fixing things.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-08-26 17:15:56 -04:00
parent 9f0c77dc31
commit 4b4017d7c6
9 changed files with 71 additions and 82 deletions

View File

@@ -10,11 +10,10 @@ from pydantic import EmailStr
from sqlalchemy.exc import SQLAlchemyError
from backend.dependencies.db import DBSession
from backend.models import User
from backend.models import UserList
from backend.models import Invitee
from backend.models import InviteeList
from backend.schemas.invitees import InviteeCreate
from backend.schemas.invitees import InviteeResponse
from backend.schemas.users import UserResponse
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
@@ -25,8 +24,8 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/invitees", tags=["invitees"])
@router.get("/booking/{booking_id}", response_model=List[UserResponse])
async def read_invitees_for_booking(booking_id: int, session: DBSession) -> UserList:
@router.get("/booking/{booking_id}", response_model=List[InviteeResponse])
async def read_invitees_for_booking(booking_id: int, session: DBSession) -> InviteeList:
"""Retrieve all invitees for a specific booking.
Args:
@@ -71,7 +70,7 @@ async def read_invitees_for_booking(booking_id: int, session: DBSession) -> User
)
async def add_invitee(
booking_id: int, invitee: InviteeCreate, session: DBSession
) -> User:
) -> Invitee:
"""Add an invitee to a booking.
Args:

View File

@@ -7,7 +7,6 @@ including relationships to bookings and users.
from pydantic import BaseModel
from pydantic import ConfigDict
from backend.schemas.bookings import BookingResponse
from backend.schemas.users import UserResponse
@@ -52,12 +51,10 @@ class InviteeResponse(InviteeBase):
(inherited from InviteeBase).
user_email: The email address of the user associated with the invitee
(inherited from InviteeBase).
booking: The BookingResponse object containing details of the associated booking.
user: The UserResponse object containing details of the associated user.
"""
id: int
booking: BookingResponse
user: UserResponse
model_config = ConfigDict(from_attributes=True)

View File

@@ -12,14 +12,15 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Invitee
from backend.models import User
from backend.models import UserList
from backend.models import InviteeList
logger = logging.getLogger(__name__)
async def get_invitees_for_booking(session: AsyncSession, booking_id: int) -> UserList:
async def get_invitees_for_booking(
session: AsyncSession, booking_id: int
) -> InviteeList:
"""Retrieve all invitees for a specific booking.
Args:
@@ -27,16 +28,16 @@ async def get_invitees_for_booking(session: AsyncSession, booking_id: int) -> Us
booking_id: The ID of the booking to retrieve invitees for.
Returns:
A list of User objects associated with the booking as invitees.
A list of Invitee objects associated with the booking as invitees.
Raises:
Exception: Any database error encountered during the query is logged and re-raised.
"""
logger.debug(f"Entering get_invitees_for_booking with booking_id: {booking_id}")
try:
stmt = select(Invitee.user).where(Invitee.booking_id == booking_id)
stmt = select(Invitee).where(Invitee.booking_id == booking_id)
result = await session.scalars(stmt)
invitees = cast(UserList, result.all())
invitees = cast(InviteeList, result.all())
logger.info(
f"Successfully retrieved {len(invitees)} invitees for booking_id: {booking_id}"
)
@@ -52,7 +53,7 @@ async def get_invitees_for_booking(session: AsyncSession, booking_id: int) -> Us
async def add_invitee_to_booking(
session: AsyncSession, booking_id: int, user_email: str
) -> User:
) -> Invitee:
"""Add an invitee to a booking.
Args:
@@ -76,7 +77,7 @@ async def add_invitee_to_booking(
logger.info(
f"Successfully added invitee with email {user_email} to booking_id: {booking_id}"
)
return invitee.user
return invitee
except Exception as e:
logger.error(
f"Failed to add invitee with email {user_email} to"

View File

@@ -186,3 +186,16 @@ def sample_invitees(sample_booking: Booking, sample_users: UserList) -> list[Inv
invitee2.booking = sample_booking
invitee2.user = sample_users[1]
return [invitee1, invitee2]
@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]

View File

@@ -12,13 +12,16 @@ from sqlalchemy.exc import SQLAlchemyError
from backend.models import Booking
from backend.models import Invitee
from backend.services.invitees import UserList
from backend.models import InviteeList
@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
client: TestClient,
sample_booking: Booking,
sample_invitees: InviteeList,
mock_logger: MagicMock,
) -> None:
"""Test successful retrieval of all invitees for a booking.
@@ -27,23 +30,28 @@ async def test_get_invitees_for_booking_success(
Args:
client: The FastAPI test client for making HTTP requests.
sample_users: The mocked list of users to return.
sample_booking: The mocked booking object.
sample_invitees: The mocked list of invitees to return.
mock_logger: The mocked logger instance.
"""
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
mock_get_invitees.return_value = sample_invitees
response = client.get(f"/invitees/booking/{booking_id}")
response = client.get(f"/invitees/booking/{sample_booking.id}")
assert response.status_code == 200
assert response.json() == [
{"email": "user1@example.com", "name": "User One"},
{"email": "user2@example.com", "name": "User Two"},
{
"id": invitee.id,
"booking_id": invitee.booking_id,
"user_email": invitee.user_email,
"user": {"name": invitee.user.name, "email": invitee.user.email},
}
for invitee in sample_invitees
]
mock_get_invitees.assert_called_once_with(ANY, booking_id)
mock_get_invitees.assert_called_once_with(ANY, sample_booking.id)
@pytest.mark.asyncio
@@ -103,8 +111,7 @@ async def test_get_invitees_for_booking_database_error(
@pytest.mark.parametrize("mock_logger", ["backend.routers.invitees"], indirect=True)
async def test_add_invitee_success(
client: TestClient,
sample_booking: Booking,
sample_users: UserList,
sample_invitee: Invitee,
mock_logger: MagicMock,
) -> None:
"""Test successful addition of an invitee to a booking.
@@ -114,44 +121,28 @@ async def test_add_invitee_success(
Args:
client: The FastAPI test client for making HTTP requests.
sample_booking: The mocked booking object.
sample_users: The mocked list of users.
sample_invitee: The mocked invitee object.
mock_logger: The mocked logger instance.
"""
booking_id = 1
invitee_data: dict[str, Any] = {
"booking_id": booking_id,
"user_email": "user1@example.com",
"booking_id": sample_invitee.booking_id,
"user_email": sample_invitee.user_email,
}
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
mock_add_invitee.return_value = sample_invitee
response = client.post(f"/invitees/booking/{booking_id}", json=invitee_data)
response = client.post(
f"/invitees/booking/{sample_invitee.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",
},
}
result = response.json()
assert result["booking_id"] == sample_invitee.booking_id
assert result["user_email"] == sample_invitee.user_email
mock_add_invitee.assert_called_once_with(
ANY, booking_id, invitee_data["user_email"]
ANY, sample_invitee.booking_id, sample_invitee.user_email
)

View File

@@ -14,7 +14,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Booking
from backend.services.bookings import BookingList
from backend.models 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

View File

@@ -10,7 +10,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Invitee
from backend.services.invitees import UserList
from backend.models import InviteeList
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
@@ -19,7 +19,7 @@ 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
async_session: AsyncSession, sample_invitees: InviteeList, mock_logger: MagicMock
) -> None:
"""Test successful retrieval of all invitees for a booking.
@@ -37,14 +37,14 @@ async def test_get_invitees_for_booking_success(
mock_scalars = AsyncMock(return_value=mock_scalars_result)
async_session.scalars = mock_scalars # type: ignore [method-assign]
result: UserList = await get_invitees_for_booking(async_session, booking_id)
result: InviteeList = 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)
select(Invitee).where(Invitee.booking_id == booking_id)
)
@@ -67,13 +67,13 @@ async def test_get_invitees_for_booking_empty(
mock_scalars = AsyncMock(return_value=mock_scalars_result)
async_session.scalars = mock_scalars # type: ignore [method-assign]
result: UserList = await get_invitees_for_booking(async_session, booking_id)
result: InviteeList = 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)
select(Invitee).where(Invitee.booking_id == booking_id)
)
@@ -102,18 +102,9 @@ 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",
[
(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,
sample_invitee: Invitee,
mock_logger: MagicMock,
) -> None:
"""Test successful addition of an invitee to a booking.
@@ -123,22 +114,19 @@ async def test_add_invitee_to_booking_success(
Args:
async_session: The asynchronous database session.
booking_id: The ID of the booking to add the invitee to.
user_email: The email of the user to add as an invitee.
sample_invitee: The mocked invitee object.
mock_logger: The mocked logger instance.
"""
invitee = Invitee(booking_id=booking_id, user_email=user_email)
async_session.add = MagicMock() # type: ignore [method-assign]
async_session.commit = AsyncMock() # type: ignore [method-assign]
result: Invitee = await add_invitee_to_booking(
async_session, booking_id, user_email
async_session, sample_invitee.booking_id, sample_invitee.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)
assert result.booking_id == sample_invitee.booking_id
assert result.user_email == sample_invitee.user_email
async_session.add.assert_called_once()
async_session.commit.assert_called_once()

View File

@@ -13,7 +13,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import Room
from backend.services.rooms import RoomList
from backend.models import RoomList
from backend.services.rooms import delete_room
from backend.services.rooms import get_room
from backend.services.rooms import get_rooms

View File

@@ -10,7 +10,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.models import User
from backend.services.users import UserList
from backend.models import UserList
from backend.services.users import get_user
from backend.services.users import get_users