Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

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"));
}