image_server/tests/test_endpoints.py
2026-04-23 22:11:51 -05:00

186 lines
7.7 KiB
Python

"""Tests for FastAPI endpoints."""
import pytest
from httpx import AsyncClient
import main
class TestHealthCheck:
"""Tests for GET /api/health."""
async def test_returns_200(self, client_dir: AsyncClient) -> None:
"""Health endpoint returns 200 OK."""
response = await client_dir.get("/api/health")
assert response.status_code == 200
async def test_returns_file_count(self, client_dir: AsyncClient) -> None:
"""Health endpoint reports correct file count."""
response = await client_dir.get("/api/health")
data = response.json()
assert data["status"] == "healthy"
assert data["file_count"] == len(main.file_mapping)
async def test_returns_zero_when_empty(self) -> None:
"""Health endpoint returns 0 when no files are indexed."""
main.file_mapping.clear()
from httpx import ASGITransport
transport = ASGITransport(app=main.app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
response = await ac.get("/api/health")
data = response.json()
assert data["file_count"] == 0
class TestGetFileData:
"""Tests for GET /api/{file_hash}/data."""
async def test_returns_file_content(self, client_dir: AsyncClient) -> None:
"""Returns the content of a valid file."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/api/{file_hash}/data")
assert response.status_code == 200
assert len(response.content) > 0
async def test_returns_correct_content_type_for_text(
self, client_dir: AsyncClient
) -> None:
"""Text files get text/plain content type."""
# Find a .txt file hash
for file_hash, filepath in main.file_mapping.items():
if filepath.endswith(".txt"):
response = await client_dir.get(f"/api/{file_hash}/data")
assert response.status_code == 200
assert "text/plain" in response.headers["content-type"]
break
async def test_returns_correct_content_type_for_image(
self, client_dir: AsyncClient
) -> None:
"""Image files get appropriate content type."""
for file_hash, filepath in main.file_mapping.items():
if filepath.endswith(".jpg"):
response = await client_dir.get(f"/api/{file_hash}/data")
assert response.status_code == 200
assert "image/jpeg" in response.headers["content-type"]
break
async def test_returns_404_for_invalid_hash(self, client_dir: AsyncClient) -> None:
"""Returns 404 for a hash that doesn't exist."""
response = await client_dir.get("/api/nonexistent-hash/data")
assert response.status_code == 404
async def test_has_content_disposition_header(
self, client_dir: AsyncClient
) -> None:
"""Response includes Content-Disposition header."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/api/{file_hash}/data")
assert "content-disposition" in response.headers
assert "inline" in response.headers["content-disposition"]
async def test_zip_file_content(self, client_zip: AsyncClient) -> None:
"""Files from a zip archive are served correctly."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_zip.get(f"/api/{file_hash}/data")
assert response.status_code == 200
assert len(response.content) > 0
class TestRootRedirect:
"""Tests for GET /."""
async def test_redirects_to_random_hash(self, client_dir: AsyncClient) -> None:
"""Root redirects (307) to a random file hash page."""
response = await client_dir.get("/", follow_redirects=False)
assert response.status_code in (307, 302, 301)
location = response.headers["location"]
# Location should be a hash in the mapping
hash_from_url = location.lstrip("/")
assert hash_from_url in main.file_mapping
class TestOrderDelayRoute:
"""Tests for GET /{order}/{delay}."""
async def test_next_order_redirects(self, client_dir: AsyncClient) -> None:
"""/next/5 redirects to /next/5/{hash}."""
response = await client_dir.get("/next/5", follow_redirects=False)
assert response.status_code in (307, 302, 301)
assert "/next/5/" in response.headers["location"]
async def test_random_order_redirects(self, client_dir: AsyncClient) -> None:
"""/random/3 redirects to /random/3/{hash}."""
response = await client_dir.get("/random/3", follow_redirects=False)
assert response.status_code in (307, 302, 301)
assert "/random/3/" in response.headers["location"]
class TestHashPage:
"""Tests for GET /{file_hash}."""
async def test_returns_html_page(self, client_dir: AsyncClient) -> None:
"""Returns an HTML page for a valid file hash."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/{file_hash}")
assert response.status_code == 200
assert "text/html" in response.headers["content-type"]
async def test_page_contains_image_url(self, client_dir: AsyncClient) -> None:
"""HTML page contains the image URL."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/{file_hash}")
assert f"/api/{file_hash}/data" in response.text
async def test_page_contains_prev_next_buttons(
self, client_dir: AsyncClient
) -> None:
"""HTML page contains prev and next navigation."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/{file_hash}")
assert 'class="chevron left"' in response.text
assert 'class="chevron right"' in response.text
async def test_returns_404_for_invalid_hash(self, client_dir: AsyncClient) -> None:
"""Returns 404 for a hash that doesn't exist."""
response = await client_dir.get("/nonexistent-hash")
assert response.status_code == 404
class TestHashPageWithRefresh:
"""Tests for GET /{order}/{delay}/{file_hash}."""
async def test_next_order_returns_html(self, client_dir: AsyncClient) -> None:
"""Next order returns HTML with refresh meta tag."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/next/5/{file_hash}")
assert response.status_code == 200
assert 'http-equiv="refresh"' in response.text
async def test_random_order_returns_html(self, client_dir: AsyncClient) -> None:
"""Random order returns HTML with refresh meta tag."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/random/3/{file_hash}")
assert response.status_code == 200
assert 'http-equiv="refresh"' in response.text
async def test_invalid_order_returns_400(self, client_dir: AsyncClient) -> None:
"""Invalid order parameter returns 400."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/shuffle/5/{file_hash}")
assert response.status_code == 400
async def test_returns_404_for_invalid_hash(self, client_dir: AsyncClient) -> None:
"""Returns 404 for a hash that doesn't exist."""
response = await client_dir.get("/next/5/nonexistent-hash")
assert response.status_code == 404
async def test_refresh_url_points_to_next_file(
self, client_dir: AsyncClient
) -> None:
"""Refresh meta tag points to the next file in sequence."""
file_hash = list(main.file_mapping.keys())[0]
response = await client_dir.get(f"/next/5/{file_hash}")
assert "url=/next/5/" in response.text