xtask/tests/product_names.rs
//! **The monetize core names no product** (v2.0 P3, plan §3: "product names out of the core";
//! A28 M6). In the RUNTIME code of `crates/*/src` — tests, `#[cfg(test)]` items and comments
//! left out — no line names Gunnar or Holger, except the few that are DATA, each listed below
//! with why. A product is what its signed guest says of itself (its facts and its plan); the
//! core branches on no product's name. The words are assembled here, so this guard does not
//! name them either.
use std::path::{Path, PathBuf};
/// Test tools, not the core: the UpCloud mock and the test kits and doubles (A28 M6: "exclude
/// the mock and fast tools"), each its own crate or a `testing` module only tests turn on.
const TOOLS: &[&str] = &[
"crates/mock-upcloud/",
"crates/monetize-site-buy/src/testkit.rs",
"crates/monetize/src/testing.rs",
"crates/monetize-server/src/testing.rs",
"crates/monetize-server/src/testing/",
"crates/monetize-embed/src/testing/",
"crates/monetize-shop-pairing/src/test_fixtures.rs",
];
/// The lines that name a product as DATA, by file and how the line starts, and why each stays.
const DATA: &[(&str, &str, &str)] = &[
// The fast test's owner's walk (T-OWNERFLOW) declares the sign-up plans it buys with each
// product's meter, as the prod box's solo.toml feeds them: test data of the fast driver.
("crates/monetize-fast/src/walk.rs", "let meter = ", "the walk's test plans, each product's meter as solo.toml feeds it"),
// The published nordisk-estate-svg 0.1.0 API: the old name of `from_word`, for its callers
// until their next release.
("crates/estate-svg/src/lib.rs", "pub fn from_", "the published 0.1.0 name"),
// The Build Thing's sibling checkouts and a kernel row's words: build data, not the shop.
("crates/monetize-build-thing/src/lib.rs", "\"", "a sibling checkout's name"),
("crates/monetize-build-thing/src/lib.rs", "(\"CONFIG_", "a kernel row's words"),
// The names no company may be sold as (OM-12): a list of reserved words.
("crates/monetize-cloud/src/lay.rs", "pub const RESERVED", "the names never sold as a company"),
// The signing scheme of the product control plane's door, which T13 deletes with the
// product guests' grants (A30 §3: GrpcDoor and identity are dead for a guest with none).
("crates/monetize-product-wasm/src/identity.rs", "pub const ", "the wire words of the door T13 deletes"),
// `AppState::in_memory`, the test rig's state: its one product's price list and host.
("crates/monetize-server/src/state.rs", "prices: ", "the in-memory rig's fixture"),
("crates/monetize-server/src/state.rs", "hosts: ", "the in-memory rig's fixture"),
];
fn words() -> [String; 2] {
[["gun", "nar"].concat(), ["hol", "ger"].concat()]
}
/// One pass over a whole file, line by line: each line's text without its comments (`code`),
/// and the same without its string literals too (`bare`, for counting braces) — `"…"` with its
/// escapes, raw `r"…"` and `r#"…"#`, and a string or a `/* … */` that runs over several lines.
fn split(text: &str) -> Vec<(String, String)> {
#[derive(Clone, Copy, PartialEq)]
enum In {
Code,
Str,
Raw(usize),
Block,
}
let c: Vec<char> = text.chars().collect();
let (mut state, mut i) = (In::Code, 0);
let (mut code, mut bare, mut out) = (String::new(), String::new(), Vec::new());
while i < c.len() {
let ch = c[i];
if ch == '\n' {
out.push((std::mem::take(&mut code), std::mem::take(&mut bare)));
i += 1;
continue;
}
match state {
In::Code => {
if ch == '/' && c.get(i + 1) == Some(&'/') {
while i < c.len() && c[i] != '\n' {
i += 1;
}
continue;
}
if ch == '/' && c.get(i + 1) == Some(&'*') {
state = In::Block;
i += 2;
continue;
}
if ch == 'r' && (i == 0 || !(c[i - 1].is_alphanumeric() || c[i - 1] == '_')) {
let h = c[i + 1..].iter().take_while(|&&x| x == '#').count();
if c.get(i + 1 + h) == Some(&'"') {
code.extend(&c[i..i + 2 + h]);
state = In::Raw(h);
i += 2 + h;
continue;
}
}
if ch == '"' {
state = In::Str;
} else if ch == '\'' && c.get(i + 2) == Some(&'\'') {
// A char literal (`'{'`) is no brace either.
code.extend(&c[i..i + 3]);
i += 3;
continue;
} else {
bare.push(ch);
}
code.push(ch);
i += 1;
}
In::Str => {
code.push(ch);
if ch == '\\' {
if let Some(&n) = c.get(i + 1) {
if n != '\n' {
code.push(n);
}
}
i += 2;
continue;
}
if ch == '"' {
state = In::Code;
}
i += 1;
}
In::Raw(h) => {
code.push(ch);
if ch == '"' && c[i + 1..].iter().take(h).filter(|&&x| x == '#').count() == h && c.len() >= i + 1 + h {
code.extend(&c[i + 1..i + 1 + h]);
state = In::Code;
i += 1 + h;
continue;
}
i += 1;
}
In::Block => {
if ch == '*' && c.get(i + 1) == Some(&'/') {
state = In::Code;
i += 2;
continue;
}
i += 1;
}
}
}
out.push((code, bare));
out
}
/// The runtime lines of one file: no `#[cfg(test)]` item, no comment.
fn runtime_lines(text: &str) -> Vec<(usize, String)> {
let lines = split(text);
let mut out = Vec::new();
let mut i = 0;
while i < lines.len() {
if lines[i].1.trim().starts_with("#[cfg(test)]") {
// Skip the item it guards: to its closing brace, or to its `;`.
let (mut j, mut depth, mut open) = (i + 1, 0i64, false);
while j < lines.len() {
let l = &lines[j].1;
depth += l.matches('{').count() as i64 - l.matches('}').count() as i64;
open |= l.contains('{');
if (open && depth <= 0) || (!open && l.trim_end().ends_with(';')) {
break;
}
j += 1;
}
i = j + 1;
continue;
}
if !lines[i].0.trim().is_empty() {
out.push((i + 1, lines[i].0.clone()));
}
i += 1;
}
out
}
fn rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else { return };
for e in entries.flatten() {
let p = e.path();
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or_default().to_string();
if p.is_dir() {
if !["tests", "benches", "examples", "target"].contains(&name.as_str()) && !name.ends_with("_tests") {
rust_files(&p, out);
}
} else if name.ends_with(".rs") && name != "tests.rs" && !name.ends_with("_tests.rs") {
out.push(p);
}
}
}
#[test]
fn the_cores_runtime_code_names_no_product_but_its_data() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask sits in the repo").to_path_buf();
let mut files = Vec::new();
rust_files(&root.join("crates"), &mut files);
files.sort();
assert!(files.len() > 100, "the walk found the crates ({} files)", files.len());
let [a, b] = words();
let mut named = Vec::new();
for f in &files {
let rel = f.strip_prefix(&root).unwrap_or(f).to_string_lossy().replace('\\', "/");
if TOOLS.iter().any(|t| rel.starts_with(t)) {
continue;
}
let text = std::fs::read_to_string(f).unwrap_or_default();
for (n, line) in runtime_lines(&text) {
let low = line.to_lowercase();
if !(low.contains(&a) || low.contains(&b)) {
continue;
}
if DATA.iter().any(|(file, start, _)| rel == *file && line.trim().starts_with(start)) {
continue;
}
named.push(format!("{rel}:{n}: {}", line.trim()));
}
}
assert!(named.is_empty(), "the core's runtime code names a product ({} lines): make it the product's facts, or list it in DATA with why:\n{}", named.len(), named.join("\n"));
}
/// The scan itself: a test item and a comment are not runtime code; a product's name in a
/// string is.
#[test]
fn the_scan_leaves_out_tests_and_comments_only() {
let [a, _] = words();
let text = format!("fn live() {{ let w = \"{a}\"; }}\n// {a} in a comment\nlet u = \"https://x/{a}\"; // {a}\n#[cfg(test)]\nmod tests {{\n fn t() {{ \"{{ {a}\"; let j = r#\"{{\"k\":\"{a}\"\"#; }}\n}}\nfn after() {{ \"{a}\"; }}\n");
let lines: Vec<usize> = runtime_lines(&text).into_iter().filter(|(_, l)| l.contains(&a)).map(|(n, _)| n).collect();
assert_eq!(lines, [1, 3, 8], "a brace inside a string does not end the test item early: {text}");
}
/// ★ **A32-24: the shared browser scripts name no product and branch on no product's path.**
/// `crates/*/assets/*.js` is ONE copy for every product site (monetize-site-buy's buy, welcome
/// and order pages): what differs comes from the site's settings (`@@NAME@@`), never from a
/// product's name or one of its own paths (Holger's `/-/…` doors) written in the script. Line
/// comments are left out, as in the Rust scan; string literals count.
#[test]
fn the_shared_browser_scripts_name_no_product_nor_a_products_path() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask sits in the repo").to_path_buf();
let mut scripts = Vec::new();
for c in std::fs::read_dir(root.join("crates")).expect("crates/").flatten() {
let Ok(rd) = std::fs::read_dir(c.path().join("assets")) else { continue };
scripts.extend(rd.flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "js")));
}
scripts.sort();
assert!(scripts.len() >= 3, "the walk found the shared scripts: {scripts:?}");
let [a, b] = words();
// A product's own doors: Holger's `/-/` paths (`/-/signup`, `/-/buy/account`).
let paths = ["/-/"];
let mut named = Vec::new();
for f in &scripts {
let rel = f.strip_prefix(&root).unwrap_or(f).to_string_lossy().replace('\\', "/");
let text = std::fs::read_to_string(f).unwrap_or_default();
for (n, line) in js_code_lines(&text) {
let low = line.to_lowercase();
if low.contains(&a) || low.contains(&b) || paths.iter().any(|p| line.contains(p)) {
named.push(format!("{rel}:{n}: {}", line.trim()));
}
}
}
assert!(named.is_empty(), "a shared browser script names a product or a product's path ({} lines): make it a site setting:\n{}", named.len(), named.join("\n"));
}
/// The JavaScript lines with their `//` comments left out (a `//` inside a string, as in
/// `'https://'`, stays): `(line number, code)`.
fn js_code_lines(text: &str) -> Vec<(usize, String)> {
let mut out = Vec::new();
let mut in_block = false;
for (i, line) in text.lines().enumerate() {
let mut code = String::new();
let mut quote: Option<char> = None;
let mut chars = line.chars().peekable();
while let Some(c) = chars.next() {
if in_block {
if c == '*' && chars.peek() == Some(&'/') {
chars.next();
in_block = false;
}
continue;
}
match quote {
Some(q) => {
code.push(c);
if c == '\\' {
if let Some(n) = chars.next() {
code.push(n);
}
} else if c == q {
quote = None;
}
}
None if c == '/' && chars.peek() == Some(&'/') => break,
None if c == '/' && chars.peek() == Some(&'*') => {
chars.next();
in_block = true;
}
None => {
if matches!(c, '\'' | '"' | '`') {
quote = Some(c);
}
code.push(c);
}
}
}
if !code.trim().is_empty() {
out.push((i + 1, code));
}
}
out
}
/// The JavaScript scan: a comment is left out, a string (also one holding `//`) is kept.
#[test]
fn the_script_scan_leaves_out_comments_only() {
let [a, _] = words();
let text = format!("// {a}\nconst u = 'https://x/{a}'; // {a}\n/* {a}\n {a} */ const v = 1;\nif (s === \"/-/x\") go();\n");
let lines: Vec<(usize, String)> = js_code_lines(&text);
let named: Vec<usize> = lines.iter().filter(|(_, l)| l.contains(&a)).map(|(n, _)| *n).collect();
assert_eq!(named, [2], "{lines:?}");
assert!(lines.iter().any(|(n, l)| *n == 4 && l.contains("const v")), "{lines:?}");
assert!(lines.iter().any(|(n, l)| *n == 5 && l.contains("/-/x")), "{lines:?}");
}
/// The payment vendors' names, assembled so this guard names none either (A32-15).
fn vendor_words() -> [String; 3] {
[["str", "ipe"].concat(), ["kla", "rna"].concat(), ["sw", "ish"].concat()]
}
/// ★ **A32-15: the payment core is not one vendor's shape.** The files the A32 analysis found
/// shaped by one vendor — the ONE box's site book editor (its vendor prefix, its key checks and
/// secret names), the server's site book, the shop link's default vendor, the pay crate's
/// livemode — name no payment vendor in their runtime code: what differs per vendor is the
/// vendor's own data (its kind's shape, `monetize-admin/assets/vendor-shapes.toml`) or the
/// vendor's own word (its `livemode`). Comments and tests left out.
#[test]
fn the_payment_cores_shaped_files_name_no_vendor() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask sits in the repo").to_path_buf();
let words = vendor_words();
let mut named = Vec::new();
for rel in ["crates/monetize-admin/src/app_config.rs", "crates/monetize-server/src/site_book.rs", "crates/monetize-site-buy/src/link.rs", "crates/monetize-pay/src/lib.rs"] {
let text = std::fs::read_to_string(root.join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"));
for (n, line) in runtime_lines(&text) {
let low = line.to_lowercase();
if words.iter().any(|w| low.contains(w)) {
named.push(format!("{rel}:{n}: {}", line.trim()));
}
}
}
assert!(named.is_empty(), "the payment core names a vendor ({} lines): make it the vendor's data or its own word:\n{}", named.len(), named.join("\n"));
}