test(server): regression tests for SPA fallback on client-side routes (fabro-q5 brief) - #41
test(server): regression tests for SPA fallback on client-side routes (fabro-q5 brief)#41zenprocess wants to merge 1 commit into
Conversation
… (fabro-q5 brief) The deployed build is a browser-router (createBrowserRouter in apps/fabro-web/app/entry.tsx), not the hash-router the brief assumed. URLs like /runs and /runs/<id>/stages are real server paths, and the server already has the HTML fallback wired through static_files.rs. These four tests lock that behavior in so a future revert is caught: runs_route_serves_spa_index_for_bookmarked_link runs_detail_route_serves_spa_index_for_bookmarked_link runs_route_404s_for_non_html_clients_to_avoid_silent_shells runs_route_404s_for_post_requests Together they pin both halves of the contract (HTML routes resolve to index.html; non-HTML and non-GET/HEAD keep getting 404). No server code change: this is a pure regression-test-only addition per the brief's thin-CI scope. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Evidence — reviewer could not fetch the diff blobI am re-posting the acceptance evidence here so review does not have to wait for git push problems to clear. This is what re-running on a clean checkout should produce; the coach will independently re-run before accepting. Acceptance commandPer the brief: head_rc
base_rc — two-state proof that the test FAILS when the fallback is revertedTo produce this, I commented out the SPA fallback ( State 1 — fallback REVERTED (
State 2 — fallback RESTORED (this commit's source tree, as-pushed):
So the test produces How to reproduce independently# Clean clone / fresh worktree, on this branch.
ulimit -n 4096 && cargo nextest run -p fabro-server spa_fallback_for_client_routes::
# expect: 4 passed, head_rc == 0
# To see the test catch a revert:
git checkout -b reproduce-tmp-fix/spa-fallback-runs
# edit lib/apps/fabro-server/src/static_files.rs and wrap the
# `if accepts_html(headers) { load_asset_for_mode("index.html", …) … }`
# block in /* … */ comments.
cargo test -p fabro-server --lib spa_fallback_for_client_routes::runs_route_serves_spa_index_for_bookmarked_link
# expect: 1 FAILED with `left: 404, right: 200`
git checkout fix/spa-fallback-runs
git branch -D reproduce-tmp-fix/spa-fallback-runsWhat this PR containsNo production code change. The brief's "hash-router" premise was wrong (the repo has used 🤖 Generated with Claude Code |
Summary
Regression tests for the SPA fallback that serves the app shell for client-side routes (
/runs,/runs/<id>/stages, etc.). Brief: ao-company#306.The server-side SPA fallback already lives in
lib/apps/fabro-server/src/static_files.rs(theif accepts_html(headers) { load_asset_for_mode("index.html", …) }block at the end ofserve_with_mode) and is wired into the router atlib/apps/fabro-server/src/server.rs:1878(theelse if web_enabled && matches!(req.method(), &Method::GET | &Method::HEAD)branch of the fallback service).This PR adds the missing regression surface: tests that pin both halves of the contract, so a future revert of the fallback is caught.
Why
The operator's bookmarked
/runslink currently works because the fallback is in place — but no test in the suite exercised it. Only a behavioral test catches a future revert. Four tests now do.Acceptance
Expected: 4 passed, 0 failed.
Two-state proof (test fails when fallback is reverted)
Test run #1 — with the fallback REVERTED in
lib/apps/fabro-server/src/static_files.rs(theif accepts_html(headers) { … }block commented out, no other code changed):Test run #2 — fallback restored, all four tests:
This is the regression surface: a revert of the fallback produces a non-zero exit (
base_rc != 0); with the fallback in place, exit 0 (head_rc = 0).What the four tests pin
runs_route_serves_spa_index_for_bookmarked_link— GET/runswith browserAccept: text/html,…returns 200 + the embedded SPA shell. Primary regression guard.runs_detail_route_serves_spa_index_for_bookmarked_link— deep route/runs/<id>/stagesreturns the same. The deep-link case is the operator's actual bookmark shape.runs_route_404s_for_non_html_clients_to_avoid_silent_shells— GET/runswithAccept: application/jsonreturns 404. Prevents widening the fallback so a strayfetch()/ typo'd API request silently receives a 25 KB HTML shell.runs_route_404s_for_post_requests— POST/runsreturns 404. Only GET/HEAD reach the SPA fallback; this test fails if the method match is ever widened.Diff scope
No production code change. The fallback was already correct; the brief's "hash-router" premise was wrong (the repo has used
createBrowserRoutersince 9b9ebdf), and the server already handled browser-router URLs.Live path / unrelated notes
Cargo.lockchange that builds up under this worktree is an unrelatedfabro-refereeworkspace entry that regenerates on build. Not part of this PR; reverted locally before the branch was pushed./api/v1/runs/registrations, live since fabro server38b26303with zero prior callers) is being tracked separately on ao-company#306. End-to-end live path there is unproven today (sandbox-creation failures with firecracker snapshot-load 400); the brief instructed using a local mock stand-in and stating plainly that the live path is unproven — that change lives in theao-companyrepo, not here.qa-pipelinereportsno-testson this repo and exits 0 — a manufactured green. Not cited. Real runner iscargo nextest run -p fabro-serverper the brief.🤖 Generated with Claude Code