From 2becf44e1f7dd7449f5457408fd265e1d7b23854 Mon Sep 17 00:00:00 2001 From: "druks-ai[bot]" <339005418+druks-ai[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 14:32:08 +0000 Subject: [PATCH] DRU-1149 -- Send Accept: text/html on the SPA client-route test requests FastAPI serves the index.html fallback only to a navigation request. FastAPI 0.162 no longer counts a bare Accept: */* as one. The two /work_items/42 requests now send Accept: text/html, as a browser navigation does. The serve_spa docstring states this requirement. The test module imports Session at module scope. Co-Authored-By: paulo@druks.ai --- backend/druks/api/server.py | 11 ++++++----- backend/tests/test_spa_serving.py | 10 ++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/backend/druks/api/server.py b/backend/druks/api/server.py index 1a464f4be..7150811b4 100644 --- a/backend/druks/api/server.py +++ b/backend/druks/api/server.py @@ -376,11 +376,12 @@ async def mcp_not_found(path: str) -> None: def serve_spa(app: FastAPI, dist: Path = _SPA_DIST) -> None: - """Serve the platform's own dashboard the way an app's ``dist/`` is - served — FastAPI's low-priority frontend routes, so every API path (and every - app frontend, registered earlier) wins, and unknown paths fall back to - index.html for client-side routing. A bare pip install ships no SPA build; - then the API serves JSON only.""" + """Serve the platform's own dashboard the way Druks serves an app's ``dist/``. + FastAPI serves both from low-priority frontend routes. Every API path wins over + them. Every app frontend registers earlier and also wins. An unknown path falls + back to index.html for client-side routing. The index.html fallback needs a + navigation request with an ``Accept: text/html`` header. A bare pip install + ships no SPA build. Without a build, the API serves JSON only.""" if (dist / "index.html").is_file(): app.frontend("/", directory=dist) diff --git a/backend/tests/test_spa_serving.py b/backend/tests/test_spa_serving.py index 02b97294f..ea508f32c 100644 --- a/backend/tests/test_spa_serving.py +++ b/backend/tests/test_spa_serving.py @@ -5,6 +5,7 @@ from druks.db import db_session from fastapi import Depends, FastAPI, HTTPException from fastapi.testclient import TestClient +from sqlalchemy.orm import Session def _app_with_spa(dist: Path) -> FastAPI: @@ -30,7 +31,7 @@ def test_spa_serves_index_assets_and_client_routes(tmp_path): assert "druks" in client.get("/").text assert client.get("/assets/app.js").status_code == 200 # An unknown path is a client-side route: index.html, not a 404. - assert "druks" in client.get("/work_items/42").text + assert "druks" in client.get("/work_items/42", headers={"Accept": "text/html"}).text def test_api_paths_win_over_the_spa(tmp_path): @@ -55,15 +56,16 @@ def test_cache_policy_lives_with_the_server(tmp_path): assert asset.headers["cache-control"] == "public, max-age=31536000, immutable" # index.html must revalidate every load or a deploy leaves stale bundles. assert client.get("/").headers["cache-control"] == "no-cache" - assert client.get("/work_items/42").headers["cache-control"] == "no-cache" + assert ( + client.get("/work_items/42", headers={"Accept": "text/html"}).headers["cache-control"] + == "no-cache" + ) # API responses carry no frontend cache policy; nor does a missing asset. assert "cache-control" not in client.get("/api/thing").headers assert "cache-control" not in client.get("/assets/gone.js").headers def test_request_that_never_touches_the_db_opens_no_connection(monkeypatch): - from sqlalchemy.orm import Session - connected = [] original = Session._connection_for_bind