use axum::Router;
use axum::http::header;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
const SHELL_HTML: &str = include_str!("assets/index.html");
const APP_JS: &str = include_str!("assets/app.js");
const CYTOSCAPE_JS: &str = include_str!("assets/cytoscape.min.js");
const STICKER_SVG: &str = include_str!("assets/sticker.svg");
const HTML: &str = "text/html; charset=utf-8";
const JS: &str = "text/javascript; charset=utf-8";
const SVG: &str = "image/svg+xml; charset=utf-8";
const CACHE_HTML: &str = "public, max-age=300";
const CACHE_JS: &str = "public, max-age=3600";
pub fn router() -> Router {
Router::new()
.route("/", get(|| async { asset(HTML, CACHE_HTML, SHELL_HTML) }))
.route(
"/explorer",
get(|| async { asset(HTML, CACHE_HTML, SHELL_HTML) }),
)
.route("/app.js", get(|| async { asset(JS, CACHE_JS, APP_JS) }))
.route(
"/vendor/cytoscape.min.js",
get(|| async { asset(JS, CACHE_JS, CYTOSCAPE_JS) }),
)
.route(
"/sticker.svg",
get(|| async { asset(SVG, CACHE_JS, STICKER_SVG) }),
)
}
fn asset(content_type: &'static str, cache_control: &'static str, body: &'static str) -> Response {
(
[
(header::CONTENT_TYPE, content_type),
(header::CACHE_CONTROL, cache_control),
],
body,
)
.into_response()
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt as _;
use tower::ServiceExt as _;
async fn get(uri: &str) -> (StatusCode, String, String, String) {
let resp = router()
.oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap())
.await
.unwrap();
let status = resp.status();
let headers = resp.headers();
let header_str = |name: header::HeaderName| {
headers
.get(name)
.and_then(|v| v.to_str().ok())
.unwrap_or_default()
.to_owned()
};
let ct = header_str(header::CONTENT_TYPE);
let cache = header_str(header::CACHE_CONTROL);
let body = resp.into_body().collect().await.unwrap().to_bytes();
(
status,
ct,
cache,
String::from_utf8_lossy(&body).into_owned(),
)
}
#[tokio::test]
async fn root_serves_html_shell_referencing_app_and_cytoscape() {
let (status, ct, _cache, body) = get("/").await;
assert_eq!(status, StatusCode::OK);
assert!(ct.starts_with("text/html"), "content-type was {ct}");
assert!(body.contains("<!doctype html>"));
assert!(body.contains("/app.js"), "shell must load our app script");
assert!(
body.contains("/vendor/cytoscape.min.js"),
"shell must load the vendored graph library"
);
}
#[tokio::test]
async fn explorer_alias_serves_the_same_shell() {
let (status, ct, _cache, body) = get("/explorer").await;
assert_eq!(status, StatusCode::OK);
assert!(ct.starts_with("text/html"));
assert!(body.contains("<!doctype html>"));
}
#[tokio::test]
async fn shell_scaffolds_the_project_drill_in_view() {
let (status, _ct, _cache, body) = get("/").await;
assert_eq!(status, StatusCode::OK);
for needle in [
"id=\"view-project\"",
"id=\"p-graph\"",
"colour: provenance",
"find in this repo",
"data-tab=\"hotspots\"",
"data-tab=\"node\"",
"data-tab=\"ask\"",
] {
assert!(body.contains(needle), "shell must contain `{needle}`");
}
assert!(
body.contains("requires the model build") || body.contains("roteiro serve --models"),
"Ask tab must explain it needs the model build"
);
for needle in [
"role=\"tablist\"",
"aria-controls=\"p-pane-hotspots\"",
"aria-selected=\"true\"",
"aria-labelledby=\"p-tab-node\"",
"aria-disabled=\"true\"",
] {
assert!(body.contains(needle), "shell must contain `{needle}`");
}
}
#[tokio::test]
async fn served_assets_are_free_of_raw_control_chars() {
for uri in ["/", "/app.js"] {
let (_s, _c, _cc, body) = get(uri).await;
assert!(
!body
.bytes()
.any(|b| b < 0x20 && b != b'\t' && b != b'\n' && b != b'\r'),
"{uri} contains a raw control character"
);
}
}
#[tokio::test]
async fn app_js_consumes_the_project_data_endpoints() {
let (status, _ct, _cache, body) = get("/app.js").await;
assert_eq!(status, StatusCode::OK);
for needle in [
"/hotspots",
"/debt",
"/node/",
"loadProject",
"navigateToProject",
] {
assert!(body.contains(needle), "app.js must reference `{needle}`");
}
}
#[tokio::test]
async fn app_js_wires_the_ask_tab_to_capabilities_and_chat() {
let (status, _ct, _cache, body) = get("/app.js").await;
assert_eq!(status, StatusCode::OK);
for needle in [
"/v1/graph/capabilities",
"loadCapabilities",
"enableAskTab",
"/chat/completions",
] {
assert!(body.contains(needle), "app.js must reference `{needle}`");
}
}
#[tokio::test]
async fn app_js_is_served_as_javascript() {
let (status, ct, cache, body) = get("/app.js").await;
assert_eq!(status, StatusCode::OK);
assert!(ct.contains("javascript"), "content-type was {ct}");
assert!(
cache.contains("max-age="),
"app.js must be cacheable: {cache}"
);
assert!(!body.is_empty());
assert!(body.contains("/v1/graph/workspaces"));
}
#[tokio::test]
async fn sticker_is_served_as_cacheable_nonempty_svg() {
let (status, ct, cache, body) = get("/sticker.svg").await;
assert_eq!(status, StatusCode::OK);
assert!(
ct.contains("image/svg+xml"),
"sticker must be served as SVG: {ct}"
);
assert!(
cache.contains("max-age=") && cache.contains("public"),
"sticker must send a caching Cache-Control: {cache}"
);
assert!(!body.is_empty(), "the sticker asset must be non-empty");
assert!(body.contains("<svg"), "it is an SVG document");
}
#[tokio::test]
async fn shell_scaffolds_the_workspace_selector_landing() {
let (status, _ct, _cache, body) = get("/").await;
assert_eq!(status, StatusCode::OK);
for needle in ["id=\"view-select\"", "id=\"select-grid\"", "/sticker.svg"] {
assert!(body.contains(needle), "shell must contain `{needle}`");
}
}
#[tokio::test]
async fn app_js_routes_the_selector_by_workspace_type() {
let (status, _ct, _cache, body) = get("/app.js").await;
assert_eq!(status, StatusCode::OK);
for needle in ["renderSelector", "goByType", "showSelectView"] {
assert!(body.contains(needle), "app.js must reference `{needle}`");
}
}
#[tokio::test]
async fn vendored_cytoscape_is_cacheable_nonempty_javascript() {
let (status, ct, cache, body) = get("/vendor/cytoscape.min.js").await;
assert_eq!(status, StatusCode::OK);
assert!(ct.contains("javascript"), "content-type was {ct}");
assert!(
cache.contains("max-age=") && cache.contains("public"),
"vendored bundle must send a caching Cache-Control: {cache}"
);
assert!(
body.len() > 100_000,
"the vendored UMD bundle is substantial"
);
assert!(body.contains("cytoscape"), "it is the cytoscape library");
}
}