browser-tests/tests/estate_board.rs
//! ★ **The board shows the RON's graph** (HBOARD-1; the owner's board of 2026-10-23; HOL-SVG-RON:
//! holger's own flow drawing of its RON):
//! a holger declaring two upstream sources, an upload door and one downstream connector, its
//! console in a real browser. On the Flow page (the console's only picture, HOL-UI-FIX) the
//! board draws each source and the downstream, the counts and the lag, and the line under it
//! counts the consumers: all of it holger's own answer (`GET /-/estate.json` =
//! `AdminService.Topology`), none of it made up by the console. The Admin page links to it; the
//! Security page shows security instead (owner 2026-10-27, `tests/security_page.rs`).
use holger_ui_browser_tests::browser::{Browser, Tab, NEVER};
use holger_ui_browser_tests::rig::{Rig, CONSOLE, GRAPH_DOWNSTREAM, GRAPH_ROWS};
async fn register_owner(tab: &Tab) {
tab.until("the bootstrap door", "!document.getElementById('reg').hidden").await;
tab.fill("#token", CONSOLE).await;
tab.js("document.getElementById('role').value = 'admin'").await.unwrap();
tab.until("the register button", "!!document.getElementById('register')").await;
tab.js("document.getElementById('reg').open = true").await.unwrap();
tab.fill("#user", "owner").await;
tab.click("#register").await;
tab.until("registered", holger_ui_browser_tests::browser::SIGNED_IN_OR_REGISTERED).await;
if !tab.signed_in().await {
// A real press: the sign-in starts its passkey only inside the person's gesture (RA2-6).
tab.press("#login").await;
tab.until("holger's session in this tab", holger_ui_browser_tests::browser::SIGNED_IN).await;
}
}
/// The board the page drew: its SVG, decoded from the image.
async fn drawn_board(tab: &Tab, page: &str) -> String {
tab.until(&format!("the estate board on {page}"), "!!document.querySelector('[data-board=estate] img.board-img')").await;
let drawn = tab
.js("(() => { const s = document.querySelector('[data-board=estate] img.board-img').src; return decodeURIComponent(s.slice(s.indexOf(',') + 1)); })()")
.await
.unwrap();
drawn.as_str().unwrap().to_owned()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_board_shows_every_source_the_downstream_their_counts_and_lag() {
let rig = Rig::start_with_graph().await;
// One invalidation the downstream has not acknowledged: its lag is one generation.
rig.invalidate("pypi/demo_pkg@1.0.0", "the board's lag");
let browser = Browser::launch().await.expect("chromium starts");
let owner = browser.tab().await.unwrap();
owner.open(&rig.at("/login")).await.unwrap();
register_owner(&owner).await;
// holger's own answer: every declared row is there, with its numbers.
let (status, served) = rig.console_get("/-/estate.json");
assert_eq!(status, 200, "holger's board door: {served}");
let v: serde_json::Value = serde_json::from_str(&served).unwrap();
let rows = v["boxes"].as_array().expect("the rows");
let row = |n: &str| rows.iter().find(|b| b["name"] == n).unwrap_or_else(|| panic!("holger's board has no row {n}: {served}"));
for n in GRAPH_ROWS {
row(n);
}
assert_eq!(row(GRAPH_DOWNSTREAM)["role"], "downstream");
assert_eq!(row(GRAPH_DOWNSTREAM)["lag_entries"], 1, "one generation not acknowledged: {served}");
assert_eq!(row("cloud")["upstream_kind"], "mirror");
assert_eq!(row("team")["upstream_kind"], "upload");
let summary = v["summary"].as_str().unwrap().to_owned();
// BOARD-LINE (holger 0fea2068): the uploads are no upstream; the line names the two by name.
assert!(summary.contains("2 upstreams: cloud, crates-io, and uploads") && summary.contains("1 downstream") && summary.contains("consumers"), "the server's line: {summary}");
// The console shows holger's own flow drawing of its RON (HOL-SVG-RON), as sent: every
// node of holger's model is in the image, the downstream as a downstream.
let (status, model) = rig.console_get("/-/flow.json");
assert_eq!(status, 200, "holger's flow door: {model}");
let model: serde_json::Value = serde_json::from_str(&model).unwrap();
{
let page = "/flow";
owner.open(&rig.at(page)).await.unwrap();
let drawn = drawn_board(&owner, page).await;
for n in model["nodes"].as_array().expect("the model's nodes") {
let id = n["id"].as_str().unwrap();
assert!(drawn.contains(&format!(r#"data-node="{id}""#)), "{page}: the console shows holger's node {id}");
}
for name in GRAPH_ROWS {
assert!(drawn.contains(&format!(">{name}</text>")), "{page}: the drawing names {name} as the RON writes it");
}
let down = format!(r#"<g data-node="downstream:{GRAPH_DOWNSTREAM}" data-kind="downstream">"#);
assert!(drawn.contains(&down), "{page}: the downstream is a downstream, never an upstream");
let line = owner.until("the board's line", "(document.querySelector('[data-board=estate] .board-summary') || {}).textContent || ''").await;
assert!(line.as_str().unwrap().contains(&summary), "{page}: the line under the board is the server's: {line}");
owner.assert_no_link_to(NEVER).await.unwrap();
}
// HCON-2/3/4: where the login lands (the Overview), no session internals; the uptime in
// words; the session's details are the Account page's, in plain words.
owner.open(&rig.at("/overview")).await.unwrap();
owner.until("the server card's uptime", "/up \\d/.test(document.body.innerText)").await;
let text = owner.text().await;
for internal in ["passkey:", "Global role", "Read gate", "Credential"] {
assert!(!text.contains(internal), "the Overview shows a session internal ({internal}): {text}");
}
let raw = owner.js("/up \\d+s\\b/.test(document.body.innerText)").await.unwrap();
assert_eq!(raw, serde_json::Value::Bool(false), "an uptime in raw seconds: {text}");
owner.open(&rig.at("/account")).await.unwrap();
owner.until("the session in plain words", "document.body.innerText.includes('You are signed in with a passkey.')").await;
let text = owner.text().await;
assert!(text.contains("Your role here: admin."), "{text}");
// HCON-5 (owner 2026-10-27): the estate's RON says `access: (reads: Gated, front: Open)`,
// and the console says what is open in the server's own words.
assert!(text.contains("Reading packages needs a sign-in. No repository is public. The front page and the sign-up are open."), "what is open, in the server's words: {text}");
}