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

nordisk/korp-collectors

tests/human_docs.rs

//! **Every collector carries a human card, and the card is not a formality.**
//!
//! The owner, 2026-09-04: *"collectors get HUMAN DOCS … fill it for every existing
//! collector from what the source publishes."*
//!
//! Two tests, because there are two ways to fail this. The first is leaving a collector
//! undocumented; the second — much easier, and the one that actually happens — is
//! filling the fields with something that passes a non-empty check and tells an
//! operator nothing. A card saying authority `"the API"` and terms `"see website"` is
//! worse than no card, because it looks answered.

use korp_collectors::human_doc::{Contact, RateLimit, Registration};
use korp_collectors::{registry, AuthKind};

/// **RED: name every collector with no card.** Not a count — the list, so the fix is
/// the list.
#[test]
fn every_registered_collector_carries_a_human_card() {
    let all = registry();
    assert!(
        all.len() >= 40,
        "the registry came back with {} collectors — this sweep is reading nothing",
        all.len(),
    );

    let undocumented: Vec<String> = all
        .iter()
        .filter(|c| c.human_doc().is_none())
        .map(|c| c.meta().name.clone())
        .collect();

    // It was a RATCHET at 13 for one commit, while the energy + environment family was
    // still being written, for the reason `tests/no_python.rs` records about the shell
    // census: a refusal committed before the work is done is red for every developer and
    // every CI run, and a permanently-red guard is one people learn to ignore.
    //
    // It reached ZERO on 2026-09-04 and it does not go up. A new collector arrives
    // undocumented and this test names it on the first run.
    const UNDOCUMENTED_CEILING: usize = 0;
    assert!(
        undocumented.len() <= UNDOCUMENTED_CEILING,
        "{} of {} collectors have no human card — an operator opening their pane gets a \
         blank where 'who runs this and how do I get a key' should be:\n  {}",
        undocumented.len(),
        all.len(),
        undocumented.join("\n  "),
    );
    // The other direction: a ratchet nobody lowers is a ratchet that stopped working.
    assert_eq!(
        undocumented.len(),
        UNDOCUMENTED_CEILING,
        "{} collectors are undocumented and the ceiling still says {UNDOCUMENTED_CEILING} \
         — lower it in this file; the endpoint is ZERO",
        undocumented.len(),
    );
}

/// **RED: a card that is filled in but says nothing.**
///
/// Every check here fires on a real way of writing a useless card, and each is named
/// separately so the failure says WHICH kind of empty it is.
#[test]
fn a_human_card_answers_the_questions_it_claims_to() {
    /// Words that fill a field without answering it. A card whose authority is
    /// "unknown" has not been written; a card whose authority is "Trafikverket" has.
    const WEASEL: [&str; 8] = ["n/a", "na", "none", "tbd", "todo", "unknown", "see website", "-"];

    let mut bad: Vec<String> = Vec::new();
    let mut checked = 0usize;

    for c in registry() {
        let name = c.meta().name.clone();
        let Some(d) = c.human_doc() else { continue };
        checked += 1;
        let mut say = |what: &str| bad.push(format!("{name}: {what}"));

        for (field, v) in [
            ("authority", &d.authority),
            ("owner", &d.owner),
            ("terms_url", &d.terms_url),
            ("docs_url", &d.docs_url),
        ] {
            if v.trim().is_empty() {
                say(&format!("{field} is empty"));
            } else if WEASEL.contains(&v.trim().to_ascii_lowercase().as_str()) {
                say(&format!("{field} is {v:?}, which fills the field without answering it"));
            }
        }
        // A URL field has to BE a URL. "the Trafikverket website" is a sentence, not a
        // place an operator can click.
        for (field, v) in [("terms_url", &d.terms_url), ("docs_url", &d.docs_url)] {
            if !v.trim().is_empty() && !v.starts_with("http") {
                say(&format!("{field} is {v:?}, which is not a URL"));
            }
        }

        // ── The cross-checks: the card must agree with what the CODE already says ──
        //
        // These are the ones worth having. A card is written by hand and the code is
        // executed, so the two drift silently — and the drift always favours the card
        // looking better than the collector behaves.
        let needs_secret = !matches!(c.meta().auth, AuthKind::None);
        if needs_secret && matches!(d.registration, Registration::NotNeeded) {
            say("the card says no registration is needed, but the collector declares an \
                 auth kind — one of the two is lying to the operator");
        }
        if !needs_secret && !d.registration.is_self_serve() {
            say("the card sends the operator off to register, but the collector needs no \
                 auth at all");
        }
        // A source you cannot reach and cannot register with leaves an operator with
        // nowhere to go, so at least one door has to be open.
        if matches!(d.contact, Contact::Unknown)
            && matches!(d.registration, Registration::NotNeeded)
            && matches!(d.rate_limit, RateLimit::Unknown)
        {
            say("every soft field is Unknown — this card was created, not written");
        }
    }

    assert!(
        checked >= 40,
        "only {checked} cards were examined — the sweep is not reaching the registry",
    );
    assert!(bad.is_empty(), "{} weak human card(s):\n  {}", bad.len(), bad.join("\n  "));
}