use assert_cmd::Command;
use predicates::prelude::*;
use std::io::Write;
fn cast() -> Command {
Command::cargo_bin("cast").unwrap()
}
fn write_fleet_config(dir: &std::path::Path) -> std::path::PathBuf {
let path = dir.join("fleet.json");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(
br#"{
"nodes": {
"host1": {
"name": "host1",
"targetHost": "127.0.0.1",
"targetUser": "root",
"targetPort": null,
"system": "x86_64-linux",
"profileType": "nixos",
"buildOnTarget": false,
"tags": ["web", "prod"]
},
"host2": {
"name": "host2",
"targetHost": "127.0.0.2",
"targetUser": "root",
"targetPort": null,
"system": "x86_64-linux",
"profileType": "nixos",
"buildOnTarget": false,
"tags": ["db"]
}
},
"builders": {},
"flakeUri": "."
}"#,
)
.unwrap();
path
}
#[test]
fn test_missing_config_file() {
cast()
.args(["--config", "/nonexistent/fleet.json", "eval"])
.assert()
.failure()
.stderr(predicate::str::contains("failed to read config file"))
.stderr(predicate::str::contains("No such file or directory"));
}
#[test]
fn test_invalid_json_config() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bad.json");
std::fs::write(&path, "not json").unwrap();
cast()
.args(["--config", path.to_str().unwrap(), "eval"])
.assert()
.failure()
.stderr(predicate::str::contains("failed to parse config file"));
}
#[test]
fn test_eval_all_nodes() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args(["--config", config.to_str().unwrap(), "eval"])
.assert()
.success()
.stdout(predicate::str::contains("host1"))
.stdout(predicate::str::contains("host2"))
.stdout(predicate::str::contains("Evaluating 2 host(s)"));
}
#[test]
fn test_eval_specific_node() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args([
"--config",
config.to_str().unwrap(),
"eval",
"--on",
"host1",
])
.assert()
.success()
.stdout(predicate::str::contains("host1"))
.stdout(predicate::str::contains("Evaluating 1 host(s)"));
}
#[test]
fn test_eval_by_tag() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args(["--config", config.to_str().unwrap(), "eval", "-g", "web"])
.assert()
.success()
.stdout(predicate::str::contains("host1"))
.stdout(predicate::str::contains("Evaluating 1 host(s)"));
}
#[test]
fn test_eval_group_from_xdg_groups_d() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
let groups_d = dir.path().join("xdg/clustershell/groups.d");
std::fs::create_dir_all(&groups_d).unwrap();
std::fs::write(
groups_d.join("lab.cfg"),
"# lab\nfront: host2.local\nall: host[1-2]\n",
)
.unwrap();
cast()
.env("XDG_CONFIG_HOME", dir.path().join("xdg"))
.env("HOME", dir.path())
.args([
"--config",
config.to_str().unwrap(),
"eval",
"--on",
"@front",
])
.assert()
.success()
.stdout(predicate::str::contains("Evaluating 1 host(s)"))
.stdout(predicate::str::contains("host2 →"));
cast()
.env("XDG_CONFIG_HOME", dir.path().join("xdg"))
.env("HOME", dir.path())
.args(["--config", config.to_str().unwrap(), "eval", "-w", "@nope"])
.assert()
.failure()
.stderr(predicate::str::contains("group '@nope' not found"))
.stderr(predicate::str::contains("clustershell/groups.d/*"));
}
#[test]
fn test_implicit_fleet_json_in_cwd() {
let dir = tempfile::tempdir().unwrap();
write_fleet_config(dir.path());
cast()
.current_dir(dir.path())
.args(["eval"])
.assert()
.success()
.stdout(predicate::str::contains("Evaluating 2 host(s)"));
}
#[test]
fn test_unknown_node() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args([
"--config",
config.to_str().unwrap(),
"eval",
"--on",
"doesnotexist",
])
.assert()
.failure()
.stderr(predicate::str::contains("unknown node 'doesnotexist'"))
.stderr(predicate::str::contains("host1, host2"));
}
#[test]
fn test_malformed_bracket_pattern() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args(["--config", config.to_str().unwrap(), "eval", "--on", "bad["])
.assert()
.failure()
.stderr(predicate::str::contains("invalid node pattern"));
}
#[test]
fn test_invalid_deploy_action() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args([
"--config",
config.to_str().unwrap(),
"deploy",
"--on",
"host1",
"reboot",
])
.assert()
.failure()
.stderr(predicate::str::contains("invalid action 'reboot'"))
.stderr(predicate::str::contains(
"switch, boot, test, dry-activate, build",
));
}
#[test]
fn test_health_no_builders() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args(["--config", config.to_str().unwrap(), "health"])
.assert()
.success()
.stdout(predicate::str::contains("No builders configured"));
}
#[test]
fn test_eval_shows_target_details() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args([
"--config",
config.to_str().unwrap(),
"eval",
"--on",
"host1",
])
.assert()
.success()
.stdout(predicate::str::contains("127.0.0.1"))
.stdout(predicate::str::contains("x86_64-linux"))
.stdout(predicate::str::contains("web, prod"));
}
#[test]
fn test_nix_args_accept_space_separated_flag_values() {
let dir = tempfile::tempdir().unwrap();
let config = write_fleet_config(dir.path());
cast()
.args([
"--config",
config.to_str().unwrap(),
"--nixos-nix-args",
"--override-input secrets path:./stub",
"--darwin-nix-args",
"--option builders ''",
"--nix-args",
"--impure",
"eval",
])
.assert()
.success()
.stdout(predicate::str::contains("host1"));
}