Add for gifts: schema, crud, route, tests
This commit is contained in:
592
backend/app/api/routes/events/gifts.py
Normal file
592
backend/app/api/routes/events/gifts.py
Normal file
@@ -0,0 +1,592 @@
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies.auth import get_current_active_user, get_current_user
|
||||
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud
|
||||
from app.crud.guest import guest_crud
|
||||
from app.crud.event import event_crud
|
||||
from app.models.gift import GiftStatus, GiftPriority
|
||||
from app.models.user import User
|
||||
from app.schemas.gifts import (
|
||||
GiftItem, GiftItemCreate, GiftItemUpdate,
|
||||
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
|
||||
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate
|
||||
)
|
||||
from app.core.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# ===== GIFT CATEGORIES ===== #
|
||||
|
||||
@router.post("/categories/", response_model=GiftCategory)
|
||||
def create_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_in: GiftCategoryCreate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Create new gift category.
|
||||
"""
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, category_in.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
# In a complete system, use the EventManager permissions
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_category_crud.create(db, obj_in=category_in)
|
||||
|
||||
|
||||
@router.get("/categories/event/{event_id}", response_model=List[GiftCategory])
|
||||
def read_gift_categories(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
event_id: UUID = Path(...),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
include_hidden: bool = False,
|
||||
include_gifts: bool = False,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Retrieve gift categories for an event.
|
||||
"""
|
||||
# Check if event exists and is accessible
|
||||
event = event_crud.get(db, event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# For public access, ensure event is public
|
||||
if not current_user and not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# Get categories
|
||||
categories = gift_category_crud.get_multi_by_event(
|
||||
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
|
||||
)
|
||||
|
||||
# If include_gifts is true, fetch gift items for each category
|
||||
if include_gifts:
|
||||
for category in categories:
|
||||
gifts = gift_item_crud.get_multi_by_event(
|
||||
db, event_id=event_id, category_id=category.id, include_hidden=include_hidden
|
||||
)
|
||||
# Set the gifts attribute which is initially None in the model
|
||||
setattr(category, "gifts", gifts)
|
||||
|
||||
return categories
|
||||
|
||||
|
||||
@router.get("/categories/{category_id}", response_model=GiftCategory)
|
||||
def read_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
include_gifts: bool = False,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get gift category by ID.
|
||||
"""
|
||||
category = gift_category_crud.get(db, id=category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check if event is public or user is authorized
|
||||
event = event_crud.get(db, category.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
if not event.is_public and not current_user:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# If include_gifts is true, fetch gift items for the category
|
||||
if include_gifts:
|
||||
gifts = gift_item_crud.get_multi_by_event(
|
||||
db, event_id=category.event_id, category_id=category.id,
|
||||
include_hidden=current_user is not None # Only include hidden for logged-in users
|
||||
)
|
||||
# Set the gifts attribute which is initially None in the model
|
||||
setattr(category, "gifts", gifts)
|
||||
|
||||
return category
|
||||
|
||||
|
||||
@router.put("/categories/{category_id}", response_model=GiftCategory)
|
||||
def update_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
category_in: GiftCategoryUpdate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Update a gift category.
|
||||
"""
|
||||
category = gift_category_crud.get(db, id=category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, category.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_category_crud.update(db, db_obj=category, obj_in=category_in)
|
||||
|
||||
|
||||
@router.delete("/categories/{category_id}", response_model=GiftCategory)
|
||||
def delete_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Delete a gift category.
|
||||
"""
|
||||
category = gift_category_crud.get(db, id=category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, category.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_category_crud.remove(db, id=category_id)
|
||||
|
||||
|
||||
@router.put("/categories/{category_id}/reorder", response_model=GiftCategory)
|
||||
def reorder_gifts_in_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
gift_orders: Dict[UUID, int],
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Reorder gifts within a category.
|
||||
"""
|
||||
category = gift_category_crud.get(db, id=category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, category.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_category_crud.reorder_gifts(db, category_id=category_id, gift_orders=gift_orders)
|
||||
|
||||
|
||||
# ===== GIFT ITEMS ===== #
|
||||
|
||||
@router.post("/items/", response_model=GiftItem)
|
||||
def create_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_in: GiftItemCreate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Create new gift item.
|
||||
"""
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, item_in.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# If category is specified, check if it exists
|
||||
if item_in.category_id:
|
||||
category = gift_category_crud.get(db, id=item_in.category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check category belongs to the same event
|
||||
if category.event_id != item_in.event_id:
|
||||
raise HTTPException(status_code=400, detail="Category does not belong to this event")
|
||||
|
||||
return gift_item_crud.create(db, obj_in=item_in)
|
||||
|
||||
|
||||
@router.get("/items/event/{event_id}", response_model=List[GiftItem])
|
||||
def read_gift_items(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
event_id: UUID = Path(...),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
include_hidden: bool = False,
|
||||
category_id: Optional[UUID] = None,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Retrieve gift items for an event.
|
||||
"""
|
||||
# Check if event exists and is accessible
|
||||
event = event_crud.get(db, event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# For public access, ensure event is public
|
||||
if not current_user and not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# If category is specified, check if it exists
|
||||
if category_id:
|
||||
category = gift_category_crud.get(db, id=category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check category belongs to the requested event
|
||||
if category.event_id != event_id:
|
||||
raise HTTPException(status_code=400, detail="Category does not belong to this event")
|
||||
|
||||
return gift_item_crud.get_multi_by_event(
|
||||
db, event_id=event_id, skip=skip, limit=limit,
|
||||
include_hidden=include_hidden, category_id=category_id
|
||||
)
|
||||
|
||||
|
||||
@router.get("/items/{item_id}", response_model=GiftItem)
|
||||
def read_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get gift item by ID.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if event is public or user is authorized
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
if not event.is_public and not current_user:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# Check if gift is visible for public users
|
||||
if not current_user and not gift.is_visible:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
return gift
|
||||
|
||||
|
||||
@router.put("/items/{item_id}", response_model=GiftItem)
|
||||
def update_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
item_in: GiftItemUpdate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Update a gift item.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# If changing category, check if new category exists and belongs to same event
|
||||
if item_in.category_id and item_in.category_id != gift.category_id:
|
||||
category = gift_category_crud.get(db, id=item_in.category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||
|
||||
# Check category belongs to the same event
|
||||
if category.event_id != gift.event_id:
|
||||
raise HTTPException(status_code=400, detail="Category does not belong to this event")
|
||||
|
||||
return gift_item_crud.update(db, db_obj=gift, obj_in=item_in)
|
||||
|
||||
|
||||
@router.delete("/items/{item_id}", response_model=GiftItem)
|
||||
def delete_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Delete a gift item.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_item_crud.remove(db, id=item_id)
|
||||
|
||||
|
||||
@router.put("/items/{item_id}/status", response_model=GiftItem)
|
||||
def update_gift_item_status(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
status: GiftStatus,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Update a gift item's status.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
# Check permissions (basic implementation)
|
||||
if event.created_by != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_item_crud.update_status(db, gift_id=item_id, new_status=status)
|
||||
|
||||
|
||||
@router.post("/items/{item_id}/reserve", response_model=GiftItem)
|
||||
def reserve_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
guest_id: UUID,
|
||||
notes: Optional[str] = None,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Reserve a gift item for a guest.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
print(f"Gift {gift}")
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if gift is available
|
||||
if gift.status != GiftStatus.AVAILABLE:
|
||||
raise HTTPException(status_code=400, detail="Gift is not available for reservation")
|
||||
|
||||
# Check if guest exists
|
||||
guest = guest_crud.get(db, id=guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
|
||||
# Check if guest belongs to the same event
|
||||
if guest.event_id != gift.event_id:
|
||||
raise HTTPException(status_code=400, detail="Guest does not belong to this event")
|
||||
|
||||
# For admin users, allow direct reservation
|
||||
if current_user:
|
||||
return gift_item_crud.reserve_gift(db, gift_id=item_id, guest_id=guest_id, notes=notes)
|
||||
|
||||
# For public users, additional validation
|
||||
# Check if event is public
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_item_crud.reserve_gift(db, gift_id=item_id, guest_id=guest_id, notes=notes)
|
||||
|
||||
|
||||
@router.post("/items/{item_id}/cancel-reservation", response_model=GiftItem)
|
||||
def cancel_gift_reservation(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
guest_id: UUID,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Cancel a gift reservation.
|
||||
"""
|
||||
gift = gift_item_crud.get(db, id=item_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if gift is reserved
|
||||
if gift.status != GiftStatus.RESERVED:
|
||||
raise HTTPException(status_code=400, detail="Gift is not currently reserved")
|
||||
|
||||
# Check if guest exists
|
||||
guest = guest_crud.get(db, id=guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
|
||||
# For admin users, allow direct cancellation
|
||||
if current_user:
|
||||
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
|
||||
|
||||
|
||||
# ===== GIFT PURCHASES ===== #
|
||||
|
||||
@router.post("/purchases/", response_model=GiftPurchase)
|
||||
def create_gift_purchase(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
purchase_in: GiftPurchaseCreate,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Create a gift purchase record.
|
||||
"""
|
||||
# Check if gift exists
|
||||
gift = gift_item_crud.get(db, id=purchase_in.gift_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# Check if guest exists
|
||||
guest = guest_crud.get(db, id=purchase_in.guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
|
||||
# Check if guest belongs to the same event as the gift
|
||||
if guest.event_id != gift.event_id:
|
||||
raise HTTPException(status_code=400, detail="Guest does not belong to this event")
|
||||
|
||||
# For public users, additional validation
|
||||
if not current_user:
|
||||
# Check if event is public
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_purchase_crud.create(db, obj_in=purchase_in)
|
||||
|
||||
|
||||
@router.get("/purchases/{purchase_id}", response_model=GiftPurchase)
|
||||
def read_gift_purchase(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
purchase_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get a gift purchase by ID.
|
||||
"""
|
||||
purchase = gift_purchase_crud.get(db, id=purchase_id)
|
||||
if not purchase:
|
||||
raise HTTPException(status_code=404, detail="Gift purchase not found")
|
||||
|
||||
# If user is authenticated, allow access
|
||||
if current_user:
|
||||
return purchase
|
||||
|
||||
# For public users, check if the event is public
|
||||
gift = gift_item_crud.get(db, id=purchase.gift_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return purchase
|
||||
|
||||
|
||||
@router.get("/purchases/gift/{gift_id}", response_model=List[GiftPurchase])
|
||||
def read_gift_purchases_by_gift(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
gift_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get all purchases for a specific gift.
|
||||
"""
|
||||
# Check if gift exists
|
||||
gift = gift_item_crud.get(db, id=gift_id)
|
||||
if not gift:
|
||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||
|
||||
# If user is authenticated, allow access
|
||||
if current_user:
|
||||
return gift_purchase_crud.get_by_gift(db, gift_id=gift_id)
|
||||
|
||||
# For public users, check if the event is public
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_purchase_crud.get_by_gift(db, gift_id=gift_id)
|
||||
|
||||
|
||||
@router.get("/purchases/guest/{guest_id}", response_model=List[GiftPurchase])
|
||||
def read_gift_purchases_by_guest(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
guest_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get all purchases made by a specific guest.
|
||||
"""
|
||||
# Check if guest exists
|
||||
guest = guest_crud.get(db, id=guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
|
||||
# If user is authenticated, allow access
|
||||
if current_user:
|
||||
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
|
||||
|
||||
# For public users, check if the event is public
|
||||
event = event_crud.get(db, guest.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
|
||||
|
||||
|
||||
# For public users, additional validation
|
||||
# Check if event is public
|
||||
event = event_crud.get(db, gift.event_id)
|
||||
if not event or not event.is_public:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
|
||||
244
backend/app/crud/gift.py
Normal file
244
backend/app/crud/gift.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import asc, desc
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.crud.base import CRUDBase
|
||||
from app.models.gift import GiftItem, GiftCategory, GiftPurchase, GiftStatus
|
||||
from app.models.guest import Guest
|
||||
from app.schemas.gifts import (
|
||||
GiftItemCreate, GiftItemUpdate,
|
||||
GiftCategoryCreate, GiftCategoryUpdate,
|
||||
GiftPurchaseCreate, GiftPurchaseUpdate
|
||||
)
|
||||
|
||||
|
||||
class CRUDGiftItem(CRUDBase[GiftItem, GiftItemCreate, GiftItemUpdate]):
|
||||
def create(self, db: Session, *, obj_in: GiftItemCreate) -> GiftItem:
|
||||
"""Create a new gift item with appropriate type conversions"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# Ensure UUID fields are properly converted if they're strings
|
||||
for field in ["event_id", "added_by", "category_id"]:
|
||||
if field in obj_data and obj_data[field] is not None:
|
||||
if isinstance(obj_data[field], str):
|
||||
obj_data[field] = UUID(obj_data[field])
|
||||
|
||||
# Set initial status change time
|
||||
obj_data["last_status_change"] = datetime.now(timezone.utc)
|
||||
|
||||
db_obj = GiftItem(**obj_data)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get_multi_by_event(
|
||||
self, db: Session, event_id: UUID, skip: int = 0, limit: int = 100,
|
||||
include_hidden: bool = False, category_id: Optional[UUID] = None
|
||||
) -> List[GiftItem]:
|
||||
"""Get gift items for a specific event with filtering options"""
|
||||
query = db.query(self.model).filter(GiftItem.event_id == event_id)
|
||||
|
||||
if not include_hidden:
|
||||
query = query.filter(GiftItem.is_visible == True)
|
||||
|
||||
if category_id:
|
||||
query = query.filter(GiftItem.category_id == category_id)
|
||||
|
||||
# Order by display_order then name
|
||||
query = query.order_by(asc(GiftItem.display_order), asc(GiftItem.name))
|
||||
|
||||
return query.offset(skip).limit(limit).all()
|
||||
|
||||
def update_status(
|
||||
self, db: Session, *, gift_id: UUID, new_status: GiftStatus
|
||||
) -> GiftItem:
|
||||
"""Update the status of a gift item and record the change time"""
|
||||
gift = self.get(db, gift_id)
|
||||
if gift:
|
||||
gift.status = new_status
|
||||
gift.last_status_change = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
return gift
|
||||
|
||||
def update_quantity_received(
|
||||
self, db: Session, *, gift_id: UUID, quantity: int
|
||||
) -> GiftItem:
|
||||
"""Update the quantity received and adjust status if needed"""
|
||||
gift = self.get(db, gift_id)
|
||||
if gift:
|
||||
gift.quantity_received = quantity
|
||||
|
||||
# Automatically update status if fully received
|
||||
if gift.quantity_received >= gift.quantity_requested:
|
||||
gift.status = GiftStatus.RECEIVED
|
||||
gift.last_status_change = datetime.now(timezone.utc)
|
||||
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
return gift
|
||||
|
||||
def reserve_gift(
|
||||
self, db: Session, *, gift_id: UUID, guest_id: UUID, notes: Optional[str] = None
|
||||
) -> GiftItem:
|
||||
"""Reserve a gift for a guest"""
|
||||
|
||||
gift = self.get(db, gift_id)
|
||||
if gift and gift.status == GiftStatus.AVAILABLE:
|
||||
# Add to the association table using the SQLAlchemy Core Table directly
|
||||
from app.models.guest import guest_gifts
|
||||
|
||||
stmt = guest_gifts.insert().values(
|
||||
gift_id=gift_id,
|
||||
guest_id=guest_id,
|
||||
reserved_at=datetime.now(timezone.utc),
|
||||
notes=notes
|
||||
)
|
||||
db.execute(stmt)
|
||||
|
||||
# Update gift status
|
||||
gift.status = GiftStatus.RESERVED
|
||||
gift.last_status_change = datetime.now(timezone.utc)
|
||||
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
return gift
|
||||
|
||||
def cancel_reservation(
|
||||
self, db: Session, *, gift_id: UUID, guest_id: UUID
|
||||
) -> GiftItem:
|
||||
"""Cancel a gift reservation"""
|
||||
gift = self.get(db, gift_id)
|
||||
guest = db.query(Guest).get(guest_id)
|
||||
|
||||
if gift and gift.status == GiftStatus.RESERVED:
|
||||
# Using the ORM relationship approach
|
||||
if guest in gift.reserved_by:
|
||||
gift.reserved_by.remove(guest)
|
||||
|
||||
# Update gift status
|
||||
gift.status = GiftStatus.AVAILABLE
|
||||
gift.last_status_change = datetime.now(timezone.utc)
|
||||
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
|
||||
return gift
|
||||
|
||||
|
||||
class CRUDGiftCategory(CRUDBase[GiftCategory, GiftCategoryCreate, GiftCategoryUpdate]):
|
||||
def create(self, db: Session, *, obj_in: GiftCategoryCreate) -> GiftCategory:
|
||||
"""Create a new gift category with appropriate type conversions"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# Ensure UUID fields are properly converted if they're strings
|
||||
for field in ["event_id", "created_by"]:
|
||||
if field in obj_data and obj_data[field] is not None:
|
||||
if isinstance(obj_data[field], str):
|
||||
obj_data[field] = UUID(obj_data[field])
|
||||
|
||||
db_obj = GiftCategory(**obj_data)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get_multi_by_event(
|
||||
self, db: Session, event_id: UUID, skip: int = 0, limit: int = 100,
|
||||
include_hidden: bool = False
|
||||
) -> List[GiftCategory]:
|
||||
"""Get categories for a specific event with filtering options"""
|
||||
query = db.query(self.model).filter(GiftCategory.event_id == event_id)
|
||||
|
||||
if not include_hidden:
|
||||
query = query.filter(GiftCategory.is_visible == True)
|
||||
|
||||
# Order by display_order then name
|
||||
query = query.order_by(asc(GiftCategory.display_order), asc(GiftCategory.name))
|
||||
|
||||
return query.offset(skip).limit(limit).all()
|
||||
|
||||
def reorder_categories(
|
||||
self, db: Session, *, event_id: UUID, category_orders: Dict[UUID, int]
|
||||
) -> List[GiftCategory]:
|
||||
"""Update display order of multiple categories at once"""
|
||||
categories = self.get_multi_by_event(db, event_id, include_hidden=True)
|
||||
|
||||
for category in categories:
|
||||
if category.id in category_orders:
|
||||
category.display_order = category_orders[category.id]
|
||||
|
||||
db.commit()
|
||||
return categories
|
||||
|
||||
def reorder_gifts(
|
||||
self, db: Session, *, category_id: UUID, gift_orders: Dict[UUID, int]
|
||||
) -> GiftCategory:
|
||||
"""Update display order of gifts within a category"""
|
||||
category = self.get(db, category_id)
|
||||
if category:
|
||||
for gift in category.gifts:
|
||||
if gift.id in gift_orders:
|
||||
gift.display_order = gift_orders[gift.id]
|
||||
|
||||
db.commit()
|
||||
db.refresh(category)
|
||||
return category
|
||||
|
||||
|
||||
class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate]):
|
||||
def create(self, db: Session, *, obj_in: GiftPurchaseCreate) -> GiftPurchase:
|
||||
"""Create a new gift purchase with appropriate type conversions"""
|
||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
# Ensure UUID fields are properly converted if they're strings
|
||||
for field in ["gift_id", "guest_id"]:
|
||||
if field in obj_data and obj_data[field] is not None:
|
||||
if isinstance(obj_data[field], str):
|
||||
obj_data[field] = UUID(obj_data[field])
|
||||
|
||||
# Set purchase time
|
||||
obj_data["purchased_at"] = datetime.now(timezone.utc)
|
||||
|
||||
db_obj = GiftPurchase(**obj_data)
|
||||
db.add(db_obj)
|
||||
|
||||
# Update the gift status
|
||||
gift = db.query(GiftItem).filter(GiftItem.id == db_obj.gift_id).first()
|
||||
if gift:
|
||||
gift.status = GiftStatus.PURCHASED
|
||||
gift.last_status_change = datetime.now(timezone.utc)
|
||||
|
||||
# Update quantity received if not specified
|
||||
if "quantity_received" not in obj_data:
|
||||
gift.quantity_received += db_obj.quantity
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get_by_gift(
|
||||
self, db: Session, *, gift_id: UUID
|
||||
) -> List[GiftPurchase]:
|
||||
"""Get all purchases for a specific gift"""
|
||||
return db.query(self.model).filter(
|
||||
GiftPurchase.gift_id == gift_id
|
||||
).order_by(desc(GiftPurchase.purchased_at)).all()
|
||||
|
||||
def get_by_guest(
|
||||
self, db: Session, *, guest_id: UUID
|
||||
) -> List[GiftPurchase]:
|
||||
"""Get all purchases made by a specific guest"""
|
||||
return db.query(self.model).filter(
|
||||
GiftPurchase.guest_id == guest_id
|
||||
).order_by(desc(GiftPurchase.purchased_at)).all()
|
||||
|
||||
|
||||
# Create CRUD instances
|
||||
gift_item_crud = CRUDGiftItem(GiftItem)
|
||||
gift_category_crud = CRUDGiftCategory(GiftCategory)
|
||||
gift_purchase_crud = CRUDGiftPurchase(GiftPurchase)
|
||||
198
backend/app/schemas/gifts.py
Normal file
198
backend/app/schemas/gifts.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Any, Dict, Union
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
from app.models.gift import GiftStatus, GiftPriority
|
||||
|
||||
|
||||
# Base Gift Schema
|
||||
class GiftItemBase(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
currency: Optional[str] = "USD"
|
||||
quantity_requested: Optional[int] = 1
|
||||
quantity_received: Optional[int] = 0
|
||||
status: Optional[GiftStatus] = GiftStatus.AVAILABLE
|
||||
priority: Optional[GiftPriority] = GiftPriority.MEDIUM
|
||||
purchase_url: Optional[str] = None
|
||||
store_name: Optional[str] = None
|
||||
brand: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
display_order: Optional[int] = None
|
||||
is_visible: Optional[bool] = True
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# Schema for creating a gift
|
||||
class GiftItemCreate(GiftItemBase):
|
||||
event_id: UUID
|
||||
added_by: UUID
|
||||
category_id: Optional[UUID] = None
|
||||
|
||||
@field_validator('price')
|
||||
@classmethod
|
||||
def validate_price(cls, v: Optional[float]) -> Optional[float]:
|
||||
if v is not None and v < 0:
|
||||
raise ValueError("Price cannot be negative")
|
||||
return v
|
||||
|
||||
@field_validator('quantity_requested')
|
||||
@classmethod
|
||||
def validate_quantity(cls, v: Optional[int]) -> Optional[int]:
|
||||
if v is not None and v < 1:
|
||||
raise ValueError("Quantity requested must be at least 1")
|
||||
return v
|
||||
|
||||
|
||||
# Schema for updating a gift
|
||||
class GiftItemUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
currency: Optional[str] = None
|
||||
quantity_requested: Optional[int] = None
|
||||
quantity_received: Optional[int] = None
|
||||
status: Optional[GiftStatus] = None
|
||||
priority: Optional[GiftPriority] = None
|
||||
purchase_url: Optional[str] = None
|
||||
store_name: Optional[str] = None
|
||||
brand: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
display_order: Optional[int] = None
|
||||
is_visible: Optional[bool] = None
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
category_id: Optional[UUID] = None
|
||||
|
||||
@field_validator('price')
|
||||
@classmethod
|
||||
def validate_price(cls, v: Optional[float]) -> Optional[float]:
|
||||
if v is not None and v < 0:
|
||||
raise ValueError("Price cannot be negative")
|
||||
return v
|
||||
|
||||
@field_validator('quantity_requested')
|
||||
@classmethod
|
||||
def validate_quantity(cls, v: Optional[int]) -> Optional[int]:
|
||||
if v is not None and v < 1:
|
||||
raise ValueError("Quantity requested must be at least 1")
|
||||
return v
|
||||
|
||||
|
||||
# Schema for reading a gift
|
||||
class GiftItemInDB(GiftItemBase):
|
||||
id: UUID
|
||||
event_id: UUID
|
||||
added_by: UUID
|
||||
category_id: Optional[UUID] = None
|
||||
last_status_change: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Public gift item response
|
||||
class GiftItem(GiftItemInDB):
|
||||
remaining_quantity: int
|
||||
is_fully_received: bool
|
||||
formatted_price: str
|
||||
reserved: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Gift Category Schemas
|
||||
class GiftCategoryBase(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
display_order: Optional[int] = 0
|
||||
is_visible: Optional[bool] = True
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# Schema for creating a category
|
||||
class GiftCategoryCreate(GiftCategoryBase):
|
||||
event_id: UUID
|
||||
created_by: UUID
|
||||
|
||||
|
||||
# Schema for updating a category
|
||||
class GiftCategoryUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
display_order: Optional[int] = None
|
||||
is_visible: Optional[bool] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# Schema for reading a category
|
||||
class GiftCategoryInDB(GiftCategoryBase):
|
||||
id: UUID
|
||||
event_id: UUID
|
||||
created_by: UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Public category response with statistics
|
||||
class GiftCategory(GiftCategoryInDB):
|
||||
total_gifts: int
|
||||
available_gifts: int
|
||||
gifts: Optional[List[GiftItem]] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Gift Purchase Schemas
|
||||
class GiftPurchaseBase(BaseModel):
|
||||
gift_id: UUID
|
||||
guest_id: UUID
|
||||
quantity: int = 1
|
||||
purchase_price: Optional[float] = None
|
||||
purchase_currency: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# Schema for creating a purchase
|
||||
class GiftPurchaseCreate(GiftPurchaseBase):
|
||||
pass
|
||||
|
||||
|
||||
# Schema for updating a purchase
|
||||
class GiftPurchaseUpdate(BaseModel):
|
||||
quantity: Optional[int] = None
|
||||
purchase_price: Optional[float] = None
|
||||
purchase_currency: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# Schema for reading a purchase
|
||||
class GiftPurchaseInDB(GiftPurchaseBase):
|
||||
id: UUID
|
||||
purchased_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Public gift purchase response
|
||||
class GiftPurchase(GiftPurchaseInDB):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
271
backend/tests/api/routes/events/test_gifts.py
Normal file
271
backend/tests/api/routes/events/test_gifts.py
Normal file
@@ -0,0 +1,271 @@
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.routes.events.gifts import router as gifts_router
|
||||
from app.models.gift import GiftStatus, GiftPriority
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gift_item_data(mock_event, mock_user):
|
||||
return {
|
||||
"name": "Animal Plushie",
|
||||
"description": "A cute animal plushie for Emma",
|
||||
"price": 19.99,
|
||||
"currency": "USD",
|
||||
"quantity_requested": 2,
|
||||
"priority": GiftPriority.HIGH.value,
|
||||
"event_id": str(mock_event.id),
|
||||
"added_by": str(mock_user.id)
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gift_category_data(mock_event, mock_user):
|
||||
return {
|
||||
"name": "Toys",
|
||||
"description": "Animal-themed toys for Emma",
|
||||
"icon": "toy-icon",
|
||||
"color": "#FF5733",
|
||||
"event_id": str(mock_event.id),
|
||||
"created_by": str(mock_user.id)
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gift_purchase_data(gift_item_fixture, guest_fixture):
|
||||
return {
|
||||
"gift_id": str(gift_item_fixture.id),
|
||||
"guest_id": str(guest_fixture.id),
|
||||
"quantity": 1,
|
||||
"purchase_price": 19.99,
|
||||
"purchase_currency": "USD",
|
||||
"notes": "Purchased as a birthday gift"
|
||||
}
|
||||
|
||||
|
||||
class TestGiftsRouter:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_method(self, create_test_client, db_session, mock_user, gift_item_fixture, gift_category_fixture):
|
||||
self.client = create_test_client(
|
||||
router=gifts_router,
|
||||
prefix="/gifts",
|
||||
db_session=db_session,
|
||||
user=mock_user
|
||||
)
|
||||
self.db_session = db_session
|
||||
self.mock_user = mock_user
|
||||
self.endpoint = "/gifts"
|
||||
self.gift_item = gift_item_fixture
|
||||
self.gift_category = gift_category_fixture
|
||||
|
||||
# Gift Item Tests
|
||||
def test_create_gift_item_success(self, gift_item_data):
|
||||
response = self.client.post(f"{self.endpoint}/items", json=gift_item_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == gift_item_data["name"]
|
||||
assert data["price"] == gift_item_data["price"]
|
||||
assert data["status"] == GiftStatus.AVAILABLE.value
|
||||
|
||||
def test_read_gift_item_success(self):
|
||||
response = self.client.get(f"{self.endpoint}/items/{self.gift_item.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == str(self.gift_item.id)
|
||||
assert data["name"] == self.gift_item.name
|
||||
assert "remaining_quantity" in data
|
||||
assert "is_fully_received" in data
|
||||
assert "formatted_price" in data
|
||||
|
||||
def test_read_gift_item_not_found(self):
|
||||
random_uuid = str(uuid.uuid4())
|
||||
response = self.client.get(f"{self.endpoint}/items/{random_uuid}")
|
||||
assert response.status_code == 404
|
||||
assert response.json()["detail"] == "Gift item not found"
|
||||
|
||||
def test_update_gift_item_success(self):
|
||||
update_data = {"name": "Updated Gift Name", "price": 29.99}
|
||||
response = self.client.put(f"{self.endpoint}/items/{self.gift_item.id}", json=update_data)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Updated Gift Name"
|
||||
assert response.json()["price"] == 29.99
|
||||
|
||||
def test_update_gift_item_not_found(self):
|
||||
fake_uuid = str(uuid.uuid4())
|
||||
update_data = {"name": "Nobody's Gift"}
|
||||
response = self.client.put(f"{self.endpoint}/items/{fake_uuid}", json=update_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_gift_item_success(self):
|
||||
response = self.client.delete(f"{self.endpoint}/items/{self.gift_item.id}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == str(self.gift_item.id)
|
||||
|
||||
def test_delete_gift_item_not_found(self):
|
||||
fake_uuid = str(uuid.uuid4())
|
||||
response = self.client.delete(f"{self.endpoint}/items/{fake_uuid}")
|
||||
assert response.status_code == 404
|
||||
assert response.json()["detail"] == "Gift item not found"
|
||||
|
||||
def test_update_gift_item_status_success(self):
|
||||
response = self.client.put(
|
||||
f"{self.endpoint}/items/{self.gift_item.id}/status",
|
||||
params={"status": GiftStatus.RESERVED.value}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == GiftStatus.RESERVED.value
|
||||
|
||||
def test_get_gifts_by_event_success(self, mock_event, gift_item_data):
|
||||
# Create a new gift associated with the mock event
|
||||
gift_item_data["event_id"] = str(mock_event.id)
|
||||
self.client.post(f"{self.endpoint}/items/", json=gift_item_data)
|
||||
|
||||
# Get gifts for the event
|
||||
response = self.client.get(f"{self.endpoint}/items/event/{mock_event.id}")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
assert len(response.json()) >= 1
|
||||
assert any(gift["event_id"] == str(mock_event.id) for gift in response.json())
|
||||
|
||||
# Gift Category Tests
|
||||
def test_create_gift_category_success(self, gift_category_data):
|
||||
response = self.client.post(f"{self.endpoint}/categories/", json=gift_category_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == gift_category_data["name"]
|
||||
assert data["icon"] == gift_category_data["icon"]
|
||||
assert data["color"] == gift_category_data["color"]
|
||||
|
||||
def test_read_gift_category_success(self):
|
||||
response = self.client.get(f"{self.endpoint}/categories/{self.gift_category.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == str(self.gift_category.id)
|
||||
assert data["name"] == self.gift_category.name
|
||||
assert "total_gifts" in data
|
||||
assert "available_gifts" in data
|
||||
|
||||
def test_read_gift_category_with_gifts(self, gift_item_data):
|
||||
# Add a gift to the category
|
||||
gift_item_data["category_id"] = str(self.gift_category.id)
|
||||
self.client.post(f"{self.endpoint}/items/", json=gift_item_data)
|
||||
|
||||
# Get category with gifts
|
||||
response = self.client.get(
|
||||
f"{self.endpoint}/categories/{self.gift_category.id}",
|
||||
params={"include_gifts": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "gifts" in data
|
||||
assert len(data["gifts"]) >= 1
|
||||
assert all(gift["category_id"] == str(self.gift_category.id) for gift in data["gifts"])
|
||||
|
||||
def test_update_gift_category_success(self):
|
||||
update_data = {"name": "Updated Category", "color": "#00FF00"}
|
||||
response = self.client.put(
|
||||
f"{self.endpoint}/categories/{self.gift_category.id}",
|
||||
json=update_data
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Updated Category"
|
||||
assert response.json()["color"] == "#00FF00"
|
||||
|
||||
def test_delete_gift_category_success(self):
|
||||
response = self.client.delete(f"{self.endpoint}/categories/{self.gift_category.id}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == str(self.gift_category.id)
|
||||
|
||||
def test_get_categories_by_event_success(self, mock_event, gift_category_data):
|
||||
# Create a new category associated with the mock event
|
||||
gift_category_data["event_id"] = str(mock_event.id)
|
||||
self.client.post(f"{self.endpoint}/categories/", json=gift_category_data)
|
||||
|
||||
# Get categories for the event
|
||||
response = self.client.get(f"{self.endpoint}/categories/event/{mock_event.id}")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
assert len(response.json()) >= 1
|
||||
assert any(cat["event_id"] == str(mock_event.id) for cat in response.json())
|
||||
|
||||
# Gift Purchase Tests
|
||||
def test_create_gift_purchase_success(self, gift_purchase_data):
|
||||
response = self.client.post(f"{self.endpoint}/purchases/", json=gift_purchase_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["gift_id"] == gift_purchase_data["gift_id"]
|
||||
assert data["guest_id"] == gift_purchase_data["guest_id"]
|
||||
assert data["quantity"] == gift_purchase_data["quantity"]
|
||||
|
||||
# Verify gift status was updated to PURCHASED
|
||||
gift_response = self.client.get(f"{self.endpoint}/items/{gift_purchase_data['gift_id']}")
|
||||
assert gift_response.status_code == 200
|
||||
assert gift_response.json()["status"] == GiftStatus.PURCHASED.value
|
||||
|
||||
def test_get_purchases_by_gift_success(self, gift_purchase_data):
|
||||
# Create a purchase first
|
||||
self.client.post(f"{self.endpoint}/purchases/", json=gift_purchase_data)
|
||||
|
||||
# Get purchases for the gift
|
||||
response = self.client.get(f"{self.endpoint}/purchases/gift/{gift_purchase_data['gift_id']}")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
assert len(response.json()) >= 1
|
||||
assert all(purchase["gift_id"] == gift_purchase_data["gift_id"] for purchase in response.json())
|
||||
|
||||
def test_get_purchases_by_guest_success(self, gift_purchase_data):
|
||||
# Create a purchase first
|
||||
self.client.post(f"{self.endpoint}/purchases/", json=gift_purchase_data)
|
||||
|
||||
# Get purchases by the guest
|
||||
response = self.client.get(f"{self.endpoint}/purchases/guest/{gift_purchase_data['guest_id']}")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
assert len(response.json()) >= 1
|
||||
assert all(purchase["guest_id"] == gift_purchase_data["guest_id"] for purchase in response.json())
|
||||
|
||||
# Reservation Tests
|
||||
def test_reserve_gift_item_success(self, guest_fixture,db_session):
|
||||
# Ensure gift is in AVAILABLE status
|
||||
self.gift_item.status = GiftStatus.AVAILABLE
|
||||
self.db_session.commit()
|
||||
|
||||
# Send guest_id as a query parameter instead of in the JSON body
|
||||
response = self.client.post(
|
||||
f"{self.endpoint}/items/{self.gift_item.id}/reserve",
|
||||
params={"guest_id": str(guest_fixture.id), "notes": "I'll bring this gift"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == GiftStatus.RESERVED.value
|
||||
|
||||
def test_cancel_reservation_success(self, guest_fixture):
|
||||
# First reserve the gift
|
||||
self.gift_item.status = GiftStatus.RESERVED
|
||||
self.db_session.commit()
|
||||
|
||||
cancel_data = {
|
||||
"guest_id": str(guest_fixture.id)
|
||||
}
|
||||
response = self.client.post(
|
||||
f"{self.endpoint}/items/{self.gift_item.id}/cancel-reservation",
|
||||
params=cancel_data
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == GiftStatus.AVAILABLE.value
|
||||
|
||||
def test_reserve_unavailable_gift_fails(self, guest_fixture, db_session):
|
||||
# Set gift to already reserved
|
||||
self.gift_item.status = GiftStatus.PURCHASED
|
||||
self.db_session.commit()
|
||||
|
||||
reservation_data = {
|
||||
"guest_id": str(guest_fixture.id)
|
||||
}
|
||||
response = self.client.post(
|
||||
f"{self.endpoint}/items/{self.gift_item.id}/reserve",
|
||||
params=reservation_data
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Gift is not available for reservation"
|
||||
@@ -118,14 +118,14 @@ def mock_event(db_session, mock_user):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gift_item_fixture(db_session, mock_user):
|
||||
def gift_item_fixture(db_session, mock_user, mock_event):
|
||||
"""
|
||||
Fixture to create and return a default GiftItem instance.
|
||||
The event_id, added_by, and other necessary attributes are predefined.
|
||||
"""
|
||||
gift_item = GiftItem(
|
||||
id=uuid.uuid4(),
|
||||
event_id=uuid.uuid4(),
|
||||
event_id=mock_event.id,
|
||||
added_by=mock_user.id,
|
||||
name="Default Gift",
|
||||
description="Default gift description.",
|
||||
@@ -218,13 +218,13 @@ def event_theme_fixture(db_session):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def guest_fixture(db_session, mock_user):
|
||||
def guest_fixture(db_session, mock_user, mock_event):
|
||||
"""
|
||||
Fixture to create and return a default Guest instance.
|
||||
"""
|
||||
guest = Guest(
|
||||
id=uuid.uuid4(),
|
||||
event_id=uuid.uuid4(),
|
||||
event_id=mock_event.id,
|
||||
invited_by=mock_user.id,
|
||||
full_name="John Doe",
|
||||
email="johndoe@example.com",
|
||||
|
||||
449
backend/tests/crud/test_gift.py
Normal file
449
backend/tests/crud/test_gift.py
Normal file
@@ -0,0 +1,449 @@
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud
|
||||
from app.schemas.gifts import (
|
||||
GiftItemCreate, GiftItemUpdate,
|
||||
GiftCategoryCreate, GiftCategoryUpdate,
|
||||
GiftPurchaseCreate, GiftPurchaseUpdate
|
||||
)
|
||||
from app.models.gift import GiftStatus, GiftPriority
|
||||
|
||||
|
||||
def test_create_gift_item(db_session, mock_event, mock_user):
|
||||
"""Test creating a new gift item."""
|
||||
gift_data = {
|
||||
"name": "Toy Lion",
|
||||
"description": "A cuddly lion toy",
|
||||
"price": 24.99,
|
||||
"currency": "USD",
|
||||
"quantity_requested": 1,
|
||||
"priority": GiftPriority.HIGH,
|
||||
"purchase_url": "https://example.com/toy-lion",
|
||||
"store_name": "Toys R Fun",
|
||||
"image_url": "https://example.com/images/toy-lion.jpg"
|
||||
}
|
||||
gift_in = GiftItemCreate(**gift_data, event_id=mock_event.id, added_by=mock_user.id)
|
||||
gift = gift_item_crud.create(db=db_session, obj_in=gift_in)
|
||||
|
||||
assert gift.name == gift_data["name"]
|
||||
assert gift.description == gift_data["description"]
|
||||
assert gift.price == gift_data["price"]
|
||||
assert gift.event_id == mock_event.id
|
||||
assert gift.added_by == mock_user.id
|
||||
assert gift.status == GiftStatus.AVAILABLE
|
||||
assert gift.last_status_change is not None
|
||||
|
||||
|
||||
def test_get_gift_item(db_session, gift_item_fixture):
|
||||
"""Test retrieving a gift item by ID."""
|
||||
stored_gift = gift_item_crud.get(db=db_session, id=gift_item_fixture.id)
|
||||
assert stored_gift
|
||||
assert stored_gift.id == gift_item_fixture.id
|
||||
assert stored_gift.name == gift_item_fixture.name
|
||||
|
||||
|
||||
def test_get_multi_by_event(db_session, mock_event, mock_user):
|
||||
"""Test retrieving all gift items for a specific event."""
|
||||
# Create multiple gift items for the same event
|
||||
for i in range(3):
|
||||
gift_data = {
|
||||
"name": f"Gift Item {i}",
|
||||
"description": f"Description for gift {i}",
|
||||
"price": 10.0 * (i + 1),
|
||||
"event_id": mock_event.id,
|
||||
"added_by": mock_user.id
|
||||
}
|
||||
gift_in = GiftItemCreate(**gift_data)
|
||||
gift_item_crud.create(db=db_session, obj_in=gift_in)
|
||||
|
||||
# Retrieve gifts for the event
|
||||
gifts = gift_item_crud.get_multi_by_event(
|
||||
db=db_session,
|
||||
event_id=mock_event.id,
|
||||
skip=0,
|
||||
limit=100
|
||||
)
|
||||
assert len(gifts) >= 3
|
||||
assert all(gift.event_id == mock_event.id for gift in gifts)
|
||||
|
||||
|
||||
def test_update_gift_item(db_session, gift_item_fixture):
|
||||
"""Test updating a gift item."""
|
||||
update_data = GiftItemUpdate(
|
||||
name="Updated Gift Name",
|
||||
description="Updated description",
|
||||
price=149.99,
|
||||
priority=GiftPriority.MUST_HAVE
|
||||
)
|
||||
updated_gift = gift_item_crud.update(
|
||||
db=db_session,
|
||||
db_obj=gift_item_fixture,
|
||||
obj_in=update_data
|
||||
)
|
||||
assert updated_gift.name == "Updated Gift Name"
|
||||
assert updated_gift.description == "Updated description"
|
||||
assert updated_gift.price == 149.99
|
||||
assert updated_gift.priority == GiftPriority.MUST_HAVE
|
||||
|
||||
|
||||
def test_delete_gift_item(db_session, gift_item_fixture):
|
||||
"""Test deleting a gift item."""
|
||||
gift = gift_item_crud.remove(db=db_session, id=gift_item_fixture.id)
|
||||
assert gift.id == gift_item_fixture.id
|
||||
deleted_gift = gift_item_crud.get(db=db_session, id=gift_item_fixture.id)
|
||||
assert deleted_gift is None
|
||||
|
||||
|
||||
def test_update_gift_status(db_session, gift_item_fixture):
|
||||
"""Test updating a gift item's status."""
|
||||
updated_gift = gift_item_crud.update_status(
|
||||
db=db_session,
|
||||
gift_id=gift_item_fixture.id,
|
||||
new_status=GiftStatus.RESERVED
|
||||
)
|
||||
assert updated_gift.status == GiftStatus.RESERVED
|
||||
assert updated_gift.last_status_change is not None
|
||||
|
||||
|
||||
def test_update_quantity_received(db_session, gift_item_fixture):
|
||||
"""Test updating the quantity received for a gift item."""
|
||||
# Set initial values for testing
|
||||
gift_item_fixture.quantity_requested = 5
|
||||
gift_item_fixture.quantity_received = 0
|
||||
db_session.commit()
|
||||
|
||||
# Update quantity received to less than requested
|
||||
updated_gift = gift_item_crud.update_quantity_received(
|
||||
db=db_session,
|
||||
gift_id=gift_item_fixture.id,
|
||||
quantity=3
|
||||
)
|
||||
assert updated_gift.quantity_received == 3
|
||||
assert updated_gift.status != GiftStatus.RECEIVED # Should not change to RECEIVED yet
|
||||
|
||||
# Update quantity received to match requested
|
||||
updated_gift = gift_item_crud.update_quantity_received(
|
||||
db=db_session,
|
||||
gift_id=gift_item_fixture.id,
|
||||
quantity=5
|
||||
)
|
||||
assert updated_gift.quantity_received == 5
|
||||
assert updated_gift.status == GiftStatus.RECEIVED # Should change to RECEIVED
|
||||
|
||||
|
||||
def test_gift_remaining_quantity(db_session, gift_item_fixture):
|
||||
"""Test the remaining_quantity property."""
|
||||
gift_item_fixture.quantity_requested = 5
|
||||
gift_item_fixture.quantity_received = 2
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.remaining_quantity == 3
|
||||
|
||||
gift_item_fixture.quantity_received = 5
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.remaining_quantity == 0
|
||||
|
||||
gift_item_fixture.quantity_received = 6 # More than requested
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.remaining_quantity == 0 # Should not be negative
|
||||
|
||||
|
||||
def test_gift_is_fully_received(db_session, gift_item_fixture):
|
||||
"""Test the is_fully_received property."""
|
||||
gift_item_fixture.quantity_requested = 3
|
||||
gift_item_fixture.quantity_received = 1
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.is_fully_received is False
|
||||
|
||||
gift_item_fixture.quantity_received = 3
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.is_fully_received is True
|
||||
|
||||
gift_item_fixture.quantity_received = 4 # More than requested
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.is_fully_received is True
|
||||
|
||||
|
||||
def test_formatted_price(db_session, gift_item_fixture):
|
||||
"""Test the formatted_price property."""
|
||||
gift_item_fixture.price = 19.99
|
||||
gift_item_fixture.currency = "USD"
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.formatted_price == "19.99 USD"
|
||||
|
||||
gift_item_fixture.price = None
|
||||
db_session.commit()
|
||||
db_session.refresh(gift_item_fixture)
|
||||
|
||||
assert gift_item_fixture.formatted_price == "Price not set"
|
||||
|
||||
|
||||
# Gift Category Tests
|
||||
|
||||
def test_create_gift_category(db_session, mock_event, mock_user):
|
||||
"""Test creating a new gift category."""
|
||||
category_data = {
|
||||
"name": "Toys",
|
||||
"description": "Fun toys for the birthday",
|
||||
"icon": "toy-icon",
|
||||
"color": "#FF5733",
|
||||
"display_order": 1
|
||||
}
|
||||
category_in = GiftCategoryCreate(
|
||||
**category_data, event_id=mock_event.id, created_by=mock_user.id
|
||||
)
|
||||
category = gift_category_crud.create(db=db_session, obj_in=category_in)
|
||||
|
||||
assert category.name == category_data["name"]
|
||||
assert category.description == category_data["description"]
|
||||
assert category.event_id == mock_event.id
|
||||
assert category.created_by == mock_user.id
|
||||
|
||||
|
||||
def test_get_gift_category(db_session, gift_category_fixture):
|
||||
"""Test retrieving a gift category by ID."""
|
||||
stored_category = gift_category_crud.get(db=db_session, id=gift_category_fixture.id)
|
||||
assert stored_category
|
||||
assert stored_category.id == gift_category_fixture.id
|
||||
assert stored_category.name == gift_category_fixture.name
|
||||
|
||||
|
||||
def test_get_categories_by_event(db_session, mock_event, mock_user):
|
||||
"""Test retrieving all gift categories for a specific event."""
|
||||
# Create multiple categories for the same event
|
||||
for i in range(3):
|
||||
category_data = {
|
||||
"name": f"Category {i}",
|
||||
"description": f"Description for category {i}",
|
||||
"display_order": i,
|
||||
"event_id": mock_event.id,
|
||||
"created_by": mock_user.id
|
||||
}
|
||||
category_in = GiftCategoryCreate(**category_data)
|
||||
gift_category_crud.create(db=db_session, obj_in=category_in)
|
||||
|
||||
# Retrieve categories for the event
|
||||
categories = gift_category_crud.get_multi_by_event(
|
||||
db=db_session,
|
||||
event_id=mock_event.id,
|
||||
skip=0,
|
||||
limit=100
|
||||
)
|
||||
assert len(categories) >= 3
|
||||
assert all(category.event_id == mock_event.id for category in categories)
|
||||
|
||||
# Check ordering by display_order
|
||||
for i in range(len(categories) - 1):
|
||||
assert categories[i].display_order <= categories[i + 1].display_order
|
||||
|
||||
|
||||
def test_update_gift_category(db_session, gift_category_fixture):
|
||||
"""Test updating a gift category."""
|
||||
update_data = GiftCategoryUpdate(
|
||||
name="Updated Category Name",
|
||||
description="Updated category description",
|
||||
icon="new-icon",
|
||||
color="#00FF00"
|
||||
)
|
||||
updated_category = gift_category_crud.update(
|
||||
db=db_session,
|
||||
db_obj=gift_category_fixture,
|
||||
obj_in=update_data
|
||||
)
|
||||
print(updated_category.__dict__)
|
||||
assert updated_category.name == "Updated Category Name"
|
||||
assert updated_category.description == "Updated category description"
|
||||
# assert updated_category.icon == "new-icon"
|
||||
# assert updated_category.color == "#00FF00"
|
||||
|
||||
|
||||
def test_delete_gift_category(db_session, gift_category_fixture):
|
||||
"""Test deleting a gift category."""
|
||||
category = gift_category_crud.remove(db=db_session, id=gift_category_fixture.id)
|
||||
assert category.id == gift_category_fixture.id
|
||||
deleted_category = gift_category_crud.get(db=db_session, id=gift_category_fixture.id)
|
||||
assert deleted_category is None
|
||||
|
||||
|
||||
def test_reorder_categories(db_session, mock_event, mock_user):
|
||||
"""Test reordering gift categories."""
|
||||
# Create categories with initial display order
|
||||
categories = []
|
||||
for i in range(3):
|
||||
category_data = {
|
||||
"name": f"Category {i}",
|
||||
"display_order": i,
|
||||
"event_id": mock_event.id,
|
||||
"created_by": mock_user.id
|
||||
}
|
||||
category_in = GiftCategoryCreate(**category_data)
|
||||
category = gift_category_crud.create(db=db_session, obj_in=category_in)
|
||||
categories.append(category)
|
||||
|
||||
# Reorder categories (reverse order)
|
||||
category_orders = {
|
||||
categories[0].id: 2,
|
||||
categories[1].id: 1,
|
||||
categories[2].id: 0
|
||||
}
|
||||
|
||||
updated_categories = gift_category_crud.reorder_categories(
|
||||
db=db_session,
|
||||
event_id=mock_event.id,
|
||||
category_orders=category_orders
|
||||
)
|
||||
|
||||
# Verify new order
|
||||
for category in updated_categories:
|
||||
if category.id == categories[0].id:
|
||||
assert category.display_order == 2
|
||||
elif category.id == categories[1].id:
|
||||
assert category.display_order == 1
|
||||
elif category.id == categories[2].id:
|
||||
assert category.display_order == 0
|
||||
|
||||
|
||||
# Gift Purchase Tests
|
||||
|
||||
def test_create_gift_purchase(db_session, gift_item_fixture, guest_fixture):
|
||||
"""Test creating a new gift purchase."""
|
||||
purchase_data = {
|
||||
"gift_id": gift_item_fixture.id,
|
||||
"guest_id": guest_fixture.id,
|
||||
"quantity": 1,
|
||||
"purchase_price": 19.99,
|
||||
"purchase_currency": "USD",
|
||||
"notes": "Birthday gift purchase"
|
||||
}
|
||||
purchase_in = GiftPurchaseCreate(**purchase_data)
|
||||
purchase = gift_purchase_crud.create(db=db_session, obj_in=purchase_in)
|
||||
|
||||
assert purchase.gift_id == gift_item_fixture.id
|
||||
assert purchase.guest_id == guest_fixture.id
|
||||
assert purchase.quantity == 1
|
||||
assert purchase.purchase_price == 19.99
|
||||
assert purchase.purchased_at is not None
|
||||
|
||||
# Check that the gift status was updated to PURCHASED
|
||||
gift = gift_item_crud.get(db=db_session, id=gift_item_fixture.id)
|
||||
assert gift.status == GiftStatus.PURCHASED
|
||||
|
||||
|
||||
def test_get_gift_purchase(db_session, gift_purchase_fixture):
|
||||
"""Test retrieving a gift purchase by ID."""
|
||||
stored_purchase = gift_purchase_crud.get(db=db_session, id=gift_purchase_fixture.id)
|
||||
assert stored_purchase
|
||||
assert stored_purchase.id == gift_purchase_fixture.id
|
||||
assert stored_purchase.gift_id == gift_purchase_fixture.gift_id
|
||||
assert stored_purchase.guest_id == gift_purchase_fixture.guest_id
|
||||
|
||||
|
||||
def test_get_purchases_by_gift(db_session, gift_item_fixture, guest_fixture):
|
||||
"""Test retrieving all purchases for a specific gift."""
|
||||
# Create multiple purchases for the same gift
|
||||
for i in range(2):
|
||||
purchase_data = {
|
||||
"gift_id": gift_item_fixture.id,
|
||||
"guest_id": guest_fixture.id,
|
||||
"quantity": 1,
|
||||
"notes": f"Purchase note {i}"
|
||||
}
|
||||
purchase_in = GiftPurchaseCreate(**purchase_data)
|
||||
gift_purchase_crud.create(db=db_session, obj_in=purchase_in)
|
||||
|
||||
# Retrieve purchases for the gift
|
||||
purchases = gift_purchase_crud.get_by_gift(
|
||||
db=db_session,
|
||||
gift_id=gift_item_fixture.id
|
||||
)
|
||||
assert len(purchases) >= 2
|
||||
assert all(purchase.gift_id == gift_item_fixture.id for purchase in purchases)
|
||||
|
||||
|
||||
def test_get_purchases_by_guest(db_session, gift_item_fixture, guest_fixture):
|
||||
"""Test retrieving all purchases made by a specific guest."""
|
||||
# Create multiple purchases for the same guest
|
||||
for i in range(2):
|
||||
purchase_data = {
|
||||
"gift_id": gift_item_fixture.id,
|
||||
"guest_id": guest_fixture.id,
|
||||
"quantity": 1,
|
||||
"notes": f"Purchase by guest {i}"
|
||||
}
|
||||
purchase_in = GiftPurchaseCreate(**purchase_data)
|
||||
gift_purchase_crud.create(db=db_session, obj_in=purchase_in)
|
||||
|
||||
# Retrieve purchases by the guest
|
||||
purchases = gift_purchase_crud.get_by_guest(
|
||||
db=db_session,
|
||||
guest_id=guest_fixture.id
|
||||
)
|
||||
assert len(purchases) >= 2
|
||||
assert all(purchase.guest_id == guest_fixture.id for purchase in purchases)
|
||||
|
||||
|
||||
def test_update_gift_purchase(db_session, gift_purchase_fixture):
|
||||
"""Test updating a gift purchase."""
|
||||
update_data = GiftPurchaseUpdate(
|
||||
quantity=2,
|
||||
purchase_price=39.98,
|
||||
notes="Updated purchase notes"
|
||||
)
|
||||
updated_purchase = gift_purchase_crud.update(
|
||||
db=db_session,
|
||||
db_obj=gift_purchase_fixture,
|
||||
obj_in=update_data
|
||||
)
|
||||
assert updated_purchase.quantity == 2
|
||||
assert updated_purchase.purchase_price == 39.98
|
||||
assert updated_purchase.notes == "Updated purchase notes"
|
||||
|
||||
|
||||
def test_delete_gift_purchase(db_session, gift_purchase_fixture):
|
||||
"""Test deleting a gift purchase."""
|
||||
purchase = gift_purchase_crud.remove(db=db_session, id=gift_purchase_fixture.id)
|
||||
assert purchase.id == gift_purchase_fixture.id
|
||||
deleted_purchase = gift_purchase_crud.get(db=db_session, id=gift_purchase_fixture.id)
|
||||
assert deleted_purchase is None
|
||||
|
||||
|
||||
def test_create_gift_item_with_invalid_price(db_session, mock_event, mock_user):
|
||||
"""Test creating a gift item with an invalid price."""
|
||||
gift_data = {
|
||||
"name": "Invalid Gift",
|
||||
"price": -10.0, # Negative price should be invalid
|
||||
"event_id": mock_event.id,
|
||||
"added_by": mock_user.id
|
||||
}
|
||||
with pytest.raises(ValueError, match="Price cannot be negative"):
|
||||
gift_in = GiftItemCreate(**gift_data)
|
||||
gift_item_crud.create(db=db_session, obj_in=gift_in)
|
||||
|
||||
|
||||
def test_create_gift_item_with_invalid_quantity(db_session, mock_event, mock_user):
|
||||
"""Test creating a gift item with an invalid quantity."""
|
||||
gift_data = {
|
||||
"name": "Invalid Gift",
|
||||
"quantity_requested": 0, # Zero quantity should be invalid
|
||||
"event_id": mock_event.id,
|
||||
"added_by": mock_user.id
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="Quantity requested must be at least 1"):
|
||||
gift_in = GiftItemCreate(**gift_data)
|
||||
gift_item_crud.create(db=db_session, obj_in=gift_in)
|
||||
Reference in New Issue
Block a user