|
|
@@ -3,7 +3,7 @@
|
|
|
// shared helpers (fixture) live in tests.rs.
|
|
|
|
|
|
use super::tests::fixture;
|
|
|
-use super::{ReportRow, collect, html_page, markdown, serve};
|
|
|
+use super::{ReportRow, collect, html_page, html_page_at, markdown, serve};
|
|
|
use crate::config::Config;
|
|
|
use std::fs;
|
|
|
|
|
|
@@ -106,6 +106,60 @@ fn page_notif_diff_js() {
|
|
|
/// router on an ephemeral port (fixture recipe dir), drives page.html with
|
|
|
/// Playwright (deno, npm-free), drops a new report file into the watched
|
|
|
/// dir and expects the badge + panel to react. Skipped without deno/browser.
|
|
|
+/// The viewer page (served under a mount prefix, e.g. the dashboard's
|
|
|
+/// `/recipes` panel) must carry that prefix in META so the follow-SSE URL
|
|
|
+/// builds against the right base — a root-absolute `/r/...` 404s under the
|
|
|
+/// prefix and the follow button looks dead. The prefixed SSE route must be
|
|
|
+/// reachable too.
|
|
|
+#[tokio::test]
|
|
|
+async fn viewer_meta_carries_mount_prefix() {
|
|
|
+ let dir = fixture();
|
|
|
+ let config: Config =
|
|
|
+ toml::from_str(&format!("recipe_sources = [\"{}\"]", dir.to_str().unwrap())).unwrap();
|
|
|
+ let cfg = std::sync::Arc::new(config);
|
|
|
+ let rows = collect(&cfg, false);
|
|
|
+ let page = html_page_at(&rows, "/recipes");
|
|
|
+ let initial = serde_json::to_string(&rows).unwrap();
|
|
|
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
|
+ let addr = listener.local_addr().unwrap();
|
|
|
+ let app = serve::router(cfg, page, initial, false, "/recipes", None);
|
|
|
+ let _h = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
|
|
+ std::thread::sleep(std::time::Duration::from_millis(200));
|
|
|
+ let client = reqwest::Client::new();
|
|
|
+ let base = format!("http://{addr}");
|
|
|
+ let viewer = client
|
|
|
+ .get(format!(
|
|
|
+ "{base}/r/test-recipe-nvfp4/2026-01-04_roce-run.log"
|
|
|
+ ))
|
|
|
+ .send()
|
|
|
+ .await
|
|
|
+ .unwrap()
|
|
|
+ .text()
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert!(
|
|
|
+ viewer.contains("\"base\":\"/recipes\""),
|
|
|
+ "viewer META must carry the mount prefix"
|
|
|
+ );
|
|
|
+ assert!(viewer.contains("followbtn"));
|
|
|
+ // The SSE stream never ends — assert the route is live via its headers
|
|
|
+ // (send() resolves on the response head; drop the body).
|
|
|
+ let sse = client
|
|
|
+ .get(format!(
|
|
|
+ "{base}/r/test-recipe-nvfp4/2026-01-04_roce-run.log?follow=1"
|
|
|
+ ))
|
|
|
+ .send()
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(sse.status(), 200);
|
|
|
+ assert!(
|
|
|
+ sse.headers()
|
|
|
+ .get("content-type")
|
|
|
+ .is_some_and(|v| v.to_str().unwrap().contains("text/event-stream"))
|
|
|
+ );
|
|
|
+ fs::remove_dir_all(&dir).ok();
|
|
|
+}
|
|
|
+
|
|
|
#[tokio::test]
|
|
|
async fn report_playwright_checks() {
|
|
|
if std::process::Command::new("deno")
|