""" End-to-End tests for user preferences workflows and UI response verification. This module tests complete user workflows for changing preferences and verifying that the UI responds appropriately to preference changes. """ import time from unittest.mock import patch import pytest from fastapi.testclient import TestClient from src.server.fastapi_app import app @pytest.fixture def client(): """Create a test client for the FastAPI application.""" return TestClient(app) @pytest.fixture def auth_headers(client): """Provide authentication headers for protected endpoints.""" # Login to get token login_data = {"password": "testpassword"} with patch('src.server.fastapi_app.settings.master_password_hash') as mock_hash: mock_hash.return_value = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # 'password' hash response = client.post("/auth/login", json=login_data) if response.status_code == 200: token = response.json()["access_token"] return {"Authorization": f"Bearer {token}"} return {} class TestThemeChangeWorkflow: """End-to-end tests for theme changing workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_complete_theme_change_workflow(self, mock_user, client): """Test complete workflow of changing theme and verifying UI updates.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Get current theme current_theme_response = client.get("/api/preferences/themes/current") initial_theme = None if current_theme_response.status_code == 200: initial_theme = current_theme_response.json().get("theme", {}).get("name") # Step 2: Get available themes themes_response = client.get("/api/preferences/themes") available_themes = [] if themes_response.status_code == 200: available_themes = [theme["name"] for theme in themes_response.json().get("themes", [])] # Step 3: Change to different theme new_theme = "dark" if initial_theme != "dark" else "light" if not available_themes: available_themes = ["light", "dark"] # Default themes if new_theme in available_themes: theme_change_data = {"theme_name": new_theme} change_response = client.post("/api/preferences/themes/set", json=theme_change_data) if change_response.status_code == 200: # Step 4: Verify theme was changed updated_theme_response = client.get("/api/preferences/themes/current") if updated_theme_response.status_code == 200: updated_theme = updated_theme_response.json().get("theme", {}).get("name") assert updated_theme == new_theme # Step 5: Verify UI reflects theme change (mock check) ui_response = client.get("/api/preferences/ui") if ui_response.status_code == 200: ui_data = ui_response.json() # UI should reflect the theme change assert "theme" in str(ui_data).lower() or "current" in str(ui_data).lower() # Test passes if endpoints respond appropriately (200 or 404) assert themes_response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_custom_theme_creation_and_application(self, mock_user, client): """Test creating custom theme and applying it.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Create custom theme custom_theme_data = { "name": "my_test_theme", "display_name": "My Test Theme", "colors": { "primary": "#007acc", "secondary": "#6c757d", "background": "#ffffff", "text": "#333333", "accent": "#28a745" }, "is_dark": False } create_response = client.post("/api/preferences/themes/custom", json=custom_theme_data) if create_response.status_code == 201: theme_data = create_response.json() theme_id = theme_data.get("theme_id") # Step 2: Apply the custom theme apply_data = {"theme_name": "my_test_theme"} apply_response = client.post("/api/preferences/themes/set", json=apply_data) if apply_response.status_code == 200: # Step 3: Verify custom theme is active current_response = client.get("/api/preferences/themes/current") if current_response.status_code == 200: current_theme = current_response.json().get("theme", {}) assert current_theme.get("name") == "my_test_theme" # Test endpoints exist and respond appropriately assert create_response.status_code in [201, 404] @patch('src.server.fastapi_app.get_current_user') def test_theme_persistence_across_sessions(self, mock_user, client): """Test that theme preference persists across sessions.""" mock_user.return_value = {"user_id": "test_user"} # Set theme theme_data = {"theme_name": "dark"} set_response = client.post("/api/preferences/themes/set", json=theme_data) if set_response.status_code == 200: # Simulate new session by getting current theme current_response = client.get("/api/preferences/themes/current") if current_response.status_code == 200: current_theme = current_response.json().get("theme", {}).get("name") assert current_theme == "dark" assert set_response.status_code in [200, 404] class TestLanguageChangeWorkflow: """End-to-end tests for language changing workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_complete_language_change_workflow(self, mock_user, client): """Test complete workflow of changing language and verifying UI updates.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Get available languages languages_response = client.get("/api/preferences/languages") available_languages = [] if languages_response.status_code == 200: available_languages = [lang["code"] for lang in languages_response.json().get("languages", [])] # Step 2: Get current language current_response = client.get("/api/preferences/languages/current") current_language = None if current_response.status_code == 200: current_language = current_response.json().get("language", {}).get("code") # Step 3: Change to different language new_language = "de" if current_language != "de" else "en" if not available_languages: available_languages = ["en", "de", "fr", "es"] # Default languages if new_language in available_languages: language_data = {"language_code": new_language} change_response = client.post("/api/preferences/languages/set", json=language_data) if change_response.status_code == 200: # Step 4: Verify language was changed updated_response = client.get("/api/preferences/languages/current") if updated_response.status_code == 200: updated_language = updated_response.json().get("language", {}).get("code") assert updated_language == new_language # Step 5: Verify UI text reflects language change (mock check) # In real implementation, this would check translated text ui_response = client.get("/") # Main page assert ui_response.status_code in [200, 404] assert languages_response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_language_fallback_behavior(self, mock_user, client): """Test language fallback when preferred language is unavailable.""" mock_user.return_value = {"user_id": "test_user"} # Try to set unsupported language unsupported_language_data = {"language_code": "xyz"} # Non-existent language change_response = client.post("/api/preferences/languages/set", json=unsupported_language_data) # Should either reject or fallback to default assert change_response.status_code in [400, 404, 422] # Verify fallback to default language current_response = client.get("/api/preferences/languages/current") if current_response.status_code == 200: current_language = current_response.json().get("language", {}).get("code") # Should be a valid language code (en, de, etc.) assert len(current_language) >= 2 if current_language else True class TestAccessibilityWorkflow: """End-to-end tests for accessibility settings workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_accessibility_settings_workflow(self, mock_user, client): """Test complete accessibility settings workflow.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Get current accessibility settings current_response = client.get("/api/preferences/accessibility") initial_settings = {} if current_response.status_code == 200: initial_settings = current_response.json() # Step 2: Update accessibility settings new_settings = { "high_contrast": True, "large_text": True, "reduced_motion": False, "screen_reader_support": True, "keyboard_navigation": True, "font_size_multiplier": 1.5 } update_response = client.put("/api/preferences/accessibility", json=new_settings) if update_response.status_code == 200: # Step 3: Verify settings were updated updated_response = client.get("/api/preferences/accessibility") if updated_response.status_code == 200: updated_settings = updated_response.json() # Check that key settings were updated for key, value in new_settings.items(): if key in updated_settings: assert updated_settings[key] == value # Step 4: Verify UI reflects accessibility changes # Check main page with accessibility features main_page_response = client.get("/app") if main_page_response.status_code == 200: # In real implementation, would check for accessibility features assert main_page_response.status_code == 200 assert current_response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_high_contrast_mode_workflow(self, mock_user, client): """Test high contrast mode workflow.""" mock_user.return_value = {"user_id": "test_user"} # Enable high contrast mode accessibility_data = { "high_contrast": True, "large_text": True } update_response = client.put("/api/preferences/accessibility", json=accessibility_data) if update_response.status_code == 200: # Verify theme reflects high contrast theme_response = client.get("/api/preferences/themes/current") if theme_response.status_code == 200: theme_data = theme_response.json() # High contrast should influence theme colors assert "theme" in theme_data assert update_response.status_code in [200, 404] class TestUISettingsWorkflow: """End-to-end tests for UI settings workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_view_mode_change_workflow(self, mock_user, client): """Test changing view mode from grid to list and back.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Get current UI settings ui_response = client.get("/api/preferences/ui") current_view_mode = None if ui_response.status_code == 200: current_view_mode = ui_response.json().get("view_mode") # Step 2: Change view mode new_view_mode = "list" if current_view_mode != "list" else "grid" view_data = { "view_mode": new_view_mode, "show_thumbnails": True if new_view_mode == "grid" else False } if new_view_mode == "grid": view_data["grid_columns"] = 4 change_response = client.post("/api/preferences/ui/view-mode", json=view_data) if change_response.status_code == 200: # Step 3: Verify view mode changed updated_response = client.get("/api/preferences/ui") if updated_response.status_code == 200: updated_ui = updated_response.json() assert updated_ui.get("view_mode") == new_view_mode # Step 4: Verify anime list reflects view mode anime_response = client.get("/api/anime/search?limit=5") if anime_response.status_code == 200: # In real implementation, response format might differ based on view mode assert anime_response.status_code == 200 assert ui_response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_ui_density_change_workflow(self, mock_user, client): """Test changing UI density settings.""" mock_user.return_value = {"user_id": "test_user"} # Test different density settings density_options = ["compact", "comfortable", "spacious"] for density in density_options: density_data = { "density": density, "compact_mode": density == "compact" } density_response = client.post("/api/preferences/ui/density", json=density_data) if density_response.status_code == 200: # Verify density was set ui_response = client.get("/api/preferences/ui") if ui_response.status_code == 200: ui_data = ui_response.json() assert ui_data.get("density") == density # All density changes should be valid assert density_response.status_code in [200, 404] class TestKeyboardShortcutsWorkflow: """End-to-end tests for keyboard shortcuts workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_keyboard_shortcuts_customization(self, mock_user, client): """Test customizing keyboard shortcuts.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Get current shortcuts shortcuts_response = client.get("/api/preferences/shortcuts") if shortcuts_response.status_code == 200: current_shortcuts = shortcuts_response.json().get("shortcuts", {}) # Step 2: Update a shortcut shortcut_data = { "action": "search", "shortcut": "Ctrl+Shift+F", "description": "Global search" } update_response = client.put("/api/preferences/shortcuts", json=shortcut_data) if update_response.status_code == 200: # Step 3: Verify shortcut was updated updated_response = client.get("/api/preferences/shortcuts") if updated_response.status_code == 200: updated_shortcuts = updated_response.json().get("shortcuts", {}) if "search" in updated_shortcuts: assert updated_shortcuts["search"]["shortcut"] == "Ctrl+Shift+F" assert shortcuts_response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_shortcuts_reset_workflow(self, mock_user, client): """Test resetting shortcuts to defaults.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Modify some shortcuts custom_shortcut = { "action": "download", "shortcut": "Ctrl+Alt+D" } modify_response = client.put("/api/preferences/shortcuts", json=custom_shortcut) # Step 2: Reset to defaults reset_response = client.post("/api/preferences/shortcuts/reset") if reset_response.status_code == 200: # Step 3: Verify shortcuts were reset shortcuts_response = client.get("/api/preferences/shortcuts") if shortcuts_response.status_code == 200: shortcuts = shortcuts_response.json().get("shortcuts", {}) # Should have default shortcuts assert len(shortcuts) > 0 assert reset_response.status_code in [200, 404] class TestPreferencesIntegrationWorkflow: """End-to-end tests for integrated preferences workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_complete_preferences_setup_workflow(self, mock_user, client): """Test complete new user preferences setup workflow.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Set theme theme_data = {"theme_name": "dark"} theme_response = client.post("/api/preferences/themes/set", json=theme_data) # Step 2: Set language language_data = {"language_code": "en"} language_response = client.post("/api/preferences/languages/set", json=language_data) # Step 3: Configure accessibility accessibility_data = { "high_contrast": False, "large_text": False, "reduced_motion": True } accessibility_response = client.put("/api/preferences/accessibility", json=accessibility_data) # Step 4: Set UI preferences ui_data = { "view_mode": "grid", "grid_columns": 4, "show_thumbnails": True } ui_response = client.post("/api/preferences/ui/view-mode", json=ui_data) # Step 5: Verify all preferences were set all_prefs_response = client.get("/api/preferences") if all_prefs_response.status_code == 200: prefs_data = all_prefs_response.json() # Should contain all preference sections expected_sections = ["theme", "language", "accessibility", "ui_settings"] for section in expected_sections: if section in prefs_data: assert prefs_data[section] is not None # All steps should complete successfully or return 404 (not implemented) responses = [theme_response, language_response, accessibility_response, ui_response] for response in responses: assert response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_preferences_export_import_workflow(self, mock_user, client): """Test exporting and importing preferences.""" mock_user.return_value = {"user_id": "test_user"} # Step 1: Set some preferences preferences_data = { "theme": {"name": "dark"}, "language": {"code": "de"}, "ui_settings": {"view_mode": "list", "density": "compact"} } bulk_response = client.put("/api/preferences", json=preferences_data) if bulk_response.status_code == 200: # Step 2: Export preferences export_response = client.get("/api/preferences/export") if export_response.status_code == 200: exported_data = export_response.json() # Step 3: Reset preferences reset_response = client.post("/api/preferences/reset") if reset_response.status_code == 200: # Step 4: Import preferences back import_response = client.post("/api/preferences/import", json=exported_data) if import_response.status_code == 200: # Step 5: Verify preferences were restored final_response = client.get("/api/preferences") if final_response.status_code == 200: final_prefs = final_response.json() # Should match original preferences assert final_prefs is not None # Test that export/import endpoints exist export_test_response = client.get("/api/preferences/export") assert export_test_response.status_code in [200, 404] class TestPreferencesPerformance: """Performance tests for preferences workflows.""" @patch('src.server.fastapi_app.get_current_user') def test_preferences_response_time(self, mock_user, client): """Test that preference changes respond quickly.""" mock_user.return_value = {"user_id": "test_user"} start_time = time.time() # Quick preference change theme_data = {"theme_name": "light"} response = client.post("/api/preferences/themes/set", json=theme_data) response_time = time.time() - start_time # Should respond quickly (< 2 seconds) assert response_time < 2.0 assert response.status_code in [200, 404] @patch('src.server.fastapi_app.get_current_user') def test_bulk_preferences_update_performance(self, mock_user, client): """Test performance of bulk preferences update.""" mock_user.return_value = {"user_id": "test_user"} start_time = time.time() # Large preferences update bulk_data = { "theme": {"name": "dark", "custom_colors": {"primary": "#007acc"}}, "language": {"code": "en"}, "accessibility": { "high_contrast": True, "large_text": True, "reduced_motion": False, "font_size_multiplier": 1.2 }, "ui_settings": { "view_mode": "grid", "grid_columns": 6, "density": "comfortable", "show_thumbnails": True }, "shortcuts": { "search": {"shortcut": "Ctrl+K"}, "download": {"shortcut": "Ctrl+D"} } } response = client.put("/api/preferences", json=bulk_data) response_time = time.time() - start_time # Should handle bulk update efficiently (< 3 seconds) assert response_time < 3.0 assert response.status_code in [200, 404] if __name__ == "__main__": pytest.main([__file__, "-v"])