browser-tests/tests/open_bundle.rs
//! ★ A28 M13: **the open bundle speaks no money**, checked on the BUILT bundles, beside
//! holger-ui's source guard (`tests/open_bundle_speaks_no_money.rs`, which judges the literals).
//!
//! * the files: the open `dist/` carries none of the paid half's pages, the metered one all;
//! * the module: the open wasm holds none of the paid console's words, the metered wasm every
//! one (so the check sees what it looks for);
//! * the browser: the open console, served as its files, draws no "My plan" tab, and `/plan`
//! is no page of it.
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use holger_ui_browser_tests::browser::Browser;
use holger_ui_browser_tests::bundle::{dist, dist_open};
/// The paid half's pages: the metered page copies them, the open page does not.
const PAID_FILES: [&str; 3] = ["signup.html", "welcome.html", "paid.html"];
/// Words only the paid console renders, each a literal of a `metered` item: `pages::plan`,
/// `crate::purchase`, the topbar's buy link, "More room" and the admin's payments card.
const PAID_WORDS: [&str; 6] = ["My plan", "Buy a plan", "Past purchases", "Resume payment", "Buy more room", "Stripe webhook"];
fn holds(hay: &[u8], needle: &str) -> bool {
hay.windows(needle.len()).any(|w| w == needle.as_bytes())
}
/// The bundle's wasm module, by trunk's suffix or wasm-bindgen's name.
fn module(dir: &Path) -> Vec<u8> {
let path = std::fs::read_dir(dir).unwrap().flatten().map(|e| e.path()).find(|p| p.to_string_lossy().ends_with("_bg.wasm")).unwrap_or_else(|| panic!("no wasm in {}", dir.display()));
std::fs::read(path).unwrap()
}
#[test]
fn the_open_bundle_carries_no_paid_page_and_no_paid_word() {
let (open, paid) = (dist_open(), dist());
for f in PAID_FILES {
assert!(!open.join(f).exists(), "the OPEN bundle carries {f}");
assert!(paid.join(f).is_file(), "the metered bundle lacks {f}");
}
assert!(open.join("login.html").is_file() && open.join("index.html").is_file(), "the open bundle is a console");
let (o, m) = (module(open), module(paid));
for w in PAID_WORDS {
assert!(!holds(&o, w), "the OPEN console's module holds {w:?}");
assert!(holds(&m, w), "the metered module lacks {w:?}, so this check would see nothing");
}
}
/// Serves a bundle's files as a static host does: the file at its path, else `index.html`.
async fn serve(dir: PathBuf) -> SocketAddr {
use axum::http::{header, StatusCode, Uri};
use axum::response::IntoResponse;
let app = axum::Router::new().fallback(move |uri: Uri| {
let dir = dir.clone();
async move {
let rel = uri.path().trim_start_matches('/');
let file = Some(dir.join(rel)).filter(|p| !rel.is_empty() && p.is_file()).unwrap_or_else(|| dir.join("index.html"));
let kind = match file.extension().and_then(|e| e.to_str()) {
Some("js") => "text/javascript",
Some("wasm") => "application/wasm",
Some("css") => "text/css",
Some("webp") => "image/webp",
_ => "text/html; charset=utf-8",
};
match std::fs::read(&file) {
Ok(bytes) => ([(header::CONTENT_TYPE, kind)], bytes).into_response(),
Err(_) => StatusCode::NOT_FOUND.into_response(),
}
}
});
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
addr
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_open_console_draws_no_plan_and_has_no_plan_page() {
let addr = serve(dist_open().to_path_buf()).await;
let browser = Browser::launch().await.expect("chromium starts");
let tab = browser.tab().await.unwrap();
tab.open(&format!("http://{addr}/mine")).await.unwrap();
// HOL-UI-FIX: the person's own pages are the Account entry's tabs (once a menu entry "Mine").
tab.until("the Account tabs", "document.querySelectorAll('nav[aria-label=\"Account\"] a').length > 0").await;
let tabs = tab.js("[...document.querySelectorAll('nav[aria-label=\"Account\"] a')].map(a => a.textContent.trim())").await.unwrap();
assert_eq!(tabs, serde_json::json!(["Account", "My repositories", "My tokens"]), "the open console's Account tabs");
tab.open(&format!("http://{addr}/plan")).await.unwrap();
tab.until("/plan is no page", "document.body.innerText.includes('no such page')").await;
let text = tab.text().await;
for w in PAID_WORDS {
assert!(!text.contains(w), "the open console draws {w:?}: {text}");
}
}