mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-24 05:35:31 -04:00
8f5e36a079
* fix(routes): serve 404 instead of 500 when an HTML page file is missing _serve_html_with_nonce opened the HTML file with no error handling, and callers such as /backgrounds and /login pass their paths in with no existence check, so a missing or unreadable file raised an unhandled OSError that surfaced as a 500. Wrap the read and raise HTTPException(404) instead; the normal render path (CSP-nonce substitution) is unchanged. Fixes #4594 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(routes): distinguish missing page (404) from read failure (500) The previous fix caught a broad OSError and returned 404 for every failure, which masks real server-side problems (permission errors, I/O failures) as "not found" and lets them slip past error alerting. Split FileNotFoundError (genuine 404) from other OSError, which now logs the exception and returns a generic 500 — without leaking the OS error string or file path into the response body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(routes): treat unreadable bundled HTML page as logged 500, not 404 Per PR #4637 review: every caller of the page-render helper serves a fixed, server-owned template (index/login/backgrounds), never a client-supplied path. So a missing or unreadable file is a server fault (broken deployment), not a client "not found" — a 404 there mislabels a server error and hides a missing core template from 5xx alerting, contradicting the OSError->500 rationale this PR is built on. Collapse both branches into a single logged, leak-free 500. Move the helper to src.app_helpers.serve_html_with_nonce so the behavior can be unit-tested without importing the whole app (app.py is the slim orchestrator; the test harness stubs src.database, so importing app in tests is not viable). Add tests pinning missing/unreadable -> 500 (not 404) and nonce injection on the happy path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
"""Behavior tests for src.app_helpers.serve_html_with_nonce.
|
|
|
|
Every caller of this helper serves a fixed, app-bundled template
|
|
(index/login/backgrounds), never a client-supplied path. So a read failure —
|
|
a missing file (broken deployment) or a permission/IO error — is a server
|
|
fault, not a client "not found", and must surface as a logged 500 rather than
|
|
hiding behind a 404 where 5xx alerting can't see it. These tests lock that
|
|
intent (raised in the PR #4637 review).
|
|
"""
|
|
import types
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip("fastapi")
|
|
pytest.importorskip("starlette.responses")
|
|
from fastapi import HTTPException
|
|
|
|
from src.app_helpers import serve_html_with_nonce
|
|
|
|
|
|
def _request_with_nonce(nonce: str = ""):
|
|
"""Minimal stand-in for a Starlette Request: only request.state.csp_nonce is read."""
|
|
return types.SimpleNamespace(state=types.SimpleNamespace(csp_nonce=nonce))
|
|
|
|
|
|
def test_missing_fixed_template_returns_500_not_404(tmp_path):
|
|
missing = tmp_path / "does_not_exist.html"
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
serve_html_with_nonce(_request_with_nonce(), str(missing))
|
|
assert exc_info.value.status_code == 500
|
|
# Generic detail — no OS error string or absolute path leaked to the client.
|
|
assert exc_info.value.detail == "Internal server error"
|
|
|
|
|
|
def test_unreadable_template_returns_500(tmp_path):
|
|
# A directory at the path makes open() raise an OSError subtype
|
|
# (IsADirectoryError on POSIX, PermissionError on Windows) — same branch.
|
|
a_dir = tmp_path / "a_dir.html"
|
|
a_dir.mkdir()
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
serve_html_with_nonce(_request_with_nonce(), str(a_dir))
|
|
assert exc_info.value.status_code == 500
|
|
|
|
|
|
def test_readable_template_injects_nonce(tmp_path):
|
|
page = tmp_path / "page.html"
|
|
page.write_text('<script nonce="{{CSP_NONCE}}">x</script>', encoding="utf-8")
|
|
resp = serve_html_with_nonce(_request_with_nonce("nonce-abc"), str(page))
|
|
assert resp.status_code == 200
|
|
body = resp.body.decode("utf-8")
|
|
assert "nonce-abc" in body
|
|
assert "{{CSP_NONCE}}" not in body
|