Skip to content

Commit ba1711e

Browse files
fix: use active env for auth login (#26)
## Summary - Make built-in auth login/logout accept the active middleware env when --env is omitted - Keep explicit --env as an override - Add regression tests for consumer-style global env defaults
1 parent de65d35 commit ba1711e

3 files changed

Lines changed: 190 additions & 19 deletions

File tree

docs/auth.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,10 @@ When a CLI registers auth providers or configures a default provider, `cli_engin
308308

309309
| Command | Behavior |
310310
| --- | --- |
311-
| `auth login --provider NAME --env ENV` | Clears cached credentials for the environment and authenticates. |
311+
| `auth login --provider NAME [--env ENV]` | Clears cached credentials for the explicit environment, or the active middleware environment when omitted, and authenticates. |
312312
| `auth status --provider NAME --env ENV` | Shows cached status for one provider and environment. |
313313
| `auth status` | Shows status for all providers and cached environments. |
314-
| `auth logout --provider NAME --env ENV` | Clears cached credentials for the environment. |
314+
| `auth logout --provider NAME [--env ENV]` | Clears cached credentials for the explicit environment, or the active middleware environment when omitted. |
315315

316316
These commands are implemented with the same `CommandSpec`, middleware, output envelope, and renderers
317317
as application commands.

src/auth/commands.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use serde_json::{Value, json};
44

55
use super::Dispatcher;
66
use crate::{
7-
CommandResult, CommandSpec, Credential, GroupSpec, Result, RuntimeCommandSpec,
8-
RuntimeGroupSpec, Tier,
7+
CliCoreError, CommandContext, CommandResult, CommandSpec, Credential, GroupSpec, Result,
8+
RuntimeCommandSpec, RuntimeGroupSpec, Tier,
99
};
1010

1111
/// Data rendered after a successful `auth login`.
@@ -48,7 +48,7 @@ pub fn auth_command_group(default_provider: &str, registered_names: &[String]) -
4848
.mutates(true)
4949
.no_auth(true)
5050
.with_arg(provider_arg(&effective_default, registered_names))
51-
.with_arg(Arg::new("env").long("env").value_name("ENV").required(true))
51+
.with_arg(Arg::new("env").long("env").value_name("ENV"))
5252
.with_arg(
5353
Arg::new("scope")
5454
.long("scope")
@@ -62,7 +62,7 @@ pub fn auth_command_group(default_provider: &str, registered_names: &[String]) -
6262
),
6363
async |context| {
6464
let provider = string_arg(&context.args, "provider");
65-
let env = string_arg(&context.args, "env");
65+
let env = env_arg(&context)?;
6666
let scopes = string_vec_arg(&context.args, "scope");
6767
serde_json::to_value(
6868
login_and_build_with_scopes(&context.middleware.auth, &provider, &env, &scopes)
@@ -93,10 +93,10 @@ pub fn auth_command_group(default_provider: &str, registered_names: &[String]) -
9393
.mutates(true)
9494
.no_auth(true)
9595
.with_arg(provider_arg(&effective_default, registered_names))
96-
.with_arg(Arg::new("env").long("env").value_name("ENV").required(true)),
96+
.with_arg(Arg::new("env").long("env").value_name("ENV")),
9797
async |context| {
9898
let provider = string_arg(&context.args, "provider");
99-
let env = string_arg(&context.args, "env");
99+
let env = env_arg(&context)?;
100100
logout_result(&context.middleware.auth, &provider, &env)
101101
.await
102102
.map(CommandResult::new)
@@ -119,6 +119,27 @@ fn string_arg(args: &serde_json::Map<String, Value>, name: &str) -> String {
119119
.to_owned()
120120
}
121121

122+
fn env_arg(context: &CommandContext) -> Result<String> {
123+
if let Some(env) = context.user_args.get("env").and_then(Value::as_str) {
124+
if env.is_empty() {
125+
return Err(missing_env_error());
126+
}
127+
return Ok(env.to_owned());
128+
}
129+
130+
if !context.middleware.env.is_empty() {
131+
return Ok(context.middleware.env.clone());
132+
}
133+
134+
Err(missing_env_error())
135+
}
136+
137+
fn missing_env_error() -> CliCoreError {
138+
CliCoreError::message(
139+
"auth: missing environment; pass --env or configure a default environment",
140+
)
141+
}
142+
122143
/// Reads a repeatable string argument as a `Vec<String>`, accepting either a
123144
/// JSON array (multiple values) or a single string.
124145
fn string_vec_arg(args: &serde_json::Map<String, Value>, name: &str) -> Vec<String> {

tests/foundation.rs

Lines changed: 161 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,108 @@ async fn cli_runtime_auth_login_uses_registered_provider_default() {
23742374
);
23752375
}
23762376

2377+
#[tokio::test]
2378+
async fn cli_runtime_auth_login_uses_middleware_env_when_env_flag_omitted() {
2379+
let cli = auth_cli_with_default_env("dev");
2380+
2381+
let output = cli
2382+
.run(["my-cli", "auth", "login", "--output", "json"])
2383+
.await;
2384+
2385+
assert_eq!(output.exit_code, 0, "{}", output.rendered);
2386+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2387+
assert_eq!(
2388+
rendered["data"],
2389+
json!({
2390+
"provider": "primary",
2391+
"env": "dev",
2392+
"identity": "tester",
2393+
"expires_at": "2099-01-01T00:00:00Z"
2394+
})
2395+
);
2396+
}
2397+
2398+
#[tokio::test]
2399+
async fn cli_runtime_auth_login_env_flag_overrides_middleware_env() {
2400+
let cli = auth_cli_with_default_env("dev");
2401+
2402+
let output = cli
2403+
.run([
2404+
"my-cli", "auth", "login", "--env", "prod", "--output", "json",
2405+
])
2406+
.await;
2407+
2408+
assert_eq!(output.exit_code, 0, "{}", output.rendered);
2409+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2410+
assert_eq!(rendered["data"]["env"], "prod");
2411+
}
2412+
2413+
#[tokio::test]
2414+
async fn cli_runtime_auth_login_empty_env_flag_errors_instead_of_using_middleware_env() {
2415+
let cli = auth_cli_with_default_env("dev");
2416+
2417+
let output = cli
2418+
.run(["my-cli", "auth", "login", "--env", "", "--output", "json"])
2419+
.await;
2420+
2421+
assert_ne!(output.exit_code, 0, "{}", output.rendered);
2422+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2423+
assert_eq!(
2424+
rendered["error"]["message"],
2425+
"auth: missing environment; pass --env or configure a default environment"
2426+
);
2427+
}
2428+
2429+
#[tokio::test]
2430+
async fn cli_runtime_auth_login_errors_when_env_missing() {
2431+
let cli = auth_cli_without_default_env();
2432+
2433+
let output = cli
2434+
.run(["my-cli", "auth", "login", "--output", "json"])
2435+
.await;
2436+
2437+
assert_ne!(output.exit_code, 0, "{}", output.rendered);
2438+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2439+
assert_eq!(
2440+
rendered["error"]["message"],
2441+
"auth: missing environment; pass --env or configure a default environment"
2442+
);
2443+
}
2444+
2445+
#[tokio::test]
2446+
async fn cli_runtime_auth_logout_uses_middleware_env_when_env_flag_omitted() {
2447+
let cli = auth_cli_with_default_env("dev");
2448+
2449+
let output = cli
2450+
.run(["my-cli", "auth", "logout", "--output", "json"])
2451+
.await;
2452+
2453+
assert_eq!(output.exit_code, 0, "{}", output.rendered);
2454+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2455+
assert_eq!(
2456+
rendered["data"],
2457+
json!({"provider": "primary", "env": "dev", "status": "logged out"})
2458+
);
2459+
}
2460+
2461+
#[tokio::test]
2462+
async fn cli_runtime_auth_logout_env_flag_overrides_middleware_env() {
2463+
let cli = auth_cli_with_default_env("dev");
2464+
2465+
let output = cli
2466+
.run([
2467+
"my-cli", "auth", "logout", "--env", "prod", "--output", "json",
2468+
])
2469+
.await;
2470+
2471+
assert_eq!(output.exit_code, 0, "{}", output.rendered);
2472+
let rendered: serde_json::Value = serde_json::from_str(&output.rendered).expect("valid json");
2473+
assert_eq!(
2474+
rendered["data"],
2475+
json!({"provider": "primary", "env": "prod", "status": "logged out"})
2476+
);
2477+
}
2478+
23772479
#[tokio::test]
23782480
async fn cli_runtime_auth_commands_use_init_deps_registered_providers() {
23792481
let init_count = Arc::new(AtomicUsize::new(0));
@@ -4085,18 +4187,66 @@ fn auth_command_group_sets_provider_defaults() {
40854187
.contains("one of: [primary, oauth, device]")
40864188
);
40874189

4088-
let logout = group
4089-
.commands
4090-
.iter()
4091-
.find(|command| command.spec.name == "logout")
4092-
.expect("logout subcommand should exist");
4093-
assert!(
4094-
logout
4095-
.spec
4096-
.args
4190+
for command_name in ["login", "logout"] {
4191+
let command = group
4192+
.commands
40974193
.iter()
4098-
.any(|arg| arg.get_id() == "env" && arg.is_required_set())
4099-
);
4194+
.find(|command| command.spec.name == command_name)
4195+
.expect("auth subcommand should exist");
4196+
assert!(
4197+
command
4198+
.spec
4199+
.args
4200+
.iter()
4201+
.any(|arg| arg.get_id() == "env" && !arg.is_required_set())
4202+
);
4203+
}
4204+
}
4205+
4206+
fn auth_cli_with_default_env(env: &'static str) -> Cli {
4207+
Cli::new(CliConfig {
4208+
name: "my-cli".to_owned(),
4209+
short: "Developer tooling".to_owned(),
4210+
app_id: "my-cli".to_owned(),
4211+
register_flags: Some(Arc::new(move |command: Command| {
4212+
command.arg(
4213+
Arg::new("env")
4214+
.long("env")
4215+
.global(true)
4216+
.default_value(env)
4217+
.value_name("ENV")
4218+
.help("Target environment"),
4219+
)
4220+
})),
4221+
apply_flags: Some(Arc::new(|matches, middleware| {
4222+
if let Some(env) = matches.get_one::<String>("env") {
4223+
middleware.env = env.clone();
4224+
}
4225+
Ok(())
4226+
})),
4227+
auth_providers: vec![Arc::new(FakeProvider {
4228+
name: "primary".to_owned(),
4229+
identity: "tester".to_owned(),
4230+
logout_fails: false,
4231+
environments: vec!["dev".to_owned(), "prod".to_owned()],
4232+
})],
4233+
..CliConfig::default()
4234+
})
4235+
}
4236+
4237+
fn auth_cli_without_default_env() -> Cli {
4238+
Cli::new(CliConfig {
4239+
name: "my-cli".to_owned(),
4240+
short: "Developer tooling".to_owned(),
4241+
app_id: "my-cli".to_owned(),
4242+
auth_providers: vec![Arc::new(FakeProvider {
4243+
name: "primary".to_owned(),
4244+
identity: "tester".to_owned(),
4245+
logout_fails: false,
4246+
environments: vec!["dev".to_owned(), "prod".to_owned()],
4247+
})],
4248+
..CliConfig::default()
4249+
})
41004250
}
41014251

41024252
#[test]

0 commit comments

Comments
 (0)