osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
#[cfg(unix)]
use crate::temp_support::make_temp_dir;
use assert_cmd::Command;
use predicates::prelude::*;

#[cfg(unix)]
#[test]
fn positional_profile_routes_to_plugin_command_contract() {
    let home = make_temp_dir("osp-cli-profile-home");
    write_config(
        &home,
        r#"
[default]
profile.default = "uio"

[profile.uio]
ui.format = "table"

[profile.tsd]
ui.format = "json"
"#,
    );

    let plugin_dir = make_temp_dir("osp-cli-profile-plugin");
    write_hello_plugin(&plugin_dir);

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("osp"));
    cmd.envs(crate::test_env::isolated_env(&home))
        .env("PATH", "/usr/bin:/bin")
        .env("OSP_PLUGIN_PATH", &plugin_dir)
        .args(["tsd", "hello"]);
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("hello-from-plugin"));
}

#[cfg(unix)]
#[test]
fn positional_profile_routes_to_builtin_plugins_command_contract() {
    let home = make_temp_dir("osp-cli-profile-home-builtins");
    write_config(
        &home,
        r#"
[default]
profile.default = "uio"

[profile.uio]
ui.format = "table"

[profile.tsd]
ui.format = "json"
"#,
    );

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("osp"));
    cmd.envs(crate::test_env::isolated_env(&home))
        .env("PATH", "/usr/bin:/bin")
        .args(["tsd", "plugins", "list"]);
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("No plugins discovered."));
}

#[cfg(unix)]
#[test]
fn unknown_first_token_is_treated_as_command_contract() {
    let home = make_temp_dir("osp-cli-profile-home-unknown");
    write_config(
        &home,
        r#"
[default]
profile.default = "uio"

[profile.uio]
ui.format = "table"

[profile.tsd]
ui.format = "json"
"#,
    );

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("osp"));
    cmd.envs(crate::test_env::isolated_env(&home))
        .env("PATH", "/usr/bin:/bin")
        .args(["prod", "plugins", "list"]);
    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("no plugin provides command: prod"));
}

#[cfg(unix)]
#[test]
fn explicit_profile_overrides_positional_profile_contract() {
    let home = make_temp_dir("osp-cli-profile-home-explicit");
    write_config(
        &home,
        r#"
[default]
profile.default = "uio"

[profile.uio]
ui.format = "table"

[profile.tsd]
ui.format = "json"
"#,
    );

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("osp"));
    cmd.envs(crate::test_env::isolated_env(&home))
        .env("PATH", "/usr/bin:/bin")
        .args(["--profile", "uio", "tsd", "plugins", "list"]);
    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("no plugin provides command: tsd"));
}

#[cfg(unix)]
fn write_config(home: &std::path::Path, config: &str) {
    let config_dir = home.join(".config").join("osp");
    std::fs::create_dir_all(&config_dir).expect("config dir should be created");
    std::fs::write(config_dir.join("config.toml"), config).expect("config should be written");
}

#[cfg(unix)]
fn write_hello_plugin(dir: &std::path::Path) -> std::path::PathBuf {
    use std::os::unix::fs::PermissionsExt;

    let plugin_path = dir.join("osp-hello");
    let plugin_script = r#"#!/bin/sh
PATH=/usr/bin:/bin:$PATH
if [ "$1" = "--describe" ]; then
  cat <<'JSON'
{"protocol_version":1,"plugin_id":"hello","plugin_version":"0.1.0","min_osp_version":"0.1.0","commands":[{"name":"hello","about":"hello plugin","args":[],"flags":{},"subcommands":[]}]}
JSON
  exit 0
fi

cat <<'JSON'
{"protocol_version":1,"ok":true,"data":{"message":"hello-from-plugin"},"error":null,"meta":{"format_hint":"table","columns":["message"]}}
JSON
"#;

    std::fs::write(&plugin_path, plugin_script).expect("plugin script should be written");
    let mut perms = std::fs::metadata(&plugin_path)
        .expect("metadata should be readable")
        .permissions();
    perms.set_mode(0o755);
    std::fs::set_permissions(&plugin_path, perms).expect("script should be executable");
    plugin_path
}