From 86651c2ef16dace38c35cac2d0ddf1eb14949b57 Mon Sep 17 00:00:00 2001 From: Lukas Pupka-Lipinski Date: Mon, 6 Oct 2025 11:36:01 +0200 Subject: [PATCH] Add integration tests for user preferences and UI settings - Created comprehensive test suite for preferences endpoints - Includes tests for theme management (light/dark/custom themes) - Tests language selection and localization - Covers accessibility settings (high contrast, large text, etc) - Tests keyboard shortcuts configuration - Covers UI density and view mode settings (grid/list) - Tests preferences import/export and bulk updates - Ready for future preferences endpoint implementation --- .../integration/test_user_preferences.py | 513 ++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 src/tests/integration/test_user_preferences.py diff --git a/src/tests/integration/test_user_preferences.py b/src/tests/integration/test_user_preferences.py new file mode 100644 index 0000000..a99a886 --- /dev/null +++ b/src/tests/integration/test_user_preferences.py @@ -0,0 +1,513 @@ +""" +Integration tests for user preferences and UI settings API endpoints. + +This module tests the user preferences endpoints for theme management, language selection, +accessibility settings, keyboard shortcuts, and UI density configurations. +""" + +import pytest +from fastapi.testclient import TestClient +from unittest.mock import patch + +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 TestThemeManagement: + """Test cases for theme management endpoints.""" + + def test_get_themes_requires_auth(self, client): + """Test that getting themes requires authentication.""" + response = client.get("/api/preferences/themes") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_available_themes(self, mock_user, client): + """Test getting available themes.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/themes") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "themes" in data + assert isinstance(data["themes"], list) + # Should include at least light and dark themes + theme_names = [theme["name"] for theme in data["themes"]] + assert "light" in theme_names or "dark" in theme_names + + @patch('src.server.fastapi_app.get_current_user') + def test_get_current_theme(self, mock_user, client): + """Test getting current theme.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/themes/current") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "theme" in data + assert "name" in data["theme"] + assert "colors" in data["theme"] + + @patch('src.server.fastapi_app.get_current_user') + def test_set_theme(self, mock_user, client): + """Test setting user theme.""" + mock_user.return_value = {"user_id": "test_user"} + + theme_data = { + "theme_name": "dark", + "custom_colors": { + "primary": "#007acc", + "secondary": "#6c757d", + "background": "#1a1a1a" + } + } + + response = client.post("/api/preferences/themes/set", json=theme_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert data["status"] == "success" + + @patch('src.server.fastapi_app.get_current_user') + def test_create_custom_theme(self, mock_user, client): + """Test creating custom theme.""" + mock_user.return_value = {"user_id": "test_user"} + + custom_theme = { + "name": "my_custom_theme", + "display_name": "My Custom Theme", + "colors": { + "primary": "#ff6b6b", + "secondary": "#4ecdc4", + "background": "#2c3e50", + "text": "#ecf0f1", + "accent": "#e74c3c" + }, + "is_dark": True + } + + response = client.post("/api/preferences/themes/custom", json=custom_theme) + assert response.status_code in [201, 404] + + if response.status_code == 201: + data = response.json() + assert "theme_id" in data + assert "name" in data + + def test_set_invalid_theme(self, client, auth_headers): + """Test setting invalid theme.""" + invalid_data = {"theme_name": "nonexistent_theme"} + + response = client.post("/api/preferences/themes/set", json=invalid_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestLanguageSelection: + """Test cases for language selection endpoints.""" + + def test_get_languages_requires_auth(self, client): + """Test that getting languages requires authentication.""" + response = client.get("/api/preferences/languages") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_available_languages(self, mock_user, client): + """Test getting available languages.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/languages") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "languages" in data + assert isinstance(data["languages"], list) + # Should include at least English + language_codes = [lang["code"] for lang in data["languages"]] + assert "en" in language_codes + + @patch('src.server.fastapi_app.get_current_user') + def test_get_current_language(self, mock_user, client): + """Test getting current language.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/languages/current") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "language" in data + assert "code" in data["language"] + assert "name" in data["language"] + + @patch('src.server.fastapi_app.get_current_user') + def test_set_language(self, mock_user, client): + """Test setting user language.""" + mock_user.return_value = {"user_id": "test_user"} + + language_data = {"language_code": "de"} + + response = client.post("/api/preferences/languages/set", json=language_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert "language" in data + + def test_set_invalid_language(self, client, auth_headers): + """Test setting invalid language.""" + invalid_data = {"language_code": "invalid_lang"} + + response = client.post("/api/preferences/languages/set", json=invalid_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestAccessibilitySettings: + """Test cases for accessibility settings endpoints.""" + + def test_get_accessibility_requires_auth(self, client): + """Test that getting accessibility settings requires authentication.""" + response = client.get("/api/preferences/accessibility") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_accessibility_settings(self, mock_user, client): + """Test getting accessibility settings.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/accessibility") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + expected_fields = [ + "high_contrast", "large_text", "reduced_motion", + "screen_reader_support", "keyboard_navigation" + ] + for field in expected_fields: + assert field in data + + @patch('src.server.fastapi_app.get_current_user') + def test_update_accessibility_settings(self, mock_user, client): + """Test updating accessibility settings.""" + mock_user.return_value = {"user_id": "test_user"} + + accessibility_data = { + "high_contrast": True, + "large_text": True, + "reduced_motion": False, + "screen_reader_support": True, + "keyboard_navigation": True, + "font_size_multiplier": 1.2 + } + + response = client.put("/api/preferences/accessibility", json=accessibility_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert "updated_settings" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_reset_accessibility_settings(self, mock_user, client): + """Test resetting accessibility settings to defaults.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.post("/api/preferences/accessibility/reset") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert data["status"] == "reset" + + +class TestKeyboardShortcuts: + """Test cases for keyboard shortcuts endpoints.""" + + def test_get_shortcuts_requires_auth(self, client): + """Test that getting shortcuts requires authentication.""" + response = client.get("/api/preferences/shortcuts") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_keyboard_shortcuts(self, mock_user, client): + """Test getting keyboard shortcuts.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/shortcuts") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "shortcuts" in data + assert isinstance(data["shortcuts"], dict) + + @patch('src.server.fastapi_app.get_current_user') + def test_update_keyboard_shortcut(self, mock_user, client): + """Test updating keyboard shortcut.""" + mock_user.return_value = {"user_id": "test_user"} + + shortcut_data = { + "action": "search", + "shortcut": "Ctrl+K", + "description": "Open search" + } + + response = client.put("/api/preferences/shortcuts", json=shortcut_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert "shortcut" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_reset_shortcuts_to_default(self, mock_user, client): + """Test resetting shortcuts to default.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.post("/api/preferences/shortcuts/reset") + assert response.status_code in [200, 404] + + def test_invalid_shortcut_format(self, client, auth_headers): + """Test updating shortcut with invalid format.""" + invalid_data = { + "action": "search", + "shortcut": "InvalidKey++" + } + + response = client.put("/api/preferences/shortcuts", json=invalid_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestUIDensitySettings: + """Test cases for UI density and view settings endpoints.""" + + def test_get_ui_settings_requires_auth(self, client): + """Test that getting UI settings requires authentication.""" + response = client.get("/api/preferences/ui") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_ui_density_settings(self, mock_user, client): + """Test getting UI density settings.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/ui") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + expected_fields = [ + "density", "view_mode", "grid_columns", + "show_thumbnails", "compact_mode" + ] + for field in expected_fields: + assert field in data + + @patch('src.server.fastapi_app.get_current_user') + def test_set_view_mode(self, mock_user, client): + """Test setting view mode (grid/list).""" + mock_user.return_value = {"user_id": "test_user"} + + view_data = { + "view_mode": "grid", + "grid_columns": 4, + "show_thumbnails": True + } + + response = client.post("/api/preferences/ui/view-mode", json=view_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert "view_mode" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_set_ui_density(self, mock_user, client): + """Test setting UI density.""" + mock_user.return_value = {"user_id": "test_user"} + + density_data = { + "density": "comfortable", # compact, comfortable, spacious + "compact_mode": False + } + + response = client.post("/api/preferences/ui/density", json=density_data) + assert response.status_code in [200, 404] + + @patch('src.server.fastapi_app.get_current_user') + def test_update_grid_settings(self, mock_user, client): + """Test updating grid view settings.""" + mock_user.return_value = {"user_id": "test_user"} + + grid_data = { + "columns": 6, + "thumbnail_size": "medium", + "show_titles": True, + "show_episode_count": True + } + + response = client.put("/api/preferences/ui/grid", json=grid_data) + assert response.status_code in [200, 404] + + def test_invalid_view_mode(self, client, auth_headers): + """Test setting invalid view mode.""" + invalid_data = {"view_mode": "invalid_mode"} + + response = client.post("/api/preferences/ui/view-mode", json=invalid_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestPreferencesIntegration: + """Integration tests for preferences functionality.""" + + @patch('src.server.fastapi_app.get_current_user') + def test_get_all_preferences(self, mock_user, client): + """Test getting all user preferences.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + expected_sections = [ + "theme", "language", "accessibility", + "shortcuts", "ui_settings" + ] + for section in expected_sections: + assert section in data + + @patch('src.server.fastapi_app.get_current_user') + def test_bulk_update_preferences(self, mock_user, client): + """Test bulk updating multiple preferences.""" + mock_user.return_value = {"user_id": "test_user"} + + bulk_data = { + "theme": {"name": "dark"}, + "language": {"code": "en"}, + "accessibility": {"high_contrast": True}, + "ui_settings": {"view_mode": "list", "density": "compact"} + } + + response = client.put("/api/preferences", json=bulk_data) + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert "updated_sections" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_export_preferences(self, mock_user, client): + """Test exporting user preferences.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/preferences/export") + assert response.status_code in [200, 404] + + if response.status_code == 200: + # Should return JSON or file download + assert response.headers.get("content-type") in [ + "application/json", + "application/octet-stream" + ] + + @patch('src.server.fastapi_app.get_current_user') + def test_import_preferences(self, mock_user, client): + """Test importing user preferences.""" + mock_user.return_value = {"user_id": "test_user"} + + import_data = { + "theme": {"name": "light"}, + "language": {"code": "de"}, + "ui_settings": {"view_mode": "grid"} + } + + response = client.post("/api/preferences/import", json=import_data) + assert response.status_code in [200, 404] + + @patch('src.server.fastapi_app.get_current_user') + def test_reset_all_preferences(self, mock_user, client): + """Test resetting all preferences to defaults.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.post("/api/preferences/reset") + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "status" in data + assert data["status"] == "reset" + + +class TestPreferencesValidation: + """Test cases for preferences validation.""" + + def test_theme_validation(self, client, auth_headers): + """Test theme data validation.""" + invalid_theme_data = { + "colors": { + "primary": "not_a_color", # Invalid color format + "background": "#xyz" # Invalid hex color + } + } + + response = client.post("/api/preferences/themes/custom", json=invalid_theme_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + def test_accessibility_validation(self, client, auth_headers): + """Test accessibility settings validation.""" + invalid_accessibility_data = { + "font_size_multiplier": -1, # Invalid value + "high_contrast": "not_boolean" # Invalid type + } + + response = client.put("/api/preferences/accessibility", json=invalid_accessibility_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + def test_ui_settings_validation(self, client, auth_headers): + """Test UI settings validation.""" + invalid_ui_data = { + "grid_columns": 0, # Invalid value + "density": "invalid_density" # Invalid enum value + } + + response = client.post("/api/preferences/ui/density", json=invalid_ui_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) \ No newline at end of file