use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn mecha10() -> Command {
Command::cargo_bin("mecha10").expect("mecha10 binary should be built by `cargo test`")
}
fn write_minimal_project(dir: &std::path::Path, name: &str) {
fs::write(
dir.join("mecha10.json"),
format!(
r#"{{
"name": "{name}",
"version": "0.1.0",
"robot_id": "{name}-001",
"nodes": {{}}
}}"#
),
)
.expect("failed to write mecha10.json fixture");
}
#[test]
fn top_level_help_lists_real_subcommands() {
mecha10()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("Mecha10 CLI"))
.stdout(predicate::str::contains("init"))
.stdout(predicate::str::contains("build"))
.stdout(predicate::str::contains("upload"));
}
#[test]
fn version_flag_prints_a_version_and_exits_zero() {
mecha10()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("mecha10"));
}
#[test]
fn unknown_subcommand_is_a_clap_usage_error() {
mecha10()
.arg("totally-not-a-real-command")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("unrecognized subcommand"));
}
#[test]
fn build_without_a_target_subcommand_is_a_clap_usage_error() {
mecha10().arg("build").assert().failure().code(2);
}
#[test]
fn init_help_documents_non_interactive_flags_without_touching_the_network() {
mecha10()
.arg("init")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("--non-interactive"))
.stdout(predicate::str::contains("--template"));
}
#[test]
fn validate_then_target_then_config_show_chain_against_a_real_project() {
let project = TempDir::new().expect("failed to create temp project dir");
write_minimal_project(project.path(), "test-robot");
mecha10()
.current_dir(project.path())
.arg("validate")
.assert()
.success()
.stdout(predicate::str::contains("is valid"));
mecha10()
.current_dir(project.path())
.arg("target")
.assert()
.success()
.stdout(predicate::str::contains("No targets configured"));
mecha10()
.current_dir(project.path())
.arg("config")
.arg("show")
.assert()
.success()
.stdout(predicate::str::contains("Project name:"))
.stdout(predicate::str::contains("test-robot"))
.stdout(predicate::str::contains("test-robot-001"));
}
#[test]
fn config_validate_path_flag_targets_an_arbitrary_file_instead_of_the_project() {
let project = TempDir::new().expect("failed to create temp project dir");
let config_path = project.path().join("standalone.json");
fs::write(
&config_path,
r#"{"name": "standalone-robot", "version": "1.0.0", "nodes": {}}"#,
)
.unwrap();
mecha10()
.current_dir(project.path())
.arg("config")
.arg("validate")
.arg("--path")
.arg("standalone.json")
.assert()
.success()
.stdout(predicate::str::contains("standalone.json"))
.stdout(predicate::str::contains("is valid"));
}
#[test]
fn validate_outside_a_project_fails_fast_with_exit_code_one() {
let empty_dir = TempDir::new().expect("failed to create temp dir");
mecha10()
.current_dir(empty_dir.path())
.arg("validate")
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("Not in a Mecha10 project directory"));
}
#[test]
fn target_outside_a_project_prints_guidance_and_exits_zero() {
let empty_dir = TempDir::new().expect("failed to create temp dir");
mecha10()
.current_dir(empty_dir.path())
.arg("target")
.assert()
.success()
.stdout(predicate::str::contains("Not inside a mecha10 project"));
}
#[test]
fn validate_rejects_a_config_with_an_invalid_version_string() {
let project = TempDir::new().expect("failed to create temp project dir");
fs::write(
project.path().join("mecha10.json"),
r#"{"name": "bad-robot", "version": "latest", "nodes": {}}"#,
)
.unwrap();
mecha10()
.current_dir(project.path())
.arg("validate")
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("Config validation failed"));
}
#[test]
fn upload_dry_run_without_a_built_binary_fails_offline_with_exit_code_one() {
let project = TempDir::new().expect("failed to create temp project dir");
write_minimal_project(project.path(), "test-robot");
mecha10()
.current_dir(project.path())
.arg("upload")
.arg("--dry-run")
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("Binary not found"));
}