# tests/unit/middleware/test_theme_context.py """ Comprehensive unit tests for ThemeContextMiddleware and ThemeContextManager. Tests cover: - Theme loading and caching - Default theme structure and validation - Vendor-specific theme retrieval - Fallback to default theme - Middleware integration - Edge cases and error handling """ from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from fastapi import Request from middleware.theme_context import ( ThemeContextManager, ThemeContextMiddleware, get_current_theme, ) @pytest.mark.unit class TestThemeContextManager: """Test suite for ThemeContextManager.""" def test_get_default_theme_structure(self): """Test default theme has correct structure.""" theme = ThemeContextManager.get_default_theme() assert "theme_name" in theme assert "colors" in theme assert "fonts" in theme assert "branding" in theme assert "layout" in theme assert "social_links" in theme assert "css_variables" in theme def test_get_default_theme_colors(self): """Test default theme has all required colors.""" theme = ThemeContextManager.get_default_theme() required_colors = [ "primary", "secondary", "accent", "background", "text", "border", ] for color in required_colors: assert color in theme["colors"] assert theme["colors"][color].startswith("#") def test_get_default_theme_fonts(self): """Test default theme has font configuration.""" theme = ThemeContextManager.get_default_theme() assert "heading" in theme["fonts"] assert "body" in theme["fonts"] assert isinstance(theme["fonts"]["heading"], str) assert isinstance(theme["fonts"]["body"], str) def test_get_default_theme_branding(self): """Test default theme branding structure.""" theme = ThemeContextManager.get_default_theme() assert "logo" in theme["branding"] assert "logo_dark" in theme["branding"] assert "favicon" in theme["branding"] assert "banner" in theme["branding"] def test_get_default_theme_css_variables(self): """Test default theme has CSS variables.""" theme = ThemeContextManager.get_default_theme() assert "--color-primary" in theme["css_variables"] assert "--font-heading" in theme["css_variables"] assert "--font-body" in theme["css_variables"] def test_get_vendor_theme_with_custom_theme(self): """Test getting vendor-specific theme.""" mock_db = Mock() mock_theme = Mock() # Mock to_dict to return actual dictionary custom_theme_dict = {"theme_name": "custom", "colors": {"primary": "#ff0000"}} mock_theme.to_dict.return_value = custom_theme_dict # Correct filter chain: query().filter().first() mock_db.query.return_value.filter.return_value.first.return_value = mock_theme theme = ThemeContextManager.get_vendor_theme(mock_db, vendor_id=1) assert theme["theme_name"] == "custom" assert theme["colors"]["primary"] == "#ff0000" mock_theme.to_dict.assert_called_once() def test_get_vendor_theme_fallback_to_default(self): """Test falling back to default theme when no custom theme exists.""" mock_db = Mock() # Correct filter chain: query().filter().first() mock_db.query.return_value.filter.return_value.first.return_value = None theme = ThemeContextManager.get_vendor_theme(mock_db, vendor_id=1) # Verify it returns a dict (not a Mock) assert isinstance(theme, dict) assert theme["theme_name"] == "default" assert "colors" in theme assert "fonts" in theme def test_get_vendor_theme_inactive_theme(self): """Test that inactive themes are not returned.""" mock_db = Mock() # Correct filter chain: query().filter().first() mock_db.query.return_value.filter.return_value.first.return_value = None theme = ThemeContextManager.get_vendor_theme(mock_db, vendor_id=1) # Should return default theme (actual dict) assert isinstance(theme, dict) assert theme["theme_name"] == "default" @pytest.mark.unit class TestThemeContextMiddleware: """Test suite for ThemeContextMiddleware.""" @pytest.mark.asyncio async def test_middleware_loads_theme_for_vendor(self): """Test middleware loads theme when vendor exists.""" middleware = ThemeContextMiddleware(app=None) request = Mock(spec=Request) mock_vendor = Mock() mock_vendor.id = 1 mock_vendor.name = "Test Vendor" request.state = Mock(vendor=mock_vendor) call_next = AsyncMock(return_value=Mock()) mock_db = MagicMock() mock_theme = {"theme_name": "test_theme"} with ( patch("middleware.theme_context.get_db", return_value=iter([mock_db])), patch.object( ThemeContextManager, "get_vendor_theme", return_value=mock_theme ), ): await middleware.dispatch(request, call_next) assert request.state.theme == mock_theme call_next.assert_called_once_with(request) @pytest.mark.asyncio async def test_middleware_uses_default_theme_no_vendor(self): """Test middleware uses default theme when no vendor.""" middleware = ThemeContextMiddleware(app=None) request = Mock(spec=Request) request.state = Mock(vendor=None) call_next = AsyncMock(return_value=Mock()) await middleware.dispatch(request, call_next) assert hasattr(request.state, "theme") assert request.state.theme["theme_name"] == "default" call_next.assert_called_once() @pytest.mark.asyncio async def test_middleware_handles_theme_loading_error(self): """Test middleware handles errors gracefully.""" middleware = ThemeContextMiddleware(app=None) request = Mock(spec=Request) mock_vendor = Mock(id=1, name="Test Vendor") request.state = Mock(vendor=mock_vendor) call_next = AsyncMock(return_value=Mock()) mock_db = MagicMock() with ( patch("middleware.theme_context.get_db", return_value=iter([mock_db])), patch.object( ThemeContextManager, "get_vendor_theme", side_effect=Exception("DB Error"), ), ): await middleware.dispatch(request, call_next) # Should fallback to default theme assert request.state.theme["theme_name"] == "default" call_next.assert_called_once() def test_get_current_theme_exists(self): """Test getting current theme when it exists.""" request = Mock(spec=Request) test_theme = {"theme_name": "test"} request.state.theme = test_theme theme = get_current_theme(request) assert theme == test_theme def test_get_current_theme_default(self): """Test getting theme returns default when not set.""" request = Mock(spec=Request) request.state = Mock(spec=[]) # No theme attribute theme = get_current_theme(request) assert theme["theme_name"] == "default" @pytest.mark.unit class TestThemeEdgeCases: """Test suite for edge cases and special scenarios.""" @pytest.mark.asyncio async def test_middleware_closes_db_connection(self): """Test middleware properly closes database connection.""" middleware = ThemeContextMiddleware(app=None) request = Mock(spec=Request) mock_vendor = Mock(id=1, name="Test") request.state = Mock(vendor=mock_vendor) call_next = AsyncMock(return_value=Mock()) mock_db = MagicMock() with patch("middleware.theme_context.get_db", return_value=iter([mock_db])): await middleware.dispatch(request, call_next) # Verify database was closed mock_db.close.assert_called_once() def test_theme_default_immutability(self): """Test that getting default theme doesn't share state.""" theme1 = ThemeContextManager.get_default_theme() theme2 = ThemeContextManager.get_default_theme() # Modify theme1 theme1["colors"]["primary"] = "#000000" # theme2 should not be affected (if properly implemented) # Note: This test documents expected behavior assert theme2["colors"]["primary"] == "#6366f1"