"""Unit tests for page controller. This module tests the FastAPI page controller that serves HTML templates for various application pages. Coverage Target: 85%+ """ from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import Request @pytest.fixture def mock_request(): """Create a mock FastAPI request.""" request = MagicMock(spec=Request) request.url = MagicMock() request.url.path = "/" request.headers = {} return request class TestRootEndpoint: """Test the root / endpoint.""" @pytest.mark.asyncio async def test_root_endpoint_calls_render_template(self, mock_request): """Test root endpoint calls render_template.""" from src.server.controllers.page_controller import root with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" result = await root(mock_request) mock_render.assert_called_once() call_args = mock_render.call_args[0] assert call_args[0] == "index.html" @pytest.mark.asyncio async def test_root_endpoint_passes_correct_title(self, mock_request): """Test root endpoint passes correct title.""" from src.server.controllers.page_controller import root with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await root(mock_request) call_kwargs = mock_render.call_args[1] assert call_kwargs["title"] == "Aniworld Download Manager" class TestSetupEndpoint: """Test the /setup endpoint.""" @pytest.mark.asyncio async def test_setup_endpoint_renders_setup_html(self, mock_request): """Test setup endpoint renders setup.html template.""" from src.server.controllers.page_controller import setup_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await setup_page(mock_request) call_args = mock_render.call_args[0] assert call_args[0] == "setup.html" @pytest.mark.asyncio async def test_setup_endpoint_title(self, mock_request): """Test setup endpoint passes correct title.""" from src.server.controllers.page_controller import setup_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await setup_page(mock_request) call_kwargs = mock_render.call_args[1] assert call_kwargs["title"] == "Setup - Aniworld" class TestLoginEndpoint: """Test the /login endpoint.""" @pytest.mark.asyncio async def test_login_endpoint_renders_login_html(self, mock_request): """Test login endpoint renders login.html template.""" from src.server.controllers.page_controller import login_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await login_page(mock_request) call_args = mock_render.call_args[0] assert call_args[0] == "login.html" @pytest.mark.asyncio async def test_login_endpoint_title(self, mock_request): """Test login endpoint passes correct title.""" from src.server.controllers.page_controller import login_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await login_page(mock_request) call_kwargs = mock_render.call_args[1] assert call_kwargs["title"] == "Login - Aniworld" class TestQueueEndpoint: """Test the /queue endpoint.""" @pytest.mark.asyncio async def test_queue_endpoint_renders_queue_html(self, mock_request): """Test queue endpoint renders queue.html template.""" from src.server.controllers.page_controller import queue_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await queue_page(mock_request) call_args = mock_render.call_args[0] assert call_args[0] == "queue.html" @pytest.mark.asyncio async def test_queue_endpoint_title(self, mock_request): """Test queue endpoint passes correct title.""" from src.server.controllers.page_controller import queue_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await queue_page(mock_request) call_kwargs = mock_render.call_args[1] assert call_kwargs["title"] == "Download Queue - Aniworld" class TestLoadingEndpoint: """Test the /loading endpoint.""" @pytest.mark.asyncio async def test_loading_endpoint_renders_loading_html(self, mock_request): """Test loading endpoint renders loading.html template.""" from src.server.controllers.page_controller import loading_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await loading_page(mock_request) call_args = mock_render.call_args[0] assert call_args[0] == "loading.html" @pytest.mark.asyncio async def test_loading_endpoint_title(self, mock_request): """Test loading endpoint passes correct title.""" from src.server.controllers.page_controller import loading_page with patch('src.server.controllers.page_controller.render_template') as mock_render: mock_render.return_value = "" await loading_page(mock_request) call_kwargs = mock_render.call_args[1] assert call_kwargs["title"] == "Initializing - Aniworld" class TestTemplateHelpers: """Test template helper functions.""" def test_get_base_context(self): """Test getting base context.""" from src.server.utils.template_helpers import get_base_context mock_request = MagicMock(spec=Request) context = get_base_context(mock_request, "Test Title") assert context["request"] == mock_request assert context["title"] == "Test Title" assert context["app_name"] == "Aniworld Download Manager" assert context["version"] == "1.0.0" def test_get_base_context_default_title(self): """Test getting base context with default title.""" from src.server.utils.template_helpers import get_base_context mock_request = MagicMock(spec=Request) context = get_base_context(mock_request) assert context["title"] == "Aniworld" def test_render_template_basic(self): """Test render_template function.""" from src.server.utils.template_helpers import render_template mock_request = MagicMock(spec=Request) with patch('src.server.utils.template_helpers.templates.TemplateResponse') as mock_response: mock_response.return_value = "rendered" result = render_template("index.html", mock_request, title="Test") assert result == "rendered" mock_response.assert_called_once() def test_render_template_with_context(self): """Test render_template with additional context.""" from src.server.utils.template_helpers import render_template mock_request = MagicMock(spec=Request) extra_context = {"user": "test_user", "is_authenticated": True} with patch('src.server.utils.template_helpers.templates.TemplateResponse') as mock_response: mock_response.return_value = "rendered" render_template("index.html", mock_request, context=extra_context) call_args = mock_response.call_args[0] context_arg = call_args[1] assert context_arg["user"] == "test_user" assert context_arg["is_authenticated"] is True def test_render_template_title_generation(self): """Test render_template generates title from template name.""" from src.server.utils.template_helpers import render_template mock_request = MagicMock(spec=Request) with patch('src.server.utils.template_helpers.templates.TemplateResponse') as mock_response: mock_response.return_value = "rendered" render_template("index.html", mock_request) # No title provided call_args = mock_response.call_args[0] context_arg = call_args[1] # Title should be generated from template name assert context_arg["title"] == "Index" class TestValidateTemplateExists: """Test template existence validation.""" def test_validate_template_exists_true(self, tmp_path): """Test validate_template_exists returns True for existing template.""" from src.server.utils.template_helpers import validate_template_exists with patch('src.server.utils.template_helpers.TEMPLATES_DIR', tmp_path): template_file = tmp_path / "test.html" template_file.write_text("") result = validate_template_exists("test.html") assert result is True def test_validate_template_exists_false(self, tmp_path): """Test validate_template_exists returns False for missing template.""" from src.server.utils.template_helpers import validate_template_exists with patch('src.server.utils.template_helpers.TEMPLATES_DIR', tmp_path): result = validate_template_exists("nonexistent.html") assert result is False class TestListAvailableTemplates: """Test listing available templates.""" def test_list_available_templates(self, tmp_path): """Test listing available templates.""" from src.server.utils.template_helpers import list_available_templates # Create test template files (tmp_path / "index.html").write_text("") (tmp_path / "setup.html").write_text("") (tmp_path / "README.md").write_text("# README") # Should not be included with patch('src.server.utils.template_helpers.TEMPLATES_DIR', tmp_path): templates = list_available_templates() assert "index.html" in templates assert "setup.html" in templates assert "README.md" not in templates assert len(templates) == 2 def test_list_available_templates_empty_directory(self, tmp_path): """Test listing templates from empty directory.""" from src.server.utils.template_helpers import list_available_templates with patch('src.server.utils.template_helpers.TEMPLATES_DIR', tmp_path): templates = list_available_templates() assert templates == [] def test_list_available_templates_missing_directory(self): """Test listing templates when directory doesn't exist.""" from src.server.utils.template_helpers import list_available_templates with patch('src.server.utils.template_helpers.TEMPLATES_DIR', Path("/nonexistent")): templates = list_available_templates() assert templates == [] class TestPrepareSeriesContext: """Test series context preparation for templates.""" def test_prepare_series_context_basic(self): """Test preparing basic series context.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"key": "attack-on-titan", "name": "Attack on Titan", "folder": "AOT"}, {"key": "one-piece", "name": "One Piece", "folder": "OP"} ] result = prepare_series_context(series_data) assert len(result) == 2 assert result[0]["key"] == "attack-on-titan" assert result[0]["name"] == "Attack on Titan" assert result[0]["folder"] == "AOT" def test_prepare_series_context_sort_by_name(self): """Test preparing series context sorted by name.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"key": "one-piece", "name": "One Piece", "folder": "OP"}, {"key": "attack-on-titan", "name": "Attack on Titan", "folder": "AOT"}, {"key": "death-note", "name": "Death Note", "folder": "DN"} ] result = prepare_series_context(series_data, sort_by="name") assert result[0]["name"] == "Attack on Titan" assert result[1]["name"] == "Death Note" assert result[2]["name"] == "One Piece" def test_prepare_series_context_sort_by_key(self): """Test preparing series context sorted by key.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"key": "z-series", "name": "Z Series", "folder": "Z"}, {"key": "a-series", "name": "A Series", "folder": "A"}, {"key": "m-series", "name": "M Series", "folder": "M"} ] result = prepare_series_context(series_data, sort_by="key") assert result[0]["key"] == "a-series" assert result[1]["key"] == "m-series" assert result[2]["key"] == "z-series" def test_prepare_series_context_sort_by_folder(self): """Test preparing series context sorted by folder.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"key": "series1", "name": "Series 1", "folder": "Z Folder"}, {"key": "series2", "name": "Series 2", "folder": "A Folder"}, {"key": "series3", "name": "Series 3", "folder": "M Folder"} ] result = prepare_series_context(series_data, sort_by="folder") assert result[0]["folder"] == "A Folder" assert result[1]["folder"] == "M Folder" assert result[2]["folder"] == "Z Folder" def test_prepare_series_context_empty_list(self): """Test preparing empty series context.""" from src.server.utils.template_helpers import prepare_series_context result = prepare_series_context([]) assert result == [] def test_prepare_series_context_missing_key(self): """Test preparing series context with missing key field.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"name": "No Key Series", "folder": "NoKey"}, {"key": "valid-key", "name": "Valid Series", "folder": "Valid"} ] result = prepare_series_context(series_data) # Only the series with key should be included assert len(result) == 1 assert result[0]["key"] == "valid-key" def test_prepare_series_context_default_values(self): """Test preparing series context fills in default values.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ {"key": "minimal-series"} # Missing name and folder ] result = prepare_series_context(series_data) assert result[0]["key"] == "minimal-series" assert result[0]["name"] == "minimal-series" # Should use key as default assert result[0]["folder"] == "" # Should default to empty string def test_prepare_series_context_extra_fields(self): """Test preparing series context preserves extra fields.""" from src.server.utils.template_helpers import prepare_series_context series_data = [ { "key": "series1", "name": "Series 1", "folder": "S1", "year": 2020, "status": "completed", "episodes": 12 } ] result = prepare_series_context(series_data) assert result[0]["year"] == 2020 assert result[0]["status"] == "completed" assert result[0]["episodes"] == 12 class TestGetSeriesByKey: """Test finding series by key.""" def test_get_series_by_key_found(self): """Test getting series by key when found.""" from src.server.utils.template_helpers import get_series_by_key series_data = [ {"key": "attack-on-titan", "name": "Attack on Titan"}, {"key": "one-piece", "name": "One Piece"} ] result = get_series_by_key(series_data, "attack-on-titan") assert result is not None assert result["name"] == "Attack on Titan" def test_get_series_by_key_not_found(self): """Test getting series by key when not found.""" from src.server.utils.template_helpers import get_series_by_key series_data = [ {"key": "attack-on-titan", "name": "Attack on Titan"} ] result = get_series_by_key(series_data, "nonexistent") assert result is None def test_get_series_by_key_empty_list(self): """Test getting series by key from empty list.""" from src.server.utils.template_helpers import get_series_by_key result = get_series_by_key([], "any-key") assert result is None def test_get_series_by_key_case_sensitive(self): """Test that key matching is case-sensitive.""" from src.server.utils.template_helpers import get_series_by_key series_data = [ {"key": "Attack-on-Titan", "name": "Attack on Titan"} ] result = get_series_by_key(series_data, "attack-on-titan") assert result is None # Different case should not match class TestFilterSeriesByMissingEpisodes: """Test filtering series by missing episodes.""" def test_filter_series_with_missing_episodes(self): """Test filtering series with missing episodes.""" from src.server.utils.template_helpers import filter_series_by_missing_episodes series_data = [ { "key": "series1", "name": "Series 1", "missing_episodes": {"season_1": [1, 2, 3]} }, { "key": "series2", "name": "Series 2", "missing_episodes": {"season_1": []} }, { "key": "series3", "name": "Series 3", "missing_episodes": {"season_1": [5]} } ] result = filter_series_by_missing_episodes(series_data) assert len(result) == 2 assert result[0]["key"] == "series1" assert result[1]["key"] == "series3" def test_filter_series_no_missing_episodes(self): """Test filtering when no series have missing episodes.""" from src.server.utils.template_helpers import filter_series_by_missing_episodes series_data = [ { "key": "series1", "name": "Series 1", "missing_episodes": {"season_1": []} }, { "key": "series2", "name": "Series 2", "missing_episodes": {} } ] result = filter_series_by_missing_episodes(series_data) assert len(result) == 0 def test_filter_series_empty_list(self): """Test filtering empty series list.""" from src.server.utils.template_helpers import filter_series_by_missing_episodes result = filter_series_by_missing_episodes([]) assert len(result) == 0 def test_filter_series_missing_field(self): """Test filtering when missing_episodes field is missing.""" from src.server.utils.template_helpers import filter_series_by_missing_episodes series_data = [ {"key": "series1", "name": "Series 1"} # No missing_episodes ] result = filter_series_by_missing_episodes(series_data) # Should not crash and should not include this series assert len(result) == 0 def test_filter_series_multiple_seasons(self): """Test filtering with multiple seasons.""" from src.server.utils.template_helpers import filter_series_by_missing_episodes series_data = [ { "key": "series1", "name": "Series 1", "missing_episodes": { "season_1": [1, 2], "season_2": [] } } ] result = filter_series_by_missing_episodes(series_data) # Should include because season_1 has missing episodes assert len(result) == 1