unifier-cli 0.3.0

Filesystem postbox for inter-process communication via a Unix tree
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::path::PathBuf;

fn with_home<F: FnOnce(PathBuf)>(f: F) {
    let tmp = tempfile::tempdir().unwrap();
    let home = tmp.path().to_path_buf();
    f(home);
}

#[test]
fn put_and_get_key() {
    with_home(|home| {
        let mut cmd = Command::cargo_bin("unifier").unwrap();
        cmd.args([
            "--home",
            &home.display().to_string(),
            "put",
            "app/theme",
            "dark",
        ])
        .assert()
        .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home.display().to_string(), "get", "app/theme"])
            .assert()
            .success()
            .stdout("dark\n");
    });
}

#[test]
fn send_and_poll_mailbox() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "send", "worker", "hello"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "poll", "worker"])
            .assert()
            .success()
            .stdout(predicate::str::contains("hello"));
    });
}

fn home_args(home: &str) -> Vec<String> {
    vec!["--home".into(), home.into(), "--no-daemon".into()]
}

#[test]
fn cron_post_and_list() {
    with_home(|home| {
        let home_s = home.display().to_string();
        let mut args = home_args(&home_s);
        args.extend(["cron".into(), "0_0_*_*_*".into(), "nightly backup".into()]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&args)
            .assert()
            .success();

        let mut list_args = home_args(&home_s);
        list_args.extend(["list".into(), "cron/0_0_*_*_*".into()]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&list_args)
            .assert()
            .success()
            .stdout(predicate::str::contains("nightly backup"));

        assert!(home.join("cron/0_0_*_*_*").is_dir());
        let entries: Vec<_> = fs::read_dir(home.join("cron/0_0_*_*_*"))
            .unwrap()
            .map(|e| e.unwrap().path())
            .collect();
        assert_eq!(entries.len(), 1);
        assert!(entries[0].extension().unwrap() == "txt");
    });
}

#[test]
fn root_prints_home() {
    with_home(|home| {
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home.display().to_string(), "root"])
            .assert()
            .success()
            .stdout(format!("{}\n", home.display()));
    });
}

#[test]
fn chroot_isolates_keys() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "init", "work"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--chroot", "work", "put", "k", "in-work"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--chroot", "work", "get", "k"])
            .assert()
            .success()
            .stdout("in-work\n");

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "get", "k"])
            .assert()
            .failure();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "init", "personal"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--chroot", "personal", "get", "k"])
            .assert()
            .failure();
    });
}

#[test]
fn chroot_root_shows_effective_path() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "init", "work"])
            .assert()
            .success();

        let expected = format!("{}/chroots/work\n", home.display());
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--chroot", "work", "root"])
            .assert()
            .success()
            .stdout(expected);
    });
}

#[test]
fn chroot_list_names() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "init", "work"])
            .assert()
            .success();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "init", "personal"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "chroot", "list"])
            .assert()
            .success()
            .stdout("personal\nwork\n");
    });
}

#[test]
fn list_rejects_path_escape() {
    with_home(|home| {
        let home_s = home.display().to_string();
        let mut args = home_args(&home_s);
        args.extend(["list".into(), "../keys".into()]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&args)
            .assert()
            .failure()
            .stderr(predicate::str::contains(".."));
    });
}

#[test]
fn namespace_flag_prefixes_keys() {
    with_home(|home| {
        let home_s = home.display().to_string();
        let mut put = home_args(&home_s);
        put.extend([
            "--namespace".into(),
            "job".into(),
            "put".into(),
            "theme".into(),
            "dark".into(),
        ]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&put)
            .assert()
            .success();

        assert!(home.join("keys/job/theme").is_file());
        assert!(!home.join("keys/theme").exists());

        let mut get = home_args(&home_s);
        get.extend([
            "--namespace".into(),
            "job".into(),
            "get".into(),
            "theme".into(),
        ]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&get)
            .assert()
            .success()
            .stdout("dark\n");

        let mut miss = home_args(&home_s);
        miss.extend(["get".into(), "theme".into()]);
        Command::cargo_bin("unifier")
            .unwrap()
            .args(&miss)
            .assert()
            .failure();
    });
}

#[test]
fn namespace_set_applies_until_clear() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args([
                "--home",
                &home_s,
                "--no-daemon",
                "namespace",
                "set",
                "myscript",
            ])
            .assert()
            .success()
            .stdout("myscript\n");

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "put", "app/theme", "dark"])
            .assert()
            .success();

        assert!(home.join("keys/myscript/app/theme").is_file());

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "get", "app/theme"])
            .assert()
            .success()
            .stdout("dark\n");

        Command::cargo_bin("unifier")
            .unwrap()
            .args([
                "--home",
                &home_s,
                "--no-daemon",
                "get",
                "/myscript/app/theme",
            ])
            .assert()
            .success()
            .stdout("dark\n");

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "namespace", "get"])
            .assert()
            .success()
            .stdout("myscript\n");

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "namespace", "clear"])
            .assert()
            .success();

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "get", "app/theme"])
            .assert()
            .failure();

        Command::cargo_bin("unifier")
            .unwrap()
            .args([
                "--home",
                &home_s,
                "--no-daemon",
                "get",
                "/myscript/app/theme",
            ])
            .assert()
            .success()
            .stdout("dark\n");
    });
}

#[test]
fn namespace_set_replaced_and_flag_overrides() {
    with_home(|home| {
        let home_s = home.display().to_string();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "namespace", "set", "one"])
            .assert()
            .success();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "namespace", "set", "two"])
            .assert()
            .success();
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "put", "k", "from-two"])
            .assert()
            .success();
        assert!(home.join("keys/two/k").is_file());

        Command::cargo_bin("unifier")
            .unwrap()
            .args([
                "--home",
                &home_s,
                "--no-daemon",
                "--namespace",
                "other",
                "put",
                "k",
                "from-other",
            ])
            .assert()
            .success();
        assert!(home.join("keys/other/k").is_file());

        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "get", "k"])
            .assert()
            .success()
            .stdout("from-two\n");
    });
}

#[test]
fn descendant_process_inherits_namespace() {
    with_home(|home| {
        let home_s = home.display().to_string();
        let bin = assert_cmd::cargo::cargo_bin("unifier");
        Command::cargo_bin("unifier")
            .unwrap()
            .args(["--home", &home_s, "--no-daemon", "namespace", "set", "job"])
            .assert()
            .success();

        let status = std::process::Command::new("sh")
            .arg("-c")
            .arg(format!(
                "{} --home {} --no-daemon put theme nested",
                shell_single_quote(&bin.display().to_string()),
                shell_single_quote(&home_s)
            ))
            .status()
            .unwrap();
        assert!(status.success());
        assert!(home.join("keys/job/theme").is_file());
    });
}

fn shell_single_quote(s: &str) -> String {
    format!("'{}'", s.replace('\'', "'\"'\"'"))
}