Add comprehensive test cases for theme creation API
All checks were successful
Build and Push Docker Images / changes (push) Successful in 4s
Build and Push Docker Images / build-backend (push) Successful in 50s
Build and Push Docker Images / build-frontend (push) Has been skipped

Implemented various tests to validate the theme creation process, including tests for successful creation, validation failures, image relocation, large data handling, and error scenarios such as database issues. Introduced mock dependencies to simulate behaviors like file relocation and database operations. Ensured all edge cases are covered to improve robustness and reliability.
This commit is contained in:
2025-03-13 07:32:22 +01:00
parent 0eabd9e5dd
commit 03f643895d

View File

@@ -4,7 +4,9 @@ from fastapi import status
import pytest
from app.api.dependencies.common import get_storage_provider
from app.api.routes.event_themes import router as themes_router
from unittest.mock import patch, MagicMock
class TestCreateEventTheme:
@@ -35,3 +37,533 @@ class TestCreateEventTheme:
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
def test_create_theme_unauthorized_user(self, create_test_client, db_session, theme_data):
"""Test that unauthenticated users cannot create themes."""
# Create a client with no authenticated user
client = create_test_client(
router=themes_router,
prefix="/themes",
db_session=db_session,
user=None
)
# Make the request
response = client.post("/themes/", json=theme_data)
# Assert unauthorized response
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert "Invalid authentication credentials" in response.json()["detail"]
def test_create_theme_regular_user_forbidden(self, create_test_client, db_session, theme_data, mock_user):
"""Test that regular users (non-superusers) cannot create themes."""
# Create a client with a regular user (not superuser)
client = create_test_client(
router=themes_router,
prefix="/themes",
db_session=db_session,
user=mock_user
)
# Make the request
response = client.post("/themes/", json=theme_data)
# Assert forbidden response
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert "Invalid authentication credentials" in response.json()["detail"]
def test_create_theme_with_minimal_data(self):
"""Test creating a theme with only the required minimal fields."""
# Create minimal theme data with only required fields
minimal_theme_data = {
"name": "Minimal Theme",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=minimal_theme_data)
# Assert successful response
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["name"] == minimal_theme_data["name"]
assert data["color_palette"] == minimal_theme_data["color_palette"]
assert data["fonts"] == minimal_theme_data["fonts"]
assert "id" in data
# Verify default values are set correctly
assert data["is_active"] == True
assert data["description"] is None
assert data["preview_image_url"] is None
assert data["background_image_url"] is None
assert data["foreground_image_url"] is None
assert data["asset_image_urls"] is None
def test_create_theme_empty_name_fails(self):
"""Test that creating a theme with an empty name fails validation."""
# Theme data with empty name
invalid_data = {
"name": "", # Empty name should fail validation
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=invalid_data)
# Assert validation error
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
errors = response.json()["detail"]
assert any("name" in error["loc"] for error in errors)
def test_create_theme_empty_color_palette_fails(self):
"""Test that creating a theme with an empty color palette fails validation."""
# Theme data with empty color palette
invalid_data = {
"name": "Invalid Theme",
"color_palette": {}, # Empty color palette should fail validation
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=invalid_data)
# Assert validation error
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
errors = response.json()["detail"]
assert any("color_palette" in error["loc"] for error in errors)
def test_create_theme_empty_fonts_fails(self):
"""Test that creating a theme with empty fonts fails validation."""
# Theme data with empty fonts
invalid_data = {
"name": "Invalid Theme",
"color_palette": {
"primary": "#000000"
},
"fonts": {} # Empty fonts should fail validation
}
# Make the request
response = self.client.post("/themes/", json=invalid_data)
# Assert validation error
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
errors = response.json()["detail"]
assert any("fonts" in error["loc"] for error in errors)
def test_create_theme_with_uploaded_preview_image(self, db_session):
"""Test creating a theme with an uploaded preview image that needs relocation."""
# Path to where the _relocate_theme_file is imported and used
@patch('app.api.routes.event_themes._relocate_theme_file')
def run_test(mock_relocate):
# Configure the mock to return a fixed URL
mock_relocated_url = "/files/event-themes/mock-id/preview/relocated-image.jpg"
mock_relocate.return_value = mock_relocated_url
# Create test data with URL that matches the expected pattern
theme_data = {
"name": "Theme with Preview Image",
"preview_image_url": "/files/uploads/temp-image.jpg",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response
assert response.status_code == 200
# Assert the mock was called
mock_relocate.assert_called_once()
# Get the response data
data = response.json()
# Assert the URL was updated in the response
assert data["preview_image_url"] == mock_relocated_url
# Verify other theme data
assert data["name"] == theme_data["name"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
# Verify the call parameters (theme_id, url, file_type, storage)
call_args = mock_relocate.call_args[0]
assert isinstance(call_args[0], uuid.UUID) # theme_id
assert call_args[1] == theme_data["preview_image_url"] # url
assert call_args[2] == 'preview' # file_type
return data
return run_test()
def test_create_theme_with_uploaded_background_image(self, db_session):
"""Test creating a theme with an uploaded background image that needs relocation."""
# Path to where the _relocate_theme_file is imported and used
@patch('app.api.routes.event_themes._relocate_theme_file')
def run_test(mock_relocate):
# Configure the mock to return a fixed URL
mock_relocated_url = "/files/event-themes/mock-id/background/relocated-bg.jpg"
mock_relocate.return_value = mock_relocated_url
# Create test data with URL that includes a background image
theme_data = {
"name": "Theme with Background Image",
"background_image_url": "/files/uploads/background.jpg",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response
assert response.status_code == 200
# Assert the mock was called
mock_relocate.assert_called_once()
# Get the response data
data = response.json()
# Assert the URL was updated in the response
assert data["background_image_url"] == mock_relocated_url
# Verify other theme data
assert data["name"] == theme_data["name"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
# Verify the call parameters (theme_id, url, file_type, storage)
call_args = mock_relocate.call_args[0]
assert isinstance(call_args[0], uuid.UUID) # theme_id
assert call_args[1] == theme_data["background_image_url"] # url
assert call_args[2] == 'background' # file_type
return data
# Run the test
return run_test()
def test_create_theme_with_uploaded_foreground_image(self, db_session):
"""Test creating a theme with an uploaded foreground image that needs relocation."""
# Path to where the _relocate_theme_file is imported and used
@patch('app.api.routes.event_themes._relocate_theme_file')
def run_test(mock_relocate):
# Configure the mock to return a fixed URL
mock_relocated_url = "/files/event-themes/mock-id/foreground/relocated-fg.jpg"
mock_relocate.return_value = mock_relocated_url
# Create test data with URL that includes a foreground image
theme_data = {
"name": "Theme with Foreground Image",
"foreground_image_url": "/files/uploads/foreground.jpg",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response
assert response.status_code == 200
# Assert the mock was called
mock_relocate.assert_called_once()
# Get the response data
data = response.json()
# Assert the URL was updated in the response
assert data["foreground_image_url"] == mock_relocated_url
# Verify other theme data
assert data["name"] == theme_data["name"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
# Verify the call parameters (theme_id, url, file_type, storage)
call_args = mock_relocate.call_args[0]
assert isinstance(call_args[0], uuid.UUID) # theme_id
assert call_args[1] == theme_data["foreground_image_url"] # url
assert call_args[2] == 'foreground' # file_type
return data
# Run the test
return run_test()
def test_create_theme_with_uploaded_asset_images(self, db_session):
"""Test creating a theme with uploaded asset images that need relocation."""
# Path to where the _relocate_theme_file is imported and used
@patch('app.api.routes.event_themes._relocate_theme_file')
def run_test(mock_relocate):
# Configure the mock to return different URLs based on the asset key
def side_effect(theme_id, current_url, file_type, storage):
# Extract the asset key from the file_type path (format: 'assets/{key}')
asset_key = file_type.split('/')[-1] if '/' in file_type else ''
return f"/files/event-themes/mock-id/assets/{asset_key}/relocated-asset.jpg"
mock_relocate.side_effect = side_effect
# Create test data with asset image URLs
theme_data = {
"name": "Theme with Asset Images",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
},
"asset_image_urls": {
"logo": "/files/uploads/logo.jpg",
"icon": "/files/uploads/icon.png",
"banner": "/files/uploads/banner.jpg"
}
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response
assert response.status_code == 200
# Assert the mock was called multiple times (once for each asset)
assert mock_relocate.call_count == 3
# Get the response data
data = response.json()
# Verify the asset URLs were updated in the response
assert "asset_image_urls" in data
assert data["asset_image_urls"]["logo"] == "/files/event-themes/mock-id/assets/logo/relocated-asset.jpg"
assert data["asset_image_urls"]["icon"] == "/files/event-themes/mock-id/assets/icon/relocated-asset.jpg"
assert data["asset_image_urls"]["banner"] == "/files/event-themes/mock-id/assets/banner/relocated-asset.jpg"
# Verify other theme data
assert data["name"] == theme_data["name"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
# Verify the mock was called with the correct parameters for each asset
calls = mock_relocate.call_args_list
call_urls = [call.args[1] for call in calls]
call_types = [call.args[2] for call in calls]
# Check that all expected URLs were processed
for key, url in theme_data["asset_image_urls"].items():
assert url in call_urls
assert f"assets/{key}" in call_types
return data
# Run the test
return run_test()
def test_create_theme_image_relocation_failure_handled(self, db_session):
"""Test that theme creation handles image relocation failures gracefully."""
# Path to where the _relocate_theme_file is imported and used
@patch('app.api.routes.event_themes._relocate_theme_file')
def run_test(mock_relocate):
# Configure the mock to return None, simulating a relocation failure
mock_relocate.return_value = None
# Create test data with an image URL that should trigger relocation
theme_data = {
"name": "Theme with Failed Image Relocation",
"preview_image_url": "/files/uploads/image-that-fails.jpg",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response (theme should still be created even if relocation fails)
assert response.status_code == 200
# Assert the mock was called
mock_relocate.assert_called_once()
# Get the response data
data = response.json()
# Verify the original URL was kept (since relocation failed)
assert data["preview_image_url"] == theme_data["preview_image_url"]
# Verify other theme data
assert data["name"] == theme_data["name"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert "id" in data
# Verify the call parameters
call_args = mock_relocate.call_args[0]
assert isinstance(call_args[0], uuid.UUID) # theme_id
assert call_args[1] == theme_data["preview_image_url"] # url
assert call_args[2] == 'preview' # file_type
return data
# Run the test
return run_test()
def test_create_theme_database_error_handled(self, db_session):
"""Test that theme creation attempts to create the theme but fails correctly."""
@patch('app.crud.event_theme.event_theme.create')
def run_test(mock_create):
# Configure the mock to raise a SQLAlchemyError
from sqlalchemy.exc import SQLAlchemyError
mock_create.side_effect = SQLAlchemyError("Database error")
# Create test data for a new theme
theme_data = {
"name": "Theme That Will Fail",
"color_palette": {
"primary": "#000000"
},
"fonts": {
"main": "Arial"
}
}
# Make the request and expect an exception
try:
response = self.client.post("/themes/", json=theme_data)
assert False, "Expected request to fail with SQLAlchemyError"
except SQLAlchemyError:
pass # Expected error
# Assert the mock was called
mock_create.assert_called_once()
# Verify the call parameters include our theme data
call_kwargs = mock_create.call_args.kwargs
assert "obj_in" in call_kwargs
return True # Test passed if we get here
# Run the test
return run_test()
def test_create_theme_with_large_data(self, db_session):
"""Test that themes with substantial amounts of data can be created successfully."""
# Create a moderately large description (1000 chars)
large_description = "A detailed theme description. " * 50 # ~1000 characters
# A color palette with more entries than usual
large_color_palette = {
"primary": "#ff5722",
"secondary": "#4caf50",
"background": "#ffffff",
"text": "#000000",
"accent1": "#2196f3",
"accent2": "#f44336",
"accent3": "#9c27b0",
"accent4": "#ffeb3b",
"accent5": "#795548",
"accent6": "#607d8b",
"highlight": "#03a9f4",
"subtle": "#e0e0e0",
"success": "#4caf50",
"warning": "#ff9800",
"error": "#f44336",
"info": "#2196f3",
"dark": "#212121",
"light": "#f5f5f5",
"border": "#e0e0e0",
"shadow": "#757575"
}
# Multiple font specifications
large_fonts = {
"header": "Roboto, sans-serif",
"subheader": "Montserrat, sans-serif",
"body": "Open Sans, sans-serif",
"button": "Roboto Condensed, sans-serif",
"caption": "Roboto, sans-serif",
"code": "Source Code Pro, monospace",
"emphasis": "Georgia, serif",
"quote": "Playfair Display, serif",
"small": "Roboto, sans-serif",
"display": "Montserrat, sans-serif"
}
# Asset images for various theme elements
asset_images = {
"header_bg": "/files/uploads/header_bg.jpg",
"footer_bg": "/files/uploads/footer_bg.jpg",
"sidebar_bg": "/files/uploads/sidebar_bg.jpg",
"hero_image": "/files/uploads/hero.jpg",
"logo_light": "/files/uploads/logo_light.png",
"logo_dark": "/files/uploads/logo_dark.png",
"pattern_light": "/files/uploads/pattern_light.png",
"pattern_dark": "/files/uploads/pattern_dark.png",
"icon_set": "/files/uploads/icons.svg",
"background_texture": "/files/uploads/texture.jpg"
}
# Theme data with large content
theme_data = {
"name": "Theme with Large Data",
"description": large_description,
"color_palette": large_color_palette,
"fonts": large_fonts,
"asset_image_urls": asset_images
}
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert successful response
assert response.status_code == 200
# Get the response data
data = response.json()
# Verify the large data was properly saved
assert data["name"] == theme_data["name"]
assert data["description"] == theme_data["description"]
assert data["color_palette"] == theme_data["color_palette"]
assert data["fonts"] == theme_data["fonts"]
assert data["asset_image_urls"] == theme_data["asset_image_urls"]
assert "id" in data
# Verify the theme was actually created in the database
from app.models.event_theme import EventTheme
created_id = uuid.UUID(data["id"])
db_theme = db_session.query(EventTheme).filter(EventTheme.id == created_id).first()
assert db_theme is not None
assert db_theme.name == theme_data["name"]
return data