"""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