use assert_cmd::Command;
use predicates::prelude::*;
const BIN: &str = "rusty-stack";
#[test]
fn test_update_help() {
Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("Scans installed components"))
.stdout(predicate::str::contains("--scan-only"))
.stdout(predicate::str::contains("--all-safe"))
.stdout(predicate::str::contains("--include-experimental"))
.stdout(predicate::str::contains("--json"));
}
#[test]
fn test_update_version() {
Command::cargo_bin(BIN)
.unwrap()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("rusty"));
}
#[test]
fn test_update_scan_only_json_output() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert!(parsed.get("scan").is_some(), "JSON must have 'scan' key");
assert!(parsed.get("plan").is_some(), "JSON must have 'plan' key");
assert!(
parsed.get("summary").is_some(),
"JSON must have 'summary' key"
);
let summary = parsed.get("summary").unwrap();
assert_eq!(summary.get("scan_only").unwrap().as_bool(), Some(true));
assert_eq!(summary.get("status").unwrap().as_str(), Some("scan_only"));
}
#[test]
fn test_update_json_output_has_scan_info() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let scan = parsed.get("scan").unwrap();
assert!(scan.get("rocm_version").is_some());
assert!(scan.get("rocm_channel").is_some());
assert!(scan.get("gpu_architecture").is_some());
assert!(scan.get("installed").is_some());
}
#[test]
fn test_update_json_output_has_plan() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let plan = parsed.get("plan").unwrap();
assert!(plan.get("plan").is_some());
assert!(plan.get("summary").is_some());
let summary = plan.get("summary").unwrap();
assert!(summary.get("total").is_some());
assert!(summary.get("safe").is_some());
assert!(summary.get("guarded").is_some());
assert!(summary.get("candidate").is_some());
assert!(summary.get("blocked").is_some());
assert!(summary.get("selected").is_some());
}
#[test]
fn test_update_all_safe_json() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--all-safe", "--scan-only", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let plan = parsed.get("plan").unwrap();
let plan_items = plan.get("plan").unwrap().as_array().unwrap();
for item in plan_items {
let classification = item.get("classification").unwrap().as_str().unwrap();
assert_eq!(
classification, "safe",
"With --all-safe, only safe items should appear"
);
}
}
#[test]
fn test_update_include_experimental_adds_items() {
let output_without = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout_without = String::from_utf8_lossy(&output_without.stdout);
let parsed_without: serde_json::Value = serde_json::from_str(&stdout_without).unwrap();
let plan_without = parsed_without
.get("plan")
.unwrap()
.get("plan")
.unwrap()
.as_array()
.unwrap()
.len();
let output_with = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--include-experimental", "--json"])
.assert()
.success()
.get_output()
.clone();
let stdout_with = String::from_utf8_lossy(&output_with.stdout);
let parsed_with: serde_json::Value = serde_json::from_str(&stdout_with).unwrap();
let plan_with = parsed_with
.get("plan")
.unwrap()
.get("plan")
.unwrap()
.as_array()
.unwrap()
.len();
assert!(
plan_with >= plan_without,
"Including experimental should have >= items"
);
}
#[test]
fn test_update_target_unknown_component_fails() {
Command::cargo_bin(BIN)
.unwrap()
.args([
"update",
"--scan-only",
"--json",
"nonexistent-component-xyz",
])
.assert()
.failure()
.code(1);
}
#[test]
fn test_update_target_known_component_succeeds() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only", "--json", "rocm"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let plan_items = parsed
.get("plan")
.unwrap()
.get("plan")
.unwrap()
.as_array()
.unwrap();
for item in plan_items {
let id = item.get("component_id").unwrap().as_str().unwrap();
assert_eq!(id, "rocm", "Only rocm should be in the plan");
}
}
#[test]
fn test_update_rejects_unknown_flags() {
Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--nonexistent-flag"])
.assert()
.failure();
}
#[test]
fn test_update_non_tty_produces_json() {
let output = Command::cargo_bin(BIN)
.unwrap()
.args(["update", "--scan-only"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let _: serde_json::Value =
serde_json::from_str(&stdout).expect("Non-TTY output should be valid JSON");
}