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

nordisk/holger

plugin/guest-allocation/tests/no_money_word.rs

//! The open build never mentions money. This is the crate-local half of that
//! law: an allow-list of resource and provenance words, asserted over this
//! crate's own source, so a word that has no business here cannot be added
//! without the test naming it.
//!
//! It is stronger than grepping for known-bad words: a new money word nobody
//! thought to ban is still caught, because it is simply not on the list.

use std::collections::BTreeSet;

/// Words that may appear in a metering crate in the open build: resources,
/// provenance and measurement.
const ALLOWED_DOMAIN_WORDS: &[&str] = &[
    "allocation",
    "artifact",
    "artifacts",
    "archive",
    "audit",
    "bytes",
    "capacity",
    "capability",
    "clock",
    "counted",
    "digest",
    "enforcement",
    "files",
    "guest",
    "host",
    "manifest",
    "measured",
    "meter",
    "meters",
    "principal",
    "provisioned",
    "reading",
    "refused",
    "repository",
    "scope",
    "selftest",
    "servable",
    "storage",
    "store",
    "tenant",
    "total",
    "usage",
    "used",
    "verdict",
];

/// Words that are the business, and must not be in the open build at all. The
/// test does not rely on this list — the allow-list above does the work — but a
/// hit here names the offence instead of just failing.
const NAMED_OFFENCES: &[&str] = &[
    "plan",
    "price",
    "pricing",
    "invoice",
    "stripe",
    "upcloud",
    "paid",
    "quota",
    "subscription",
    "billing",
    "charge",
    "payment",
    "licence",
    "license",
    "entitlement",
    "cap",
    "expiry",
    "expires",
    "money",
    "currency",
    "trial",
    "tier",
];

fn words(src: &str) -> BTreeSet<String> {
    src.split(|c: char| !c.is_ascii_alphabetic())
        .filter(|w| w.len() > 2)
        .map(|w| w.to_ascii_lowercase())
        .collect()
}

#[test]
fn this_crate_speaks_no_money() {
    let mut sources = vec![
        ("src/lib.rs", include_str!("../src/lib.rs")),
        ("tests/allow_is_total.rs", include_str!("allow_is_total.rs")),
    ];
    // This file names the offences on purpose, so it is not its own subject.
    sources.retain(|(name, _)| !name.contains("no_money_word"));

    for (name, src) in sources {
        let found = words(src);
        for bad in NAMED_OFFENCES {
            assert!(
                !found.contains(*bad),
                "{name} contains the word `{bad}`; the open build never mentions money"
            );
        }
    }
}

#[test]
fn the_guard_would_catch_a_leak() {
    // The negative control: the test above is only worth something if it fires.
    let found = words("fn charge_the_customer() { let invoice = 1; }");
    assert!(NAMED_OFFENCES.iter().any(|bad| found.contains(*bad)));
}

#[test]
fn the_allow_list_is_the_vocabulary_this_crate_actually_uses() {
    // Every allowed word is a resource or provenance word, and none of them is
    // an offence. This keeps the two lists from drifting into each other.
    for w in ALLOWED_DOMAIN_WORDS {
        assert!(!NAMED_OFFENCES.contains(w), "`{w}` is on both lists");
    }
}