182 lines
6.7 KiB
Python
182 lines
6.7 KiB
Python
"""Tests for --root-path URL prefix support."""
|
|
|
|
import argparse
|
|
from collections.abc import Generator
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
import main
|
|
|
|
|
|
@pytest.fixture
|
|
def args_directory_with_root(
|
|
sample_files: dict[str, Path], tmp_path: Path
|
|
) -> argparse.Namespace:
|
|
"""Argparse namespace with root_path set."""
|
|
return argparse.Namespace(
|
|
source=str(tmp_path),
|
|
host="127.0.0.1",
|
|
port=0,
|
|
salt="test-salt",
|
|
navigate=False,
|
|
root_path="/images",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def initialized_dir_with_root(args_directory_with_root: argparse.Namespace) -> None:
|
|
"""Initialize the server with sample directory files and root_path."""
|
|
main.initialize_server(args_directory_with_root)
|
|
|
|
|
|
@pytest.fixture
|
|
async def client_dir_with_root(
|
|
initialized_dir_with_root: None,
|
|
) -> Generator[AsyncClient]:
|
|
"""Async HTTP client against the app initialized with root_path."""
|
|
transport = ASGITransport(app=main.app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
yield ac
|
|
|
|
|
|
class TestRootPathGlobal:
|
|
"""Tests that root_path is set correctly on initialization."""
|
|
|
|
def test_root_path_set_from_args(self, args_directory_with_root):
|
|
"""root_path global is set from args."""
|
|
main.initialize_server(args_directory_with_root)
|
|
assert main.root_path == "/images"
|
|
|
|
def test_root_path_normalized(self, sample_files, tmp_path):
|
|
"""root_path is normalized (leading/trailing slashes handled)."""
|
|
args = argparse.Namespace(
|
|
source=str(tmp_path),
|
|
host="127.0.0.1",
|
|
port=0,
|
|
salt="test-salt",
|
|
navigate=False,
|
|
root_path="images/",
|
|
)
|
|
main.initialize_server(args)
|
|
assert main.root_path == "/images"
|
|
|
|
def test_root_path_empty_when_not_provided(self, args_directory):
|
|
"""root_path is empty string when not provided."""
|
|
main.initialize_server(args_directory)
|
|
assert main.root_path == ""
|
|
|
|
def test_app_root_path_set(self, args_directory_with_root):
|
|
"""FastAPI app.root_path is set for proper URL generation."""
|
|
main.initialize_server(args_directory_with_root)
|
|
assert main.app.root_path == "/images"
|
|
|
|
|
|
class TestRootPathURLBuilders:
|
|
"""Tests that URL builder functions include root_path."""
|
|
|
|
def test_build_url_includes_root_path(self, initialized_dir_with_root):
|
|
"""_build_url includes root_path prefix."""
|
|
url = main._build_url("abc123")
|
|
assert url == "/images/abc123"
|
|
|
|
def test_build_url_with_params_includes_root_path(self, initialized_dir_with_root):
|
|
"""_build_url with order/delay includes root_path prefix."""
|
|
url = main._build_url("abc123", order="next", delay=5)
|
|
assert url == "/images/abc123?order=next&delay=5"
|
|
|
|
def test_build_navigate_url_includes_root_path(self, initialized_dir_with_root):
|
|
"""_build_navigate_url includes root_path prefix."""
|
|
url = main._build_navigate_url("folder/file.txt")
|
|
assert url == "/images/navigate/folder/file.txt"
|
|
|
|
def test_build_navigate_url_with_params_includes_root_path(
|
|
self, initialized_dir_with_root
|
|
):
|
|
"""_build_navigate_url with order/delay includes root_path prefix."""
|
|
url = main._build_navigate_url("folder/file.txt", order="random", delay=3)
|
|
assert url == "/images/navigate/folder/file.txt?order=random&delay=3"
|
|
|
|
def test_build_url_no_root_when_empty(self, initialized_dir):
|
|
"""_build_url has no prefix when root_path is empty."""
|
|
url = main._build_url("abc123")
|
|
assert url == "/abc123"
|
|
|
|
|
|
class TestRootPathEndpoints:
|
|
"""Tests that endpoint responses include root_path in URLs."""
|
|
|
|
async def test_root_redirect_includes_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Root redirect location includes root_path."""
|
|
response = await client_dir_with_root.get("/", follow_redirects=False)
|
|
assert response.status_code == 307
|
|
location = response.headers["location"]
|
|
assert location.startswith("/images/")
|
|
|
|
async def test_root_redirect_with_params_includes_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Root redirect with order/delay includes root_path."""
|
|
response = await client_dir_with_root.get(
|
|
"/", params={"order": "next", "delay": 5}, follow_redirects=False
|
|
)
|
|
assert response.status_code == 307
|
|
location = response.headers["location"]
|
|
assert location.startswith("/images/")
|
|
assert "order=next" in location
|
|
assert "delay=5" in location
|
|
|
|
async def test_hash_page_image_url_includes_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Hash page img_url includes root_path."""
|
|
file_hash = list(main.file_mapping.keys())[0]
|
|
response = await client_dir_with_root.get(f"/{file_hash}")
|
|
assert response.status_code == 200
|
|
assert "/images/api/" in response.text
|
|
|
|
async def test_hash_page_nav_urls_include_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Hash page prev/next URLs include root_path."""
|
|
file_hash = list(main.file_mapping.keys())[0]
|
|
response = await client_dir_with_root.get(f"/{file_hash}")
|
|
assert response.status_code == 200
|
|
# Check that chevron links use root_path
|
|
import re
|
|
|
|
# Find href values on chevron elements
|
|
chevrons = re.findall(
|
|
r'class="chevron (left|right)"[^>]*href="([^"]*)"', response.text
|
|
)
|
|
for direction, href in chevrons:
|
|
assert href.startswith(
|
|
"/images/"
|
|
), f"Chevron {direction} href {href} missing root_path"
|
|
|
|
async def test_hash_page_play_button_includes_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Hash page play button URL includes root_path."""
|
|
file_hash = list(main.file_mapping.keys())[0]
|
|
response = await client_dir_with_root.get(f"/{file_hash}")
|
|
assert response.status_code == 200
|
|
# Play button should have root_path in its href
|
|
assert "/images/" in response.text
|
|
|
|
async def test_hash_page_refresh_includes_root_path(
|
|
self, client_dir_with_root: AsyncClient
|
|
):
|
|
"""Hash page in refresh mode includes root_path in meta refresh."""
|
|
file_hash = list(main.file_mapping.keys())[0]
|
|
response = await client_dir_with_root.get(
|
|
f"/{file_hash}", params={"order": "next", "delay": 5}
|
|
)
|
|
assert response.status_code == 200
|
|
assert 'http-equiv="refresh"' in response.text
|
|
# The refresh URL should include root_path
|
|
assert "/images/" in response.text
|