From d33a7775564653c814f94d9f0f367289849f0d5e Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 18 Jul 2026 16:15:17 -0400 Subject: [PATCH] test(admin): assert /admin serves the console over HTTP Route-level test (tower oneshot, no DB) confirms GET /admin returns the console HTML with text/html, and GET /admin/assets/.js returns the built bundle with a JS content-type. Pairs with the embed test so both 'not built' and 'not wired' regressions fail CI. --- src/api/admin.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/api/admin.rs b/src/api/admin.rs index f629ef1..30b980d 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -1120,6 +1120,55 @@ mod tests { ); } + // Exercise the real serve path (routing + content-type + body) without a DB: + // admin_index / admin_asset are stateless, so a tiny router reproduces exactly + // what an operator's browser hits at GET /admin and GET /admin/assets/.js. + #[tokio::test] + async fn admin_console_serves_over_http() { + use axum::{body::Body, routing::get, Router}; + use tower::ServiceExt; + + let app: Router = Router::new() + .route("/admin", get(super::admin_index)) + .route("/admin/assets/*path", get(super::admin_asset)); + + // GET /admin -> the console HTML. + let res = app + .clone() + .oneshot( + axum::http::Request::builder() + .uri("/admin") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(res.status(), axum::http::StatusCode::OK); + assert_eq!(res.headers()["content-type"], "text/html; charset=utf-8"); + let body = axum::body::to_bytes(res.into_body(), 1 << 20).await.unwrap(); + assert!(std::str::from_utf8(&body).unwrap().contains("Smirk Operator Console")); + + // GET /admin/assets/ -> served with a sane content-type. + let asset = AdminAssets::iter() + .find(|p| p.starts_with("assets/") && p.ends_with(".js")) + .expect("a built JS asset exists"); + let uri = format!("/admin/{asset}"); // asset already carries the "assets/" prefix + let res = app + .oneshot( + axum::http::Request::builder() + .uri(&uri) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(res.status(), axum::http::StatusCode::OK); + assert_eq!( + res.headers()["content-type"], + "application/javascript; charset=utf-8", + ); + } + #[test] fn host_allowlist() { // Missing host is allowed (rebinding must supply a host; bind is primary).