"""SPA fallback (core/spa.py) on a bare FastAPI app with a fake Vite dist: known routes → 200 shell, unknown → 404 shell,
real files served, hashed assets immutable + gzipped, API paths never answered by the shell."""
from __future__ import annotations
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.gzip import GZipMiddleware
@pytest.fixture(scope="module")
def spa_client(tmp_path_factory, app):
from core import errors, http, spa
dist = tmp_path_factory.mktemp("dist")
(dist / "index.html").write_text("
" + " " * 1500)
(dist / "assets").mkdir()
(dist / "assets" / "index-abc123.js").write_text("console.log('hfmd');" * 200)
(dist / "favicon.svg").write_text("")
(dist / "docs").mkdir()
(dist / "docs" / "index.html").write_text("prerendered docs")
(dist / "limits.html").write_text("prerendered limits")
a = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
errors.install(a)
a.add_middleware(GZipMiddleware, minimum_size=1024)
@a.get("/v1/ping")
def ping():
return {"ok": True}
@a.get("/health")
def health():
return {"status": "ok"}
http.install(a)
assert spa.install(a, dist) is True
with TestClient(a) as c:
yield c
def test_root_and_known_routes_serve_shell_200(spa_client):
for path in ("/", "/playground", "/charts", "/signin", "/dashboard", "/dashboard/keys", "/admin/users", "/integrations/mcp", "/pricing"):
r = spa_client.get(path)
assert r.status_code == 200, path
assert "text/html" in r.headers["content-type"] and "
" in r.text
assert r.headers["Cache-Control"] == "no-cache"
def test_prerendered_shells_served(spa_client):
r = spa_client.get("/docs")
assert r.status_code == 200 and "prerendered docs" in r.text
r = spa_client.get("/docs/errors") # no prerender → shell, still a known route
assert r.status_code == 200 and "
" in r.text
r = spa_client.get("/limits")
assert r.status_code == 200 and "prerendered limits" in r.text
def test_unknown_route_is_404_with_shell(spa_client):
r = spa_client.get("/this-page-does-not-exist")
assert r.status_code == 404
assert "
" in r.text and "text/html" in r.headers["content-type"]
r = spa_client.get("/dashboardx")
assert r.status_code == 404
def test_real_files_served(spa_client):
r = spa_client.get("/favicon.svg")
assert r.status_code == 200 and "svg" in r.headers["content-type"]
def test_assets_immutable_and_gzipped(spa_client):
r = spa_client.get("/assets/index-abc123.js", headers={"Accept-Encoding": "gzip"})
assert r.status_code == 200
assert r.headers["Cache-Control"] == "public, max-age=31536000, immutable"
assert r.headers.get("Content-Encoding") == "gzip"
assert r.headers["X-Frame-Options"] == "DENY"
r = spa_client.get("/assets/missing-000.js")
assert r.status_code == 404
assert "Cache-Control" not in r.headers or "immutable" not in r.headers["Cache-Control"]
def test_html_gets_csp_not_frame_options(spa_client):
r = spa_client.get("/")
csp = r.headers["Content-Security-Policy"]
assert "default-src 'self'" in csp and "frame-ancestors 'none'" in csp and "cdn.jsdelivr.net" not in csp
assert "X-Frame-Options" not in r.headers
assert r.headers["Strict-Transport-Security"].startswith("max-age=")
def test_api_paths_never_get_the_shell(spa_client):
r = spa_client.get("/v1/does-not-exist")
assert r.status_code == 404 and r.headers["content-type"].startswith("application/json")
assert r.json()["error"]["code"] == "NOT_FOUND"
r = spa_client.get("/v1/ping")
assert r.status_code == 200 and r.json() == {"ok": True}
r = spa_client.get("/openapi.json")
assert r.status_code == 404 and r.headers["content-type"].startswith("application/json")
r = spa_client.get("/swagger")
assert r.status_code == 404 and r.headers["content-type"].startswith("application/json")
def test_login_redirect(spa_client):
r = spa_client.get("/login", follow_redirects=False)
assert r.status_code == 301 and r.headers["Location"] == "/signin"
def test_path_traversal_blocked(spa_client):
r = spa_client.get("/../../etc/passwd")
assert r.status_code in (404, 200)
assert "root:" not in r.text