use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use umbral::cli::{CliError, PluginCommand, clap};
use umbral::{App, Settings};
struct ImpostorMigrate {
fired: Arc<AtomicBool>,
}
#[umbral::async_trait]
impl PluginCommand for ImpostorMigrate {
fn command(&self) -> clap::Command {
clap::Command::new("migrate").about("definitely not the real migrate")
}
async fn run(&self, _m: &clap::ArgMatches) -> Result<(), CliError> {
self.fired.store(true, Ordering::SeqCst);
Ok(())
}
}
#[tokio::test]
async fn an_app_command_named_migrate_never_runs() {
let settings = Settings::from_env().expect("figment defaults always load");
let pool = umbral::db::connect_sqlite("sqlite::memory:")
.await
.expect("in-memory sqlite should always connect");
let fired = Arc::new(AtomicBool::new(false));
let app = App::builder()
.settings(settings)
.database("default", pool)
.command(ImpostorMigrate {
fired: fired.clone(),
})
.build_deferred()
.expect("build_deferred with figment defaults");
let builtins = umbral_cli::builtin_command_names();
let reserved: Vec<&str> = builtins.iter().map(String::as_str).collect();
let catalog =
umbral::cli::command_catalog_with_app_commands(app.commands(), app.plugins(), &reserved);
assert!(
!catalog.iter().any(|(name, _)| name == "migrate"),
"the impostor is still listed as a runnable command: {catalog:?}"
);
let argv: Vec<std::ffi::OsString> = vec!["test-binary".into(), "migrate".into()];
let result = umbral_cli::dispatch_with_argv(app, argv).await;
assert!(
!fired.load(Ordering::SeqCst),
"a command named `migrate` hijacked the built-in — a deploy would apply \
zero migrations and report success"
);
assert!(
result.is_err(),
"expected the REAL migrate to run and refuse the in-memory database; \
got Ok, which means nothing migrated and nothing complained"
);
}