//! Integration tests for the `config` command. use std::fs; use assert_cmd::Command; use predicates::prelude::*; use serde_json::json; use tempfile::tempdir; fn cargo_command() -> Command { Command::cargo_bin("config").expect("binary") } #[test] fn no_args_prints_quick_help_card() { let mut command = cargo_command(); command .assert() .code(2) .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains( "error: provide a subcommand: get, set, delete, or ls", )) .stderr(predicate::str::contains("config - Mercury Toolbox")) .stderr(predicate::str::contains("Usage:")) .stderr(predicate::str::contains("get [PATH] [POINTER]")) .stderr(predicate::str::contains( "config [OPTIONS] inspect [PATH] [POINTER]", )) .stderr(predicate::str::contains( "Type 'config --help' for the full command reference.", )); } #[test] fn get_reads_toml_pointer_as_json() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("sample.toml"); fs::write( &path, "[package]\nname = \"toolbox\"\nversion = \"3.0.0\"\n", ) .expect("fixture"); let mut command = cargo_command(); command .arg("--json") .arg("get") .arg(&path) .arg("/package/version") .assert() .success() .stdout(predicate::str::contains("\"format\":\"toml\"")) .stdout(predicate::str::contains("\"pointer\":\"/package/version\"")) .stdout(predicate::str::contains("\"value\":\"3.0.0\"")); } #[test] fn set_then_get_updates_json_number_value() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("config.json"); fs::write(&path, "{\"retries\":1}\n").expect("fixture"); let mut set = cargo_command(); set.arg("set") .arg(&path) .arg("/retries") .arg("5") .arg("--json") .arg("--value-type") .arg("number") .assert() .success() .stdout(predicate::str::contains("\"changed\":true")); let mut get = cargo_command(); get.arg("--json") .arg("get") .arg(&path) .arg("/retries") .assert() .success() .stdout(predicate::str::contains("\"value\":5")); } #[test] fn delete_removes_env_key() { let temp = tempdir().expect("tempdir"); let path = temp.path().join(".env"); fs::write(&path, "KEEP=yes\nDROP=no\n").expect("fixture"); let mut command = cargo_command(); command .arg("delete") .arg(&path) .arg("/DROP") .assert() .success() .stdout(predicate::str::contains("op=delete")); let content = fs::read_to_string(&path).expect("updated"); assert!(content.contains("KEEP=yes")); assert!(!content.contains("DROP=no")); } #[test] fn ls_lists_ini_section_entries() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("settings.ini"); fs::write(&path, "mode=dev\n[server]\nport=8080\nhost=127.0.0.1\n").expect("fixture"); let mut command = cargo_command(); command .arg("--json") .arg("ls") .arg(&path) .arg("/server") .assert() .success() .stdout(predicate::str::contains("\"key\":\"host\"")) .stdout(predicate::str::contains("\"key\":\"port\"")); } #[test] fn ls_accepts_single_stdin_path_stream() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("config.json"); fs::write(&path, "{\"mode\":\"dev\",\"nested\":{\"ok\":true}}\n").expect("fixture"); let mut command = cargo_command(); command .arg("--json") .arg("ls") .write_stdin(format!("{}\n", path.display())) .assert() .success() .stdout(predicate::str::contains("\"pointer\":\"/\"")) .stdout(predicate::str::contains("\"key\":\"mode\"")) .stdout(predicate::str::contains("\"key\":\"nested\"")); } #[test] fn help_includes_new_subcommands_and_flags() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("get [PATH] [POINTER]")) .stdout(predicate::str::contains("set ")) .stdout(predicate::str::contains("--value-type")) .stdout(predicate::str::contains("--format")) .stdout(predicate::str::contains("config")); } #[test] fn ls_on_scalar_pointer_returns_no_results_exit_code() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("config.json"); fs::write(&path, "{\"mode\":\"dev\"}\n").expect("fixture"); let mut command = cargo_command(); command .arg("ls") .arg(&path) .arg("/mode") .assert() .code(1) .stdout(predicate::str::contains("entries=0")); } #[test] fn delete_missing_json_key_returns_no_results_and_preserves_file() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("config.json"); fs::write(&path, "{\"present\":true}\n").expect("fixture"); let mut command = cargo_command(); command .arg("--json") .arg("delete") .arg(&path) .arg("/missing") .assert() .code(1) .stdout(predicate::str::contains("\"changed\":false")) .stdout(predicate::str::contains("\"pointer\":\"/missing\"")); let content = fs::read_to_string(&path).expect("updated"); let parsed: serde_json::Value = serde_json::from_str(&content).expect("valid json"); assert_eq!(parsed, json!({"present": true})); } #[test] fn set_creates_nested_json_arrays_and_objects() { let temp = tempdir().expect("tempdir"); let path = temp.path().join("config.json"); fs::write(&path, "{}\n").expect("fixture"); let mut command = cargo_command(); command .arg("--json") .arg("set") .arg(&path) .arg("/pkg/deps/0/name") .arg("serde") .assert() .success() .stdout(predicate::str::contains("\"changed\":true")); let content = fs::read_to_string(&path).expect("updated"); let parsed: serde_json::Value = serde_json::from_str(&content).expect("valid json"); assert_eq!(parsed, json!({"pkg": {"deps": [{"name": "serde"}]}})); } #[test] fn env_set_rejects_nested_pointer_and_non_scalar_json_values() { let temp = tempdir().expect("tempdir"); let path = temp.path().join(".env"); fs::write(&path, "KEEP=yes\n").expect("fixture"); let mut nested_pointer = cargo_command(); nested_pointer .arg("set") .arg(&path) .arg("/A/B") .arg("x") .assert() .code(3) .stderr(predicate::str::contains( "env write operations only support root keys like /NAME", )); let mut object_value = cargo_command(); object_value .arg("set") .arg(&path) .arg("/OBJ") .arg("{\"nested\":true}") .arg("--value-type") .arg("json") .assert() .code(3) .stderr(predicate::str::contains( "env/ini writes require scalar string|number|bool values", )); }