#![allow(unsafe_code)]
#![allow(clippy::await_holding_lock)]
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, MutexGuard};
use cli_engine::{
Cli, CliConfig, CommandResult, CommandSpec, EnvTable, Environments, GroupSpec, Module,
RuntimeCommandSpec, RuntimeGroupSpec, Stage,
};
use serde_json::{Value, json};
fn trivial_command(name: &'static str) -> RuntimeCommandSpec {
RuntimeCommandSpec::new(
CommandSpec::new(name, "Fixture command").no_auth(true),
async |_credential, _args| Ok(CommandResult::new(json!({ "ok": true }))),
)
}
fn gated_module() -> Module {
Module::new("Feature Flag Fixtures", |_ctx| {
RuntimeGroupSpec::new(GroupSpec::new("devkit", "Devkit commands"))
.with_group(
RuntimeGroupSpec::new(
GroupSpec::new("sandbox", "Experimental sandbox tools")
.with_feature_flag("sandbox-flag", Stage::Experimental),
)
.with_command(trivial_command("peek")),
)
.with_command(trivial_command("status"))
})
}
#[tokio::test]
async fn cascading_flag_prunes_inherited_descendant_but_keeps_unflagged_sibling() {
let cli = Cli::new(
CliConfig::new("flagcascade", "Flag cascade test", "flagcascade-app")
.with_module(gated_module()),
);
let help = cli.run(["flagcascade", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("status"), "{}", help.rendered);
assert!(!help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli.run(["flagcascade", "devkit", "sandbox", "peek"]).await;
assert_ne!(dispatch.exit_code, 0, "{}", dispatch.rendered);
assert!(
dispatch.rendered.contains("unknown command"),
"{}",
dispatch.rendered
);
let schema = cli
.run([
"flagcascade",
"devkit",
"sandbox",
"peek",
"--schema",
"--output",
"json",
])
.await;
assert_ne!(schema.exit_code, 0, "{}", schema.rendered);
assert!(
schema.rendered.contains("unknown command"),
"{}",
schema.rendered
);
let visible = cli
.run(["flagcascade", "devkit", "status", "--output", "json"])
.await;
assert_eq!(visible.exit_code, 0, "{}", visible.rendered);
let visible_json: Value = serde_json::from_str(&visible.rendered).expect("json output");
assert_eq!(visible_json["data"]["ok"], true);
}
#[tokio::test]
async fn permissive_min_stage_reveals_previously_pruned_subtree() {
let cli = Cli::new(
CliConfig::new(
"flagpermissive",
"Flag permissive test",
"flagpermissive-app",
)
.with_min_stage(Stage::Experimental)
.with_module(gated_module()),
);
let help = cli.run(["flagpermissive", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run([
"flagpermissive",
"devkit",
"sandbox",
"peek",
"--output",
"json",
])
.await;
assert_eq!(dispatch.exit_code, 0, "{}", dispatch.rendered);
}
#[tokio::test]
async fn environment_min_stage_loosens_consumer_policy_end_to_end() {
let cli = Cli::new(
CliConfig::new("flagenv", "Flag environment test", "flagenv-app")
.with_environments(Arc::new(
Environments::new("flagtest-envmin").with_environment(
"flagtest-envmin",
EnvTable::new().with("min_stage", "experimental"),
),
))
.with_module(gated_module()),
);
let help = cli.run(["flagenv", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run(["flagenv", "devkit", "sandbox", "peek", "--output", "json"])
.await;
assert_eq!(dispatch.exit_code, 0, "{}", dispatch.rendered);
}
#[tokio::test]
async fn environment_feature_override_reveals_pruned_subtree_end_to_end() {
let cli = Cli::new(
CliConfig::new(
"flagenvoverride",
"Flag environment override test",
"flagenvoverride-app",
)
.with_environments(Arc::new(
Environments::new("flagtest-featoverride").with_environment(
"flagtest-featoverride",
EnvTable::new().with(
"feature_overrides",
BTreeMap::from([("sandbox-flag", "ga")]),
),
),
))
.with_module(gated_module()),
);
let help = cli.run(["flagenvoverride", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run([
"flagenvoverride",
"devkit",
"sandbox",
"peek",
"--output",
"json",
])
.await;
assert_eq!(dispatch.exit_code, 0, "{}", dispatch.rendered);
}
#[tokio::test]
async fn environment_min_stage_tightens_permissive_consumer_policy_end_to_end() {
let cli = Cli::new(
CliConfig::new("flagtighten", "Flag tighten test", "flagtighten-app")
.with_min_stage(Stage::Experimental)
.with_environments(Arc::new(
Environments::new("flagtest-tighten")
.with_environment("flagtest-tighten", EnvTable::new().with("min_stage", "ga")),
))
.with_module(gated_module()),
);
let help = cli.run(["flagtighten", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("status"), "{}", help.rendered);
assert!(!help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run([
"flagtighten",
"devkit",
"sandbox",
"peek",
"--output",
"json",
])
.await;
assert_ne!(dispatch.exit_code, 0, "{}", dispatch.rendered);
assert!(
dispatch.rendered.contains("unknown command"),
"{}",
dispatch.rendered
);
}
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn lock() -> MutexGuard<'static, ()> {
ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct EnvGuard {
key: &'static str,
prev: Option<std::ffi::OsString>,
}
impl EnvGuard {
fn set(key: &'static str, value: &str) -> Self {
let prev = std::env::var_os(key);
unsafe { std::env::set_var(key, value) };
Self { key, prev }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prev {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
const GLOBAL_MIN_STAGE_APP_ID: &str = "globalminstage";
const GLOBAL_MIN_STAGE_VAR: &str = "GLOBALMINSTAGE_MIN_STAGE";
#[tokio::test]
async fn global_min_stage_env_var_reveals_previously_pruned_subtree_end_to_end() {
let _lock = lock();
let _guard = EnvGuard::set(GLOBAL_MIN_STAGE_VAR, "experimental");
let cli = Cli::new(
CliConfig::new(
"globalminstage",
"Global min-stage test",
GLOBAL_MIN_STAGE_APP_ID,
)
.with_module(gated_module()),
);
let help = cli.run(["globalminstage", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run([
"globalminstage",
"devkit",
"sandbox",
"peek",
"--output",
"json",
])
.await;
assert_eq!(dispatch.exit_code, 0, "{}", dispatch.rendered);
}
const GLOBAL_MIN_STAGE_ENV_APP_ID: &str = "globalminstageenv";
const GLOBAL_MIN_STAGE_ENV_VAR: &str = "GLOBALMINSTAGEENV_MIN_STAGE";
#[tokio::test]
async fn active_environment_tightens_beyond_global_min_stage_env_var_end_to_end() {
let _lock = lock();
let _guard = EnvGuard::set(GLOBAL_MIN_STAGE_ENV_VAR, "experimental");
let cli = Cli::new(
CliConfig::new(
"globalminstageenv",
"Global min-stage + environment test",
GLOBAL_MIN_STAGE_ENV_APP_ID,
)
.with_environments(Arc::new(
Environments::new("globalminstageenv-env").with_environment(
"globalminstageenv-env",
EnvTable::new().with("min_stage", "ga"),
),
))
.with_module(gated_module()),
);
let help = cli.run(["globalminstageenv", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("status"), "{}", help.rendered);
assert!(!help.rendered.contains("sandbox"), "{}", help.rendered);
let dispatch = cli
.run([
"globalminstageenv",
"devkit",
"sandbox",
"peek",
"--output",
"json",
])
.await;
assert_ne!(dispatch.exit_code, 0, "{}", dispatch.rendered);
assert!(
dispatch.rendered.contains("unknown command"),
"{}",
dispatch.rendered
);
}
const MALFORMED_GLOBAL_MIN_STAGE_APP_ID: &str = "malformedminstage";
const MALFORMED_GLOBAL_MIN_STAGE_VAR: &str = "MALFORMEDMINSTAGE_MIN_STAGE";
#[tokio::test]
async fn malformed_global_min_stage_env_var_is_ignored_end_to_end() {
let _lock = lock();
let _guard = EnvGuard::set(MALFORMED_GLOBAL_MIN_STAGE_VAR, "nightly");
let cli = Cli::new(
CliConfig::new(
"malformedminstage",
"Malformed global min-stage test",
MALFORMED_GLOBAL_MIN_STAGE_APP_ID,
)
.with_module(gated_module()),
);
let help = cli.run(["malformedminstage", "devkit"]).await;
assert_eq!(help.exit_code, 0, "{}", help.rendered);
assert!(help.rendered.contains("status"), "{}", help.rendered);
assert!(!help.rendered.contains("sandbox"), "{}", help.rendered);
}
fn flagged_module(group_name: &'static str, key: &'static str, stage: Stage) -> Module {
Module::new("Flags Introspection Fixtures", move |_ctx| {
RuntimeGroupSpec::new(
GroupSpec::new(group_name, "Introspection fixture group").with_feature_flag(key, stage),
)
.with_command(trivial_command("list"))
})
}
#[tokio::test]
async fn flags_list_and_info_report_override_and_min_stage_decisions_end_to_end() {
let cli = Cli::new(
CliConfig::new("flagintro", "Flags introspection test", "flagintro-app")
.with_min_stage(Stage::Beta)
.with_feature_override("override-key", Stage::Ga)
.with_module(flagged_module(
"override-group",
"override-key",
Stage::Experimental,
))
.with_module(flagged_module(
"min-stage-group",
"min-stage-key",
Stage::Beta,
)),
);
let list = cli
.run(["flagintro", "flags", "list", "--output", "json"])
.await;
assert_eq!(list.exit_code, 0, "{}", list.rendered);
let rendered: Value = serde_json::from_str(&list.rendered).expect("json output");
let entries = rendered["data"].as_array().expect("data should be array");
let override_entry = entries
.iter()
.find(|entry| entry["path"] == "override-group:list")
.expect("override-decided command entry present");
assert_eq!(override_entry["key"], "override-key");
assert_eq!(override_entry["stage"], "experimental");
assert_eq!(override_entry["visible"], true);
let min_stage_entry = entries
.iter()
.find(|entry| entry["path"] == "min-stage-group:list")
.expect("min-stage-decided command entry present");
assert_eq!(min_stage_entry["key"], "min-stage-key");
assert_eq!(min_stage_entry["stage"], "beta");
assert_eq!(min_stage_entry["visible"], true);
let override_info = cli
.run([
"flagintro",
"flags",
"info",
"override-key",
"--output",
"json",
])
.await;
assert_eq!(override_info.exit_code, 0, "{}", override_info.rendered);
let override_data: Value = serde_json::from_str(&override_info.rendered).expect("json output");
assert_eq!(override_data["data"]["policy"]["override"], "ga");
assert!(
override_data["data"]["entries"]
.as_array()
.expect("entries should be array")
.iter()
.all(|entry| entry["decided_by"] == "override")
);
let min_stage_info = cli
.run([
"flagintro",
"flags",
"info",
"min-stage-key",
"--output",
"json",
])
.await;
assert_eq!(min_stage_info.exit_code, 0, "{}", min_stage_info.rendered);
let min_stage_data: Value =
serde_json::from_str(&min_stage_info.rendered).expect("json output");
assert!(min_stage_data["data"]["policy"]["override"].is_null());
assert!(
min_stage_data["data"]["entries"]
.as_array()
.expect("entries should be array")
.iter()
.all(|entry| entry["decided_by"] == "min_stage")
);
let unknown = cli.run(["flagintro", "flags", "info", "no-such-key"]).await;
assert_ne!(unknown.exit_code, 0, "{}", unknown.rendered);
assert!(
unknown.rendered.contains("no such flag"),
"{}",
unknown.rendered
);
}
#[tokio::test]
async fn flags_info_decides_per_entry_when_multiple_nodes_share_a_key() {
let cli = Cli::new(
CliConfig::new("flagintro2", "Flags introspection test", "flagintro2-app")
.with_min_stage(Stage::Beta)
.with_feature_override("shared-key", Stage::Beta)
.with_module(flagged_module(
"already-visible-group",
"shared-key",
Stage::Beta,
))
.with_module(flagged_module(
"flipped-group",
"shared-key",
Stage::Experimental,
)),
);
let info = cli
.run([
"flagintro2",
"flags",
"info",
"shared-key",
"--output",
"json",
])
.await;
assert_eq!(info.exit_code, 0, "{}", info.rendered);
let data: Value = serde_json::from_str(&info.rendered).expect("json output");
let entries = data["data"]["entries"].as_array().expect("entries array");
let already_visible = entries
.iter()
.find(|entry| entry["path"] == "already-visible-group:list")
.expect("already-visible entry present");
assert_eq!(already_visible["visible"], true);
assert_eq!(already_visible["decided_by"], "min_stage");
let flipped = entries
.iter()
.find(|entry| entry["path"] == "flipped-group:list")
.expect("flipped entry present");
assert_eq!(flipped["visible"], true);
assert_eq!(flipped["decided_by"], "override");
}