"""Tests for the /navigate/{path} endpoint.""" from httpx import AsyncClient import main class TestNavigatePage: """Tests for GET /navigate/{path}.""" async def test_returns_html_page(self, client_zip_navigate: AsyncClient) -> None: """Returns an HTML page for a valid internal ZIP path.""" response = await client_zip_navigate.get("/navigate/top.txt") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] async def test_page_contains_image_url( self, client_zip_navigate: AsyncClient ) -> None: """HTML page contains the image URL using hash-based /api endpoint.""" response = await client_zip_navigate.get("/navigate/top.txt") # The image URL should use the hash-based /api endpoint assert "/api/" in response.text assert "/data" in response.text async def test_page_contains_navigate_nav_links( self, client_zip_navigate: AsyncClient ) -> None: """HTML page contains /navigate/ URLs for prev and next navigation.""" response = await client_zip_navigate.get("/navigate/top.txt") assert "/navigate/" in response.text async def test_page_contains_prev_next_buttons( self, client_zip_navigate: AsyncClient ) -> None: """HTML page contains prev and next navigation buttons.""" response = await client_zip_navigate.get("/navigate/top.txt") assert 'class="chevron left"' in response.text assert 'class="chevron right"' in response.text async def test_page_contains_play_button( self, client_zip_navigate: AsyncClient ) -> None: """HTML page contains play button with /navigate query param URL.""" response = await client_zip_navigate.get("/navigate/top.txt") assert "/navigate/" in response.text assert "?order=next&delay=5" in response.text async def test_subdirectory_path(self, client_zip_navigate: AsyncClient) -> None: """Paths with subdirectories work correctly.""" response = await client_zip_navigate.get("/navigate/folder/deep.txt") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] async def test_returns_404_for_invalid_path( self, client_zip_navigate: AsyncClient ) -> None: """Returns 404 for a path that doesn't exist in the zip.""" response = await client_zip_navigate.get("/navigate/nonexistent.txt") assert response.status_code == 404 async def test_returns_folder_index_for_directory_path( self, client_zip_navigate: AsyncClient ) -> None: """Returns folder index page for a directory path.""" response = await client_zip_navigate.get("/navigate/folder/") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] assert "index-item" in response.text async def test_returns_404_for_empty_directory_path( self, client_zip_navigate: AsyncClient ) -> None: """Returns 404 for a non-existent directory path.""" response = await client_zip_navigate.get("/navigate/nonexistent/") assert response.status_code == 404 async def test_returns_root_folder_index( self, client_zip_navigate: AsyncClient ) -> None: """Returns folder index page for root path.""" response = await client_zip_navigate.get("/navigate/") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] assert "index-item" in response.text assert "folder/" in response.text async def test_folder_index_has_breadcrumb( self, client_zip_navigate: AsyncClient ) -> None: """Folder index page has breadcrumb navigation.""" response = await client_zip_navigate.get("/navigate/folder/") assert "breadcrumb" in response.text async def test_folder_index_shows_subfolder_links( self, client_zip_navigate: AsyncClient ) -> None: """Root folder index shows subfolder links.""" response = await client_zip_navigate.get("/navigate/") assert "/navigate/folder/" in response.text async def test_folder_index_shows_file_links( self, client_zip_navigate: AsyncClient ) -> None: """Folder index shows file links.""" response = await client_zip_navigate.get("/navigate/") assert "/navigate/top.txt" in response.text async def test_subfolder_index_shows_nested_files( self, client_zip_navigate: AsyncClient ) -> None: """Subfolder index shows files within that folder.""" response = await client_zip_navigate.get("/navigate/folder/") assert response.status_code == 200 assert "/navigate/folder/deep.txt" in response.text assert "/navigate/folder/image.png" in response.text async def test_subfolder_index_shows_full_tree( self, client_zip_navigate: AsyncClient ) -> None: """Subfolder index sidebar shows full tree from root.""" response = await client_zip_navigate.get("/navigate/folder/") assert response.status_code == 200 # Should still show root-level items in the tree assert "/navigate/top.txt" in response.text async def test_file_page_has_sidebar( self, client_zip_navigate: AsyncClient ) -> None: """File page in navigate mode has folder index sidebar.""" response = await client_zip_navigate.get("/navigate/top.txt") assert "sidebar" in response.text assert "index-item" in response.text async def test_hash_page_has_no_sidebar( self, client_zip_navigate: AsyncClient ) -> None: """Hash page does not show folder index sidebar content.""" # Get a valid hash from the zip response = await client_zip_navigate.get("/navigate/top.txt") assert response.status_code == 200 # Extract hash from the response import re match = re.search(r'data-hash="([^"]+)"', response.text) assert match is not None file_hash = match.group(1) # Now visit the hash page hash_response = await client_zip_navigate.get(f"/{file_hash}") assert hash_response.status_code == 200 # Sidebar should be hidden on hash pages assert 'id="sidebar" class="hidden"' in hash_response.text # No folder index content in sidebar assert "📁" not in hash_response.text assert "📄" not in hash_response.text async def test_returns_404_on_directory_indexer( self, client_dir: AsyncClient ) -> None: """Returns 404 when server has only directory indexers (no ZIP).""" response = await client_dir.get("/navigate/some/path.txt") assert response.status_code == 404 async def test_returns_404_when_navigate_disabled( self, client_zip: AsyncClient ) -> None: """Returns 404 when navigate flag is not enabled.""" response = await client_zip.get("/navigate/top.txt") assert response.status_code == 404 async def test_page_has_data_hash_attribute( self, client_zip_navigate: AsyncClient ) -> None: """HTML page has data-hash attribute on image for toggle support.""" response = await client_zip_navigate.get("/navigate/top.txt") assert "data-hash=" in response.text async def test_page_has_data_path_attribute( self, client_zip_navigate: AsyncClient ) -> None: """HTML page has data-path attribute on image for toggle support.""" response = await client_zip_navigate.get("/navigate/top.txt") assert 'data-path="top.txt"' in response.text async def test_toggle_url_points_to_hash_mode( self, client_zip_navigate: AsyncClient ) -> None: """Navigate page toggle URL points to hash-based URL.""" import re response = await client_zip_navigate.get("/navigate/top.txt") match = re.search(r'id="toggle-link" href="(/[^/][^"]*?)"', response.text) assert match is not None toggle_href = match.group(1) assert not toggle_href.startswith("/navigate/") async def test_toggle_url_preserves_query_params( self, client_zip_navigate: AsyncClient ) -> None: """Navigate page toggle URL preserves order/delay query params.""" import re response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "next", "delay": 5} ) match = re.search(r'id="toggle-link" href="(/[^"]*?)"', response.text) assert match is not None toggle_href = match.group(1) assert "order=next" in toggle_href assert "delay=5" in toggle_href class TestNavigatePageWithRefresh: """Tests for GET /navigate/{path}?order=...&delay=... (auto-refresh mode).""" async def test_next_order_returns_html( self, client_zip_navigate: AsyncClient ) -> None: """Next order returns HTML with refresh meta tag.""" response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "next", "delay": 5} ) assert response.status_code == 200 assert 'http-equiv="refresh"' in response.text async def test_random_order_returns_html( self, client_zip_navigate: AsyncClient ) -> None: """Random order returns HTML with refresh meta tag.""" response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "random", "delay": 3} ) assert response.status_code == 200 assert 'http-equiv="refresh"' in response.text async def test_invalid_order_returns_400( self, client_zip_navigate: AsyncClient ) -> None: """Invalid order parameter returns 400.""" response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "shuffle", "delay": 5} ) assert response.status_code == 400 async def test_returns_404_for_invalid_path( self, client_zip_navigate: AsyncClient ) -> None: """Returns 404 for a path that doesn't exist.""" response = await client_zip_navigate.get( "/navigate/nonexistent.txt", params={"order": "next", "delay": 5} ) assert response.status_code == 404 async def test_refresh_url_uses_navigate_path( self, client_zip_navigate: AsyncClient ) -> None: """Refresh meta tag uses /navigate/ URLs with paths.""" response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "next", "delay": 5} ) assert "/navigate/" in response.text assert "order=next" in response.text assert "delay=5" in response.text async def test_pause_button_uses_navigate_url( self, client_zip_navigate: AsyncClient ) -> None: """Pause button links to /navigate/{path} without query params.""" response = await client_zip_navigate.get( "/navigate/top.txt", params={"order": "next", "delay": 5} ) assert '/navigate/top.txt" class="play-btn"' in response.text class TestNavigateHelperFunctions: """Tests for navigate helper functions.""" def test_find_hash_by_path_returns_hash(self, initialized_zip: None) -> None: """_find_hash_by_path returns the correct hash for a valid path.""" file_hash = main._find_hash_by_path("top.txt") assert file_hash is not None assert file_hash in main.file_mapping def test_find_hash_by_path_returns_none_for_invalid( self, initialized_zip: None ) -> None: """_find_hash_by_path returns None for a path that doesn't exist.""" file_hash = main._find_hash_by_path("nonexistent.txt") assert file_hash is None def test_find_hash_by_path_returns_none_for_directory( self, initialized_zip: None ) -> None: """_find_hash_by_path returns None for a directory path.""" file_hash = main._find_hash_by_path("folder/") assert file_hash is None def test_get_path_from_hash(self, initialized_zip: None) -> None: """_get_path_from_hash returns the correct path for a valid hash.""" # Find a hash that maps to a known path for file_hash, filename in main.indexers[0]._file_mapping.items(): result = main._get_path_from_hash(file_hash) assert result == filename break def test_build_navigate_url_no_params(self) -> None: """_build_navigate_url builds correct URL without query params.""" url = main._build_navigate_url("folder/deep.txt") assert url == "/navigate/folder/deep.txt" def test_build_navigate_url_with_params(self) -> None: """_build_navigate_url builds correct URL with query params.""" url = main._build_navigate_url("folder/deep.txt", order="next", delay=5) assert url == "/navigate/folder/deep.txt?order=next&delay=5" def test_get_navigation_data_by_path(self, initialized_zip: None) -> None: """_get_navigation_data_by_path returns correct navigation data.""" data = main._get_navigation_data_by_path("top.txt") assert data is not None assert "file_hash" in data assert "next_path" in data assert "prev_path" in data assert data["filename"] == "top.txt" def test_get_navigation_data_by_path_returns_none_for_invalid( self, initialized_zip: None ) -> None: """_get_navigation_data_by_path returns None for invalid path.""" data = main._get_navigation_data_by_path("nonexistent.txt") assert data is None