use std::io::{Read, Write};
use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::time::Duration;
use supercode::store::SessionStore;
use supercode::ChatMessage;
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-ux25-names-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn spawn_sse_stub(reply: &'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");
sock.set_read_timeout(Some(Duration::from_millis(500)))
.expect("set read timeout");
let mut buf = [0u8; 65536];
loop {
match sock.read(&mut buf) {
Ok(0) => break,
Ok(_) => continue,
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
break
}
Err(e) => panic!("stub read failed: {e}"),
}
}
let sse = format!(
"data: {{\"choices\":[{{\"delta\":{{\"content\":\"{reply}\"}}}}]}}\n\ndata: [DONE]\n\n"
);
let resp = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
sse.len(),
sse
);
sock.write_all(resp.as_bytes())
.expect("write stub response");
sock.flush().ok();
});
(addr, handle)
}
fn run(home: &Path, base_url: &str, extra: &[&str]) -> Output {
let mut args = vec!["--api-key", "x", "--base-url", base_url];
args.extend_from_slice(extra);
Command::new(bin())
.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)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("failed to spawn the supercode binary")
}
fn run_json(home: &Path, args: &[&str]) -> serde_json::Value {
let out = run(home, "http://127.0.0.1:1", args);
assert!(
out.status.success(),
"expected success for {args:?}: stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
serde_json::from_slice(&out.stdout).unwrap_or_else(|e| {
panic!(
"stdout was not valid JSON: {e}\nstdout: {}",
String::from_utf8_lossy(&out.stdout)
)
})
}
fn assert_memorable_name_shape(name: &str) {
let parts: Vec<&str> = name.split('-').collect();
assert_eq!(
parts.len(),
3,
"expected `<tag>-<adjective>-<noun>`, got `{name}`"
);
let (tag, adj, noun) = (parts[0], parts[1], parts[2]);
assert_eq!(tag.len(), 16, "cwd tag should be 16 hex chars: `{tag}`");
assert!(
tag.chars().all(|c| c.is_ascii_hexdigit()),
"cwd tag should be hex: `{tag}`"
);
assert!(
!adj.is_empty() && adj.chars().all(|c| c.is_ascii_lowercase()),
"adjective should be lowercase alpha: `{adj}`"
);
assert!(!noun.is_empty(), "noun segment empty in `{name}`");
let alpha_prefix_len = noun.chars().take_while(|c| c.is_ascii_lowercase()).count();
assert!(
alpha_prefix_len > 0,
"noun segment should start with lowercase letters: `{noun}`"
);
assert!(
noun[alpha_prefix_len..].chars().all(|c| c.is_ascii_digit()),
"trailing chars after the noun should be a numeric discriminator: `{noun}`"
);
}
#[test]
fn run_without_an_explicit_name_creates_a_memorable_session() {
let (addr, _server) = spawn_sse_stub("hello there");
let home = fresh_home("mint");
let proj = home.join("proj");
std::fs::create_dir_all(&proj).unwrap();
let out = run(
&home,
&format!("http://{addr}"),
&["--cwd", proj.to_str().unwrap(), "run", "say hi"],
);
assert!(
out.status.success(),
"run failed: stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(String::from_utf8_lossy(&out.stdout).contains("hello there"));
let listed = run_json(&home, &["sessions", "list", "--json"]);
let arr = listed.as_array().expect("array");
assert_eq!(arr.len(), 1, "expected exactly one saved session: {arr:?}");
let name = arr[0]["name"]
.as_str()
.expect("name is a string")
.to_string();
assert_memorable_name_shape(&name);
let expected_short_id = name.rsplit('-').next().unwrap();
assert_eq!(arr[0]["short_id"], expected_short_id);
let human = run(&home, "http://127.0.0.1:1", &["sessions", "list"]);
assert!(human.status.success());
let human_out = String::from_utf8_lossy(&human.stdout);
assert!(
human_out.contains(expected_short_id),
"human `sessions list` should show the memorable short id: {human_out}"
);
}
#[test]
fn two_sessions_created_back_to_back_get_distinct_memorable_names() {
let (addr1, _s1) = spawn_sse_stub("first reply");
let home = fresh_home("distinct");
let proj = home.join("proj");
std::fs::create_dir_all(&proj).unwrap();
let out1 = run(
&home,
&format!("http://{addr1}"),
&["--cwd", proj.to_str().unwrap(), "run", "one"],
);
assert!(out1.status.success());
let (addr2, _s2) = spawn_sse_stub("second reply");
let out2 = run(
&home,
&format!("http://{addr2}"),
&["--cwd", proj.to_str().unwrap(), "run", "two"],
);
assert!(out2.status.success());
let listed = run_json(&home, &["sessions", "list", "--json"]);
let arr = listed.as_array().expect("array");
assert_eq!(arr.len(), 2, "expected two saved sessions: {arr:?}");
let names: Vec<&str> = arr.iter().map(|s| s["name"].as_str().unwrap()).collect();
assert_ne!(names[0], names[1], "two sessions collided on name");
for n in &names {
assert_memorable_name_shape(n);
}
}
#[test]
fn sessions_show_reductions_resolves_a_memorable_name_and_a_unique_prefix() {
let (addr, _server) = spawn_sse_stub("hi back");
let home = fresh_home("resolve");
let proj = home.join("proj");
std::fs::create_dir_all(&proj).unwrap();
let out = run(
&home,
&format!("http://{addr}"),
&["--cwd", proj.to_str().unwrap(), "run", "say hi"],
);
assert!(out.status.success());
let listed = run_json(&home, &["sessions", "list", "--json"]);
let name = listed[0]["name"].as_str().unwrap().to_string();
let exact = run(
&home,
"http://127.0.0.1:1",
&["sessions", "show-reductions", &name],
);
assert!(
exact.status.success(),
"resolving by exact memorable name failed: stderr={}",
String::from_utf8_lossy(&exact.stderr)
);
let prefix: String = name.rsplit_once('-').unwrap().0.to_string();
let by_prefix = run(
&home,
"http://127.0.0.1:1",
&["sessions", "show-reductions", &prefix],
);
assert!(
by_prefix.status.success(),
"resolving by unique prefix `{prefix}` failed: stderr={}",
String::from_utf8_lossy(&by_prefix.stderr)
);
}
#[test]
fn continue_flag_resumes_the_freshly_minted_memorable_session() {
let (addr, _server) = spawn_sse_stub("hi back");
let home = fresh_home("continue-new");
let proj = home.join("proj");
std::fs::create_dir_all(&proj).unwrap();
let out = run(
&home,
&format!("http://{addr}"),
&["--cwd", proj.to_str().unwrap(), "run", "say hi"],
);
assert!(out.status.success());
let cont = run(
&home,
"http://127.0.0.1:1",
&["--cwd", proj.to_str().unwrap(), "-c", "run", "again"],
);
let stderr = String::from_utf8_lossy(&cont.stderr);
assert!(
stderr.contains("Continuing session ("),
"expected a `Continuing session (...)` banner, got: {stderr}"
);
assert!(
!stderr.contains("no prior session found"),
"the freshly minted memorable session should have been found: {stderr}"
);
}
#[test]
fn existing_timestamp_named_sessions_still_resume_and_list() {
let home = fresh_home("legacy");
let store = SessionStore::open(home.join("sessions")).unwrap();
let legacy_tag = "00112233445566aa";
let old_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros()
- 3_600_000_000; let legacy_name = format!("{legacy_tag}-{old_micros:020}");
let legacy_jsonl = r#"{"role":"system","content":"hi"}
{"role":"user","content":"hello"}
{"role":"assistant","content":"hi there"}"#;
store
.save(&legacy_name, "legacy session", legacy_jsonl)
.unwrap();
let listed = run_json(&home, &["sessions", "list", "--json"]);
let arr = listed.as_array().unwrap();
assert_eq!(arr.len(), 1);
assert_eq!(arr[0]["name"], legacy_name);
assert_eq!(arr[0]["short_id"], format!("{old_micros:020}"));
let age = arr[0]["age"].as_str().unwrap();
assert_ne!(age, "?", "legacy session age should resolve, got `?`");
let exact = run(
&home,
"http://127.0.0.1:1",
&["sessions", "show-reductions", &legacy_name],
);
assert!(exact.status.success());
let by_prefix = run(
&home,
"http://127.0.0.1:1",
&["sessions", "show-reductions", legacy_tag],
);
assert!(by_prefix.status.success());
let cont = run(&home, "http://127.0.0.1:1", &["--last", "run", "again"]);
let stderr = String::from_utf8_lossy(&cont.stderr);
assert!(
stderr.contains("Continuing session (3 messages)"),
"expected the legacy session's 3 messages to load, got: {stderr}"
);
}
#[test]
fn newest_first_ordering_mixes_legacy_and_memorable_sessions_correctly() {
let (addr, _server) = spawn_sse_stub("hi back");
let home = fresh_home("mixed-order");
let proj = home.join("proj");
std::fs::create_dir_all(&proj).unwrap();
let store = SessionStore::open(home.join("sessions")).unwrap();
let old_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros()
- 7_200_000_000; let legacy_name = format!("ffeeddccbbaa9988-{old_micros:020}");
store.save(&legacy_name, "old", "{}\n").unwrap();
let out = run(
&home,
&format!("http://{addr}"),
&["--cwd", proj.to_str().unwrap(), "run", "say hi"],
);
assert!(out.status.success());
let listed = run_json(&home, &["sessions", "list", "--json"]);
let arr = listed.as_array().unwrap();
assert_eq!(arr.len(), 2);
assert_ne!(
arr[0]["name"], legacy_name,
"memorable session should sort newest-first"
);
assert_eq!(arr[1]["name"], legacy_name);
let legacy_age = arr[1]["age"].as_str().unwrap();
assert!(
legacy_age.ends_with("h ago") || legacy_age.ends_with("m ago"),
"legacy session should show a real age, got `{legacy_age}`"
);
}
#[test]
fn no_reduced_saved_session_is_guarded_before_network_or_persistence() {
let home = fresh_home("context-guard");
let store = SessionStore::open(home.join("sessions")).unwrap();
let messages = [
ChatMessage::system("system"),
ChatMessage::user("x".repeat(900_000)),
];
let transcript = messages
.iter()
.map(|message| serde_json::to_string(message).unwrap())
.collect::<Vec<_>>()
.join("\n");
store.save("oversized", "oversized", &transcript).unwrap();
let before = store.load("oversized").unwrap();
let out = run(
&home,
"http://127.0.0.1:1",
&[
"--model",
"tiny-unknown/model",
"--last",
"--no-reduced",
"run",
"must be refused locally",
],
);
assert!(
!out.status.success(),
"oversized run unexpectedly succeeded"
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.contains("cannot reduce below context limit") && !err.contains("transport error"),
"the local context guard must win before any network attempt: {err}"
);
assert_eq!(
store.load("oversized").unwrap(),
before,
"a locally-refused prompt must not mutate the persisted transcript"
);
}
#[test]
fn bare_guard_uses_the_agents_model_not_the_ignored_user_config_model() {
let home = fresh_home("context-guard-bare-model");
std::fs::write(
home.join("config.toml"),
"schema_version = 1\nmodel = \"google/gemini-2.5-pro\"\n",
)
.unwrap();
let store = SessionStore::open(home.join("sessions")).unwrap();
let messages = [
ChatMessage::system("system"),
ChatMessage::user("x".repeat(2_200_000)),
];
let transcript = messages
.iter()
.map(|message| serde_json::to_string(message).unwrap())
.collect::<Vec<_>>()
.join("\n");
store
.save("bare-oversized", "bare oversized", &transcript)
.unwrap();
let out = run(
&home,
"http://127.0.0.1:1",
&["--bare", "--last", "--no-reduced", "run", "refuse"],
);
assert!(
!out.status.success(),
"oversized run unexpectedly succeeded"
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.contains("model anthropic/claude-opus-4-8 limit 500000")
&& !err.contains("transport error"),
"guard must use the model installed on the bare agent: {err}"
);
}