Task 4: Add Services & Utilities tests (66 tests)
- test_media_utils.py: 29 tests for check_media_files, get_media_file_paths, has_all_images, count_video_files, has_video_files, constants - test_nfo_factory.py: 11 tests for NFOServiceFactory.create, create_optional, get_nfo_factory singleton, create_nfo_service convenience - test_series_manager_service.py: 15 tests for init, from_settings, process_nfo_for_series, scan_and_process_nfo, close - test_templates_utils.py: 4 tests for TEMPLATES_DIR path resolution - test_error_controller.py: 7 tests for 404/500 handlers (API vs HTML)
This commit is contained in:
110
tests/unit/test_error_controller.py
Normal file
110
tests/unit/test_error_controller.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""Unit tests for error controller module.
|
||||
|
||||
Tests custom 404 and 500 error handlers for both API and HTML responses.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException, Request
|
||||
|
||||
from src.server.controllers.error_controller import (
|
||||
not_found_handler,
|
||||
server_error_handler,
|
||||
)
|
||||
|
||||
|
||||
def _make_request(path: str = "/") -> MagicMock:
|
||||
"""Create a mock Request object with a given path."""
|
||||
request = MagicMock(spec=Request)
|
||||
url = MagicMock()
|
||||
url.path = path
|
||||
request.url = url
|
||||
return request
|
||||
|
||||
|
||||
class TestNotFoundHandler:
|
||||
"""Tests for the 404 not_found_handler."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_path_returns_json(self):
|
||||
"""API paths get a JSON response with 404 status."""
|
||||
request = _make_request("/api/anime/123")
|
||||
exc = HTTPException(status_code=404)
|
||||
resp = await not_found_handler(request, exc)
|
||||
assert resp.status_code == 404
|
||||
assert resp.body is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("src.server.controllers.error_controller.render_template")
|
||||
async def test_web_path_renders_template(self, mock_render):
|
||||
"""Non-API paths render the error template."""
|
||||
mock_render.return_value = MagicMock(status_code=404)
|
||||
request = _make_request("/anime/details")
|
||||
exc = HTTPException(status_code=404)
|
||||
await not_found_handler(request, exc)
|
||||
mock_render.assert_called_once_with(
|
||||
"error.html",
|
||||
request,
|
||||
context={"error": "Page not found", "status_code": 404},
|
||||
title="404 - Not Found",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_json_structure(self):
|
||||
"""API 404 response has 'detail' field."""
|
||||
import json
|
||||
request = _make_request("/api/missing")
|
||||
exc = HTTPException(status_code=404)
|
||||
resp = await not_found_handler(request, exc)
|
||||
body = json.loads(resp.body)
|
||||
assert body["detail"] == "API endpoint not found"
|
||||
|
||||
|
||||
class TestServerErrorHandler:
|
||||
"""Tests for the 500 server_error_handler."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_path_returns_json(self):
|
||||
"""API paths get a JSON response with 500 status."""
|
||||
request = _make_request("/api/download")
|
||||
exc = RuntimeError("crash")
|
||||
resp = await server_error_handler(request, exc)
|
||||
assert resp.status_code == 500
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("src.server.controllers.error_controller.render_template")
|
||||
async def test_web_path_renders_template(self, mock_render):
|
||||
"""Non-API paths render the error template."""
|
||||
mock_render.return_value = MagicMock(status_code=500)
|
||||
request = _make_request("/settings")
|
||||
exc = RuntimeError("crash")
|
||||
await server_error_handler(request, exc)
|
||||
mock_render.assert_called_once_with(
|
||||
"error.html",
|
||||
request,
|
||||
context={"error": "Internal server error", "status_code": 500},
|
||||
title="500 - Server Error",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_error_does_not_expose_stack_trace(self):
|
||||
"""API 500 response doesn't contain the actual error message."""
|
||||
import json
|
||||
request = _make_request("/api/vulnerable")
|
||||
exc = RuntimeError("secret database credentials")
|
||||
resp = await server_error_handler(request, exc)
|
||||
body = json.loads(resp.body)
|
||||
assert "secret" not in body["detail"]
|
||||
assert body["detail"] == "Internal server error"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_path_detection(self):
|
||||
"""Correctly distinguishes API vs web paths."""
|
||||
api_request = _make_request("/api/test")
|
||||
web_request = _make_request("/dashboard")
|
||||
|
||||
# API path returns JSONResponse
|
||||
from fastapi.responses import JSONResponse
|
||||
resp = await server_error_handler(api_request, Exception("err"))
|
||||
assert isinstance(resp, JSONResponse)
|
||||
Reference in New Issue
Block a user