use std::process::ExitCode;
use std::sync::atomic::{AtomicUsize, Ordering};
use djogi::apps::{AppDescriptor, AppRegistry};
use djogi::descriptor::{DeferrabilitySpec, EnumDescriptor, ModelDescriptor};
use djogi::migrate::DescriptorProvider;
struct ObservableProvider {
models_called: AtomicUsize,
}
impl ObservableProvider {
fn new() -> Self {
Self {
models_called: AtomicUsize::new(0),
}
}
fn models_call_count(&self) -> usize {
self.models_called.load(Ordering::SeqCst)
}
}
impl DescriptorProvider for ObservableProvider {
fn models(&self) -> Vec<&'static ModelDescriptor> {
self.models_called.fetch_add(1, Ordering::SeqCst);
Vec::new()
}
fn enums(&self) -> Vec<&'static EnumDescriptor> {
Vec::new()
}
fn apps(&self) -> &'static [AppDescriptor] {
AppRegistry::all()
}
fn deferrability_specs(&self) -> Vec<&'static DeferrabilitySpec> {
Vec::new()
}
}
#[test]
fn unknown_subcommand_returns_failure() {
let result = djogi_cli::run_with_args(["djogi", "definitely_not_a_real_subcommand"]);
assert_eq!(
result,
ExitCode::from(2),
"clap parse error must map to exit 2 (refusal), not exit 1 (runtime error)"
);
}
#[test]
fn help_flag_returns_success() {
let result = djogi_cli::run_with_args(["djogi", "--help"]);
assert_eq!(result, ExitCode::SUCCESS, "--help should succeed");
}
#[test]
fn schema_with_empty_provider_returns_failure_and_consults_provider() {
let provider = ObservableProvider::new();
let result = djogi_cli::run_with_provider(["djogi", "schema", "--format", "json"], &provider);
assert_eq!(
result,
ExitCode::from(2),
"empty provider must trigger zero-descriptor refusal (exit 2), not runtime error (exit 1)"
);
assert!(
provider.models_call_count() > 0,
"provider.models() should be called during schema dispatch; \
got {} calls",
provider.models_call_count()
);
}
fn fresh_empty_workspace(stub: &str) -> std::path::PathBuf {
let stamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!("djogi-370-{stub}-{stamp}"));
std::fs::create_dir_all(&dir).expect("create temp workspace");
dir
}
#[test]
fn verify_with_empty_provider_and_no_snapshots_refuses_exit_2() {
let provider = ObservableProvider::new();
let workspace = fresh_empty_workspace("verify-zero-zero");
let result = djogi_cli::run_with_provider(
[
"djogi",
"migrations",
"verify",
"--workspace",
workspace.to_str().expect("utf8 temp path"),
],
&provider,
);
assert_eq!(
result,
ExitCode::from(2),
"verify with no descriptors AND no snapshots must refuse (exit 2)"
);
assert!(
provider.models_call_count() > 0,
"verify dispatch must consult provider.models(); got {} calls",
provider.models_call_count()
);
let _ = std::fs::remove_dir_all(&workspace);
}