use std::io::{Read, Write};
use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}
fn fresh_home(tag: &str) -> PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"supercode-ux18-login-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn spawn_models_stub(
status_line: &'static str,
) -> (std::net::SocketAddr, std::thread::JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind stub listener");
let addr = listener.local_addr().unwrap();
let handle = std::thread::spawn(move || {
let (mut sock, _) = listener.accept().expect("accept one connection");
let mut buf = [0u8; 4096];
let _ = sock.read(&mut buf);
let body = "{\"data\":[]}";
let resp = format!(
"HTTP/1.1 {status_line}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
sock.write_all(resp.as_bytes())
.expect("write stub response");
sock.flush().ok();
});
(addr, handle)
}
fn run(home: &Path, stdin_data: Option<&str>, args: &[&str]) -> Output {
let mut cmd = Command::new(bin());
cmd.env("SUPERCODE_HOME", home)
.env_remove("OPENROUTER_API_KEY")
.env_remove("OPENAI_API_KEY")
.env_remove("ANTHROPIC_API_KEY")
.env_remove("NO_COLOR")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if stdin_data.is_some() {
cmd.stdin(Stdio::piped());
} else {
cmd.stdin(Stdio::null());
}
let mut child = cmd.spawn().expect("failed to spawn the supercode binary");
if let Some(data) = stdin_data {
child
.stdin
.take()
.expect("stdin was piped")
.write_all(data.as_bytes())
.expect("write to child stdin");
}
child.wait_with_output().expect("child process failed")
}
fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
const OLD_ECHO_WARNING: &str = "input hidden is not supported";
#[test]
fn non_interactive_api_key_flag_skips_prompt_saves_key_and_pings_reachability() {
let (addr, server) = spawn_models_stub("200 OK");
let home = fresh_home("flag-reachable");
let out = run(
&home,
None,
&[
"login",
"--api-key",
"sk-flag-test",
"--base-url",
&format!("http://{addr}"),
],
);
assert!(
out.status.success(),
"login --api-key failed: status={:?} stderr={}",
out.status,
stderr(&out)
);
let creds = std::fs::read_to_string(home.join("credentials.toml")).expect("credentials.toml");
assert!(
creds.contains("sk-flag-test"),
"expected the key to be persisted, got: {creds}"
);
let err = stderr(&out);
assert!(
!err.contains(OLD_ECHO_WARNING),
"the old plaintext-echo warning must be gone, got: {err}"
);
assert!(
err.contains("reachable (200"),
"expected a reachable(200) ping line, got: {err}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn unreachable_or_rejected_key_still_saves_and_reports_the_failure_without_erroring() {
let (addr, server) = spawn_models_stub("401 Unauthorized");
let home = fresh_home("flag-rejected");
let out = run(
&home,
None,
&[
"login",
"--api-key",
"sk-bad-key",
"--base-url",
&format!("http://{addr}"),
],
);
assert!(
out.status.success(),
"login must still succeed (save is independent of the ping): status={:?} stderr={}",
out.status,
stderr(&out)
);
let creds = std::fs::read_to_string(home.join("credentials.toml")).expect("credentials.toml");
assert!(
creds.contains("sk-bad-key"),
"the key must be saved even when the provider rejects it"
);
let err = stderr(&out);
assert!(
err.contains("HTTP 401") && err.contains("key saved"),
"expected an HTTP 401 rejection line with a save acknowledgement, got: {err}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn api_key_stdin_reads_the_piped_key_without_a_prompt_banner() {
let (addr, server) = spawn_models_stub("200 OK");
let home = fresh_home("stdin");
let out = run(
&home,
Some("sk-from-stdin\n"),
&[
"login",
"--api-key-stdin",
"--base-url",
&format!("http://{addr}"),
],
);
assert!(
out.status.success(),
"login --api-key-stdin failed: status={:?} stderr={}",
out.status,
stderr(&out)
);
let creds = std::fs::read_to_string(home.join("credentials.toml")).expect("credentials.toml");
assert!(
creds.contains("sk-from-stdin"),
"expected the piped key to be persisted, got: {creds}"
);
let err = stderr(&out);
assert!(
!err.contains("API key (OpenRouter"),
"no interactive prompt banner should appear for --api-key-stdin, got: {err}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn api_key_and_api_key_stdin_are_mutually_exclusive() {
let home = fresh_home("conflict");
let out = run(&home, None, &["login", "--api-key", "x", "--api-key-stdin"]);
assert!(
!out.status.success(),
"clap should reject combining --api-key and --api-key-stdin"
);
let err = stderr(&out);
assert!(
err.contains("cannot be used with"),
"expected clap's conflicts_with message, got: {err}"
);
}