Files
eventspace/backend/tests/api/routes/test_event_themes.py
Felipe Cardoso 12161e048d Refactor tests and fix patch imports in event themes tests
Updated `@patch` targets to match their correct import paths and adjusted imports for clarity and consistency. Fixed indentation issues and resolved minor import ordering problems to improve code readability.
2025-03-15 01:29:39 +01:00

578 lines
22 KiB
Python

# tests/api/routes/test_event_themes.py
import uuid
from unittest.mock import patch
import pytest
from fastapi import status
from app.api.routes.event_themes import router as themes_router
class TestCreateEventTheme:
"""Test scenarios for the create_theme endpoint."""
@pytest.fixture(autouse=True)
def setup_method(self, create_test_client, db_session, mock_superuser):
self.client = create_test_client(
router=themes_router,
prefix="/themes",
db_session=db_session,
user=mock_superuser
)
self.db_session = db_session
self.mock_superuser = mock_superuser
def test_create_theme_success(self, theme_data):
"""Test successful theme creation."""
# Make the request
response = self.client.post("/themes/", json=theme_data)
# Assert response
assert response.status_code == 200
data = response.json()
assert data["name"] == theme_data["name"]
assert data["description"] == theme_data["description"]
assert data["preview_image_url"] == theme_data["preview_image_url"]
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_crud.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