wsb 0.251.6311

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
use super::*;

fn registration(services: &[&str], endpoint: &str) -> Registration {
    Registration {
        plugin: PluginName::parse("sdx").expect("a plugin name"),
        services: services
            .iter()
            .map(|service| PluginService::parse(service).expect("a service name"))
            .collect(),
        endpoint: endpoint.to_string(),
        token: "t".repeat(32),
        pid: std::process::id(),
    }
}

/// TEST20019: plugin and service names are words, and a service says whose
/// it is.
///
/// A service name is the key of a per-peer switch and the thing a person
/// types to turn it on; a name with a dot inside a half, an uppercase letter
/// or a space would be one that two spellings could both claim.
#[test]
fn test20019_plugin_and_service_names_are_words() {
    for good in ["sdx", "a", "lab-2"] {
        PluginName::parse(good).unwrap_or_else(|e| panic!("{good}: {e}"));
    }
    for bad in ["", "Sdx", "2sdx", "sdx.secrets", "sd x", "-sdx", "sdx_"] {
        assert!(PluginName::parse(bad).is_err(), "{bad:?} was taken as a plugin name");
    }

    let service = PluginService::parse("sdx.secrets").expect("a service");
    assert_eq!(service.plugin().as_str(), "sdx");
    for bad in ["sdx", "sdx.", ".secrets", "sdx.secrets.more", "Sdx.secrets", "sdx.Secrets"] {
        assert!(PluginService::parse(bad).is_err(), "{bad:?} was taken as a service");
    }
}

/// TEST20020: a plugin registers only its own services, on this machine,
/// with a token worth checking.
///
/// Each is a way a registration could send a peer's request somewhere it was
/// not meant to go: under another plugin's name, to an address off the
/// machine (the bearer token travelling with it), or behind a token anyone
/// could guess.
#[test]
fn test20020_a_registration_is_its_own_on_this_machine() {
    registration(&["sdx.secrets"], "http://127.0.0.1:4100/plugin").check().expect("loopback v4");
    registration(&["sdx.secrets", "sdx.platform"], "http://[::1]:4100/plugin")
        .check()
        .expect("loopback v6");

    let foreign = registration(&["sdx.secrets", "other.secrets"], "http://127.0.0.1:4100/");
    assert!(foreign.check().unwrap_err().contains("only services under its own name"));

    for endpoint in [
        "http://192.168.1.5:4100/plugin",
        "http://localhost.example.com:4100/",
        "https://127.0.0.1:4100/",
        "127.0.0.1:4100",
        "http://0.0.0.0:4100/",
    ] {
        let refused = registration(&["sdx.secrets"], endpoint).check();
        assert!(refused.is_err(), "{endpoint} was accepted as a plugin endpoint");
    }

    assert!(registration(&[], "http://127.0.0.1:4100/").check().unwrap_err().contains("no services"));

    let mut short = registration(&["sdx.secrets"], "http://127.0.0.1:4100/");
    short.token = "short".to_string();
    assert!(short.check().unwrap_err().contains("at least 32"));
}

/// TEST20021: the enabled list is what the owner wrote, kept whole.
///
/// Enabling and disabling round-trip through the file; enabling twice is one
/// entry; no file is no plugins. A file that cannot be read is an error, not
/// an empty list — an empty list would refuse every plugin somebody enabled,
/// and blame them.
#[test]
fn test20021_the_enabled_list_is_kept_whole() {
    let dir = tempfile::tempdir().expect("a scratch directory");
    let machine = dir.path().join("machine");
    assert!(enabled(&machine).expect("no file reads").is_empty());

    let sdx = PluginName::parse("sdx").unwrap();
    let lab = PluginName::parse("lab").unwrap();
    assert!(enable(&machine, &sdx).unwrap(), "the first enable adds it");
    assert!(!enable(&machine, &sdx).unwrap(), "the second finds it there");
    assert!(enable(&machine, &lab).unwrap());
    assert_eq!(
        enabled(&machine).unwrap().into_iter().map(String::from).collect::<Vec<_>>(),
        vec!["lab", "sdx"]
    );

    assert!(disable(&machine, &lab).unwrap());
    assert!(!disable(&machine, &lab).unwrap(), "disabling what is not enabled says so");
    assert_eq!(enabled(&machine).unwrap().len(), 1);

    std::fs::write(machine.join(ENABLED_FILE), "{ not json").unwrap();
    assert!(enabled(&machine).is_err(), "an unreadable list read as an empty one");
}