#![allow(unsafe_code)]
#![allow(clippy::await_holding_lock)]
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
use cli_engine::{
Cli, CliConfig, CommandResult, CommandSpec, GroupSpec, Module, RuntimeCommandSpec,
RuntimeGroupSpec,
};
use serde::Deserialize;
use serde_json::json;
const APP_ID: &str = "cfgtest";
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn lock() -> MutexGuard<'static, ()> {
ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
struct EnvGuard {
prev: Option<String>,
}
impl EnvGuard {
fn set(path: &Path) -> Self {
let prev = std::env::var("XDG_CONFIG_HOME").ok();
unsafe { std::env::set_var("XDG_CONFIG_HOME", path) };
Self { prev }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prev {
Some(v) => std::env::set_var("XDG_CONFIG_HOME", v),
None => std::env::remove_var("XDG_CONFIG_HOME"),
}
}
}
}
#[derive(Debug, Deserialize)]
struct Deploy {
region: String,
}
fn write_config(xdg: &Path, contents: &str) {
let dir = xdg.join(APP_ID);
std::fs::create_dir_all(&dir).expect("mkdir");
std::fs::write(dir.join("config.toml"), contents).expect("write config");
}
fn config_contents(xdg: &Path) -> Option<String> {
std::fs::read_to_string(xdg.join(APP_ID).join("config.toml")).ok()
}
fn build_cli() -> Cli {
let module = Module::new("Deploy", |_ctx| {
RuntimeGroupSpec::new(GroupSpec::new("deploy", "Deploy things")).with_command(
RuntimeCommandSpec::new_with_context(
CommandSpec::new("show", "Show configured region")
.with_system("deploy")
.no_auth(true),
async |ctx| {
let region = ctx
.config()
.section::<Deploy>("deploy")?
.map(|d| d.region)
.unwrap_or_else(|| "<none>".to_owned());
Ok(CommandResult::new(json!({ "region": region })))
},
),
)
});
Cli::new(
CliConfig::new(APP_ID, "Config test CLI", APP_ID)
.with_config_commands()
.with_module(module),
)
}
#[tokio::test]
async fn handler_reads_consumer_section() {
let _guard = lock();
let dir = tempfile::tempdir().expect("tempdir");
let _env = EnvGuard::set(dir.path());
write_config(dir.path(), "[deploy]\nregion = \"us-west\"\n");
let out = build_cli().run(["cfgtest", "deploy", "show"]).await;
assert_eq!(out.exit_code, 0, "{}", out.rendered);
assert!(
out.rendered.contains("us-west"),
"handler should read [deploy].region: {}",
out.rendered
);
}
#[tokio::test]
async fn config_set_then_get_roundtrips_and_preserves_other_tables() {
let _guard = lock();
let dir = tempfile::tempdir().expect("tempdir");
let _env = EnvGuard::set(dir.path());
write_config(
dir.path(),
"# keep this comment\n[credentials]\nstore = \"file\"\n",
);
let set = build_cli()
.run(["cfgtest", "config", "set", "deploy.region", "eu-west"])
.await;
assert_eq!(set.exit_code, 0, "{}", set.rendered);
let get = build_cli()
.run(["cfgtest", "config", "get", "deploy.region"])
.await;
assert_eq!(get.exit_code, 0, "{}", get.rendered);
assert!(get.rendered.contains("eu-west"), "{}", get.rendered);
let on_disk = config_contents(dir.path()).expect("file exists");
assert!(on_disk.contains("store = \"file\""), "{on_disk}");
assert!(on_disk.contains("# keep this comment"), "{on_disk}");
let store = build_cli()
.run(["cfgtest", "config", "get", "credentials.store"])
.await;
assert!(store.rendered.contains("file"), "{}", store.rendered);
}
#[tokio::test]
async fn config_set_dry_run_does_not_write() {
let _guard = lock();
let dir = tempfile::tempdir().expect("tempdir");
let _env = EnvGuard::set(dir.path());
let out = build_cli()
.run([
"cfgtest",
"config",
"set",
"deploy.region",
"eu-west",
"--dry-run",
])
.await;
assert_eq!(out.exit_code, 0, "{}", out.rendered);
assert!(
config_contents(dir.path()).is_none(),
"dry-run must not create the config file"
);
}
#[tokio::test]
async fn config_path_prints_path() {
let _guard = lock();
let dir = tempfile::tempdir().expect("tempdir");
let _env = EnvGuard::set(dir.path());
let out = build_cli().run(["cfgtest", "config", "path"]).await;
assert_eq!(out.exit_code, 0, "{}", out.rendered);
assert!(out.rendered.contains("cfgtest"), "{}", out.rendered);
assert!(out.rendered.contains("config.toml"), "{}", out.rendered);
}
#[tokio::test]
async fn config_set_rejects_invalid_engine_value() {
let _guard = lock();
let dir = tempfile::tempdir().expect("tempdir");
let _env = EnvGuard::set(dir.path());
let out = build_cli()
.run(["cfgtest", "config", "set", "credentials.store", "bogus"])
.await;
assert_ne!(
out.exit_code, 0,
"invalid engine value should fail: {}",
out.rendered
);
assert!(
config_contents(dir.path()).is_none(),
"rejected set must not write the file"
);
}