Fixe (partially) gift tests and api

This commit is contained in:
2025-03-16 15:07:31 +01:00
parent 4ef202cc5a
commit b5f1c7ddcb
3 changed files with 212 additions and 83 deletions

View File

@@ -1,25 +1,25 @@
from typing import List, Optional, Dict, Any, Union
from typing import List, Optional, Dict, Any
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlalchemy.orm import Session
from app.api.dependencies.auth import get_current_active_user, get_current_user
from app.core.database import get_db
from app.crud.event import event_crud
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud, event_gift_category_crud
from app.crud.guest import guest_crud
from app.crud.event import event_crud
from app.models.gift import GiftStatus, GiftPriority, EventGiftCategory, GiftItem
from app.models.gift import GiftStatus, EventGiftCategory, GiftItem as GiftItemModel
from app.models.user import User
from app.schemas.gifts import (
GiftItem, GiftItemCreate, GiftItemUpdate,
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate,
EventGiftCategoryCreate, EventGiftCategoryUpdate, EventGiftCategoryInDB
GiftPurchase, GiftPurchaseCreate, EventGiftCategoryCreate
)
from app.core.database import get_db
router = APIRouter()
# ===== GIFT CATEGORIES ===== #
@router.post("/categories/", response_model=GiftCategory)
@@ -90,19 +90,42 @@ def read_gift_categories(
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
)
# Enhance categories with display information from the association
# Create a list to hold the enhanced category responses
enhanced_categories = []
for category in categories:
# Get the association to access display_order and is_visible
association = event_gift_category_crud.get(
db, event_id=event_id, category_id=category.id
)
# Default values
display_order = None
is_visible = None
if association:
category.display_order = association.display_order
category.is_visible = association.is_visible
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
total_gifts = 0
available_gifts = 0
gifts_list = None
# 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=event_id, category_id=category.id, include_hidden=include_hidden
)
gifts_list = gifts
# Calculate statistics
for gift in gifts:
if gift.event_id == event_id:
total_gifts += 1
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
available_gifts += 1
else:
# Calculate statistics without fetching all gifts
if category.gifts:
for gift in category.gifts:
if gift.event_id == event_id:
@@ -110,17 +133,27 @@ def read_gift_categories(
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
available_gifts += 1
category.total_gifts = total_gifts
category.available_gifts = available_gifts
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# 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)
if gifts_list is not None:
category_data["gifts"] = gifts_list
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
enhanced_category = GiftCategory(**category_data)
enhanced_categories.append(enhanced_category)
# Replace the original categories list with the enhanced one
categories = enhanced_categories
return categories
@@ -141,11 +174,12 @@ def read_gift_category(
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Initialize event-specific properties
category.display_order = None
category.is_visible = None
category.total_gifts = None
category.available_gifts = None
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
gifts_list = None
# If event_id is provided, get event-specific information
if event_id:
@@ -163,9 +197,9 @@ def read_gift_category(
if not association:
raise HTTPException(status_code=404, detail="Category not associated with this event")
# Set event-specific display properties
category.display_order = association.display_order
category.is_visible = association.is_visible
# Get event-specific display properties
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
total_gifts = 0
@@ -177,8 +211,7 @@ def read_gift_category(
db, event_id=event_id, category_id=category.id,
include_hidden=current_user is not None # Only include hidden for logged-in users
)
# Set the gifts attribute
setattr(category, "gifts", gifts)
gifts_list = gifts
# Calculate statistics
for gift in gifts:
@@ -187,23 +220,39 @@ def read_gift_category(
available_gifts += 1
else:
# Calculate statistics without fetching all gifts
gifts_query = db.query(GiftItem).filter(
GiftItem.event_id == event_id,
GiftItem.category_id == category_id
gifts_query = db.query(GiftItemModel).filter(
GiftItemModel.event_id == event_id,
GiftItemModel.category_id == category_id
)
total_gifts = gifts_query.count()
available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
GiftItemModel.status == GiftStatus.AVAILABLE,
GiftItemModel.is_visible == True
).count()
category.total_gifts = total_gifts
category.available_gifts = available_gifts
elif include_gifts:
# If no event_id but include_gifts is true, just get all gifts for this category
# This is less useful without event context but included for completeness
gifts = db.query(GiftItem).filter(GiftItem.category_id == category_id).all()
setattr(category, "gifts", gifts)
gifts_list = gifts
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
if gifts_list is not None:
category_data["gifts"] = gifts_list
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category = GiftCategory(**category_data)
return category
@@ -227,11 +276,11 @@ def update_gift_category(
# Update the category itself
updated_category = gift_category_crud.update(db, db_obj=category, obj_in=category_in)
# Initialize event-specific properties for the response
updated_category.display_order = None
updated_category.is_visible = None
updated_category.total_gifts = None
updated_category.available_gifts = None
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
# If event_id is provided, update the event-specific settings
if event_id:
@@ -268,21 +317,37 @@ def update_gift_category(
db, db_obj=association, obj_in=association_update
)
# Set event-specific properties for the response
updated_category.display_order = association.display_order
updated_category.is_visible = association.is_visible
# Get event-specific properties for the response
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
gifts_query = db.query(GiftItem).filter(
GiftItem.event_id == event_id,
GiftItem.category_id == category_id
)
updated_category.total_gifts = gifts_query.count()
updated_category.available_gifts = gifts_query.filter(
total_gifts = gifts_query.count()
available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
).count()
# Create a new category response with the calculated values
category_data = {
**updated_category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
updated_category = GiftCategory(**category_data)
return updated_category
@@ -303,8 +368,12 @@ def delete_gift_category(
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Make a copy of the category for the response
category_copy = GiftCategory.model_validate(category)
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
gifts_list = None
if event_id:
# Check if event exists
@@ -324,13 +393,23 @@ def delete_gift_category(
# Remove the association
event_gift_category_crud.remove(db, event_id=event_id, category_id=category_id)
# Return the category with event-specific properties set to None
category_copy.display_order = None
category_copy.is_visible = None
category_copy.total_gifts = None
category_copy.available_gifts = None
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
return category_copy
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category_response = GiftCategory(**category_data)
return category_response
elif force:
# Check if the user has permission to delete the category
# This is a more restrictive operation, so we might want to add additional checks
@@ -360,7 +439,25 @@ def delete_gift_category(
event_gift_category_crud.remove(db, event_id=assoc.event_id, category_id=category_id)
# Now delete the category itself
return gift_category_crud.remove(db, id=category_id)
deleted_category = gift_category_crud.remove(db, id=category_id)
# Create a new category response with the calculated values
category_data = {
**deleted_category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category_response = GiftCategory(**category_data)
return category_response
else:
# If no event_id and not force, raise an error
raise HTTPException(
@@ -583,8 +680,9 @@ def create_gift_item(
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:
# Check category belongs to the same event by checking the association
association = event_gift_category_crud.get(db, event_id=item_in.event_id, category_id=item_in.category_id)
if not association:
raise HTTPException(status_code=400, detail="Category does not belong to this event")
return gift_item_crud.create(db, obj_in=item_in)
@@ -619,8 +717,9 @@ def read_gift_items(
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:
# Check category belongs to the requested event by checking the association
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
raise HTTPException(status_code=400, detail="Category does not belong to this event")
return gift_item_crud.get_multi_by_event(
@@ -688,8 +787,9 @@ def update_gift_item(
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:
# Check category belongs to the same event by checking the association
association = event_gift_category_crud.get(db, event_id=gift.event_id, category_id=item_in.category_id)
if not association:
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)

View File

@@ -27,7 +27,6 @@ def gift_category_data(mock_event, mock_user):
"description": "Animal-themed toys for Emma",
"icon": "toy-icon",
"color": "#FF5733",
"event_id": str(mock_event.id),
"created_by": str(mock_user.id)
}
@@ -130,13 +129,21 @@ class TestGiftsRouter:
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)
def test_create_gift_category_success(self, gift_category_data, mock_event):
# Need to provide event_id as a query parameter
response = self.client.post(
f"{self.endpoint}/categories/",
json=gift_category_data,
params={"event_id": str(mock_event.id)}
)
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"]
# Check that display_order and is_visible are set with default values
assert data["display_order"] == 0
assert data["is_visible"] == True
def test_read_gift_category_success(self):
response = self.client.get(f"{self.endpoint}/categories/{self.gift_category.id}")
@@ -174,21 +181,33 @@ class TestGiftsRouter:
assert response.json()["color"] == "#00FF00"
def test_delete_gift_category_success(self):
response = self.client.delete(f"{self.endpoint}/categories/{self.gift_category.id}")
response = self.client.delete(
f"{self.endpoint}/categories/{self.gift_category.id}",
params={"force": True}
)
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)
self.client.post(
f"{self.endpoint}/categories/",
json=gift_category_data,
params={"event_id": str(mock_event.id)}
)
# 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())
# Check that at least one category has display_order and is_visible set
# These properties come from the EventGiftCategory association
assert any(
cat.get("display_order") is not None and cat.get("is_visible") is not None
for cat in response.json()
)
# Gift Purchase Tests
def test_create_gift_purchase_success(self, gift_purchase_data):

View File

@@ -340,17 +340,27 @@ def gift_category_fixture(db_session, mock_user, mock_event):
"""
Fixture to create and return a GiftCategory instance.
"""
# Create the category without event_id and display_order
gift_category = GiftCategory(
id=uuid.uuid4(),
name="Electronics",
description="Category for electronic gifts",
event_id=mock_event.id,
created_by=mock_user.id,
display_order=0,
is_visible=True
created_by=mock_user.id
)
db_session.add(gift_category)
db_session.commit()
# Create the association between the category and the event
from app.models.gift import EventGiftCategory
event_gift_category = EventGiftCategory(
event_id=mock_event.id,
category_id=gift_category.id,
display_order=0,
is_visible=True
)
db_session.add(event_gift_category)
db_session.commit()
return gift_category