diff --git a/crates/cli/src/subcommands/login.rs b/crates/cli/src/subcommands/login.rs index df07ec5087d..daa216a8389 100644 --- a/crates/cli/src/subcommands/login.rs +++ b/crates/cli/src/subcommands/login.rs @@ -71,6 +71,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E Ok(identity) => println!("Logged in with identity {identity}"), Err(_) => println!("Token saved."), } + return Ok(()); } if let Some(server) = server_issued_login { diff --git a/crates/smoketests/tests/smoketests/cli/auth.rs b/crates/smoketests/tests/smoketests/cli/auth.rs index 977c7e88109..73e9477d51f 100644 --- a/crates/smoketests/tests/smoketests/cli/auth.rs +++ b/crates/smoketests/tests/smoketests/cli/auth.rs @@ -3,6 +3,7 @@ use spacetimedb_smoketests::{require_local_server, Smoketest}; use std::fs; use std::process::Output; +use std::time::{Duration, Instant}; fn output_stdout(output: &Output) -> String { String::from_utf8_lossy(&output.stdout).to_string() @@ -170,3 +171,62 @@ fn cli_logging_in_twice_works() { second_stdout ); } + +fn try_until_timeout Option, R>(timeout: Duration, mut f: F) -> Option { + let start = Instant::now(); + loop { + match f() { + Some(result) => return Some(result), + None => { + if start.elapsed() > timeout { + return None; + } + std::thread::sleep(std::time::Duration::from_millis(200)); + } + } + } +} + +/// Test that `spacetime login --token ` exits immediately after saving +/// the token, without falling through to the interactive web login flow. +/// +/// Without the fix in PR #4579, the command would fall through to the web +/// login flow, which hangs waiting for a browser callback. +#[test] +fn cli_login_with_token() { + use std::io::Read; + use std::process::{Command, Stdio}; + + let test = Smoketest::builder().autopublish(false).build(); + let cli_path = spacetimedb_guard::ensure_binaries_built(); + + let mut child = Command::new(&cli_path) + .arg("--config-path") + .arg(&test.config_path) + .args(["login", "--token", "test-dummy-token", "--no-browser"]) + .env_remove("BROWSER") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to spawn spacetime login"); + + // Run with timeout in case something goes wrong and it tries to open the browser for login. + let timeout = Duration::from_secs(5); + let Some(status) = try_until_timeout(timeout, || child.try_wait().expect("Failed to poll child")) else { + child.kill().ok(); + panic!( + "spacetime login --token hung for >{timeout:?} — \ + likely fell through to web login flow" + ); + }; + let mut stdout = String::new(); + child.stdout.take().unwrap().read_to_string(&mut stdout).unwrap(); + assert!( + status.success(), + "spacetime login --token failed (exit {status}):\n{stdout}" + ); + assert!( + stdout.contains("Token saved."), + "Expected 'Token saved.' in output, got: {stdout}" + ); +}