use std::io::{Read, Write};
use std::net::TcpListener;
use std::process::{Command, Output, Stdio};
fn bin() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}
fn spawn_http_stub(
status_line: &'static str,
body: impl Into<String>,
) -> (std::net::SocketAddr, std::thread::JoinHandle<()>) {
let body = body.into();
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 resp = format!(
"HTTP/1.1 {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
status_line,
body.len(),
body
);
sock.write_all(resp.as_bytes())
.expect("write stub response");
sock.flush().ok();
});
(addr, handle)
}
fn run_update(check_url: &str) -> Output {
let home = std::env::temp_dir().join(format!(
"supercode-ux20-update-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&home).unwrap();
Command::new(bin())
.env("SUPERCODE_HOME", &home)
.env("SUPERCODE_UPDATE_CHECK_URL", check_url)
.env_remove("NO_COLOR")
.args(["update"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("failed to spawn the supercode binary")
}
const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[test]
fn newer_release_available_reports_update_and_upgrade_command() {
let body = r#"{"tag_name":"v999.0.0","name":"v999.0.0","draft":false,"prerelease":false}"#;
let (addr, server) = spawn_http_stub("200 OK", body);
let out = run_update(&format!("http://{addr}/repos/x/x/releases/latest"));
assert!(
out.status.success(),
"update should exit 0 even when reporting an available update: status={:?} stdout={} stderr={}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("v999.0.0"),
"expected the newer tag in the output, got: {stdout}"
);
assert!(
stdout.to_lowercase().contains("update"),
"expected an update-available notice, got: {stdout}"
);
assert!(
stdout.contains("brew upgrade supercode") && stdout.contains("cargo install supercode-cli"),
"expected the static upgrade command rows to remain present, got: {stdout}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn up_to_date_release_reports_current() {
let tag = format!("v{CURRENT_VERSION}");
let body = format!(r#"{{"tag_name":"{tag}","name":"{tag}","draft":false}}"#);
let (addr, server) = spawn_http_stub("200 OK", body);
let out = run_update(&format!("http://{addr}/repos/x/x/releases/latest"));
assert!(
out.status.success(),
"update failed: status={:?} stdout={} stderr={}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.to_lowercase().contains("up to date"),
"expected an up-to-date notice when the latest tag matches the running version, got: {stdout}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn check_failure_404_degrades_gracefully_and_still_exits_zero() {
let body = r#"{"message":"Not Found","documentation_url":"https://docs.github.com/rest/releases/releases#get-the-latest-release","status":"404"}"#;
let (addr, server) = spawn_http_stub("404 Not Found", body);
let out = run_update(&format!("http://{addr}/repos/x/x/releases/latest"));
assert!(
out.status.success(),
"a failed update check must never fail the command: status={:?} stdout={} stderr={}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.to_lowercase().contains("couldn't check"),
"expected a graceful 'couldn't check' notice on 404, got: {stdout}"
);
assert!(
stdout.contains("curl -fsSL https://supercode.volter.ai/install.sh"),
"expected the static upgrade commands to still print on check failure, got: {stdout}"
);
server.join().expect("stub server thread panicked");
}
#[test]
fn check_failure_unreachable_endpoint_degrades_gracefully() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind to find a free port");
let addr = listener.local_addr().unwrap();
drop(listener);
let out = run_update(&format!("http://{addr}/repos/x/x/releases/latest"));
assert!(
out.status.success(),
"an unreachable update-check endpoint must never fail the command: status={:?} stdout={} stderr={}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.to_lowercase().contains("couldn't check"),
"expected a graceful 'couldn't check' notice when unreachable, got: {stdout}"
);
}