use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use supercode::store::SessionStore;
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-ux24-json-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn run(home: &Path, args: &[&str]) -> Output {
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 json_stdout(out: &Output) -> serde_json::Value {
assert!(
out.status.success(),
"expected success, got {:?}\nstdout: {}\nstderr: {}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.trim().is_empty(),
"expected clean stderr, got: {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)
)
})
}
#[test]
fn doctor_json_is_valid_and_documents_the_expected_keys() {
let home = fresh_home("doctor");
let out = run(&home, &["doctor", "--json"]);
let v = json_stdout(&out);
let obj = v.as_object().expect("doctor --json is an object");
for key in [
"version",
"config_home",
"config_file_found",
"model",
"base_url",
"api_key_source",
"provider",
"aliases",
"tiers",
"first_failing_tier",
"deep_probe_requested",
] {
assert!(obj.contains_key(key), "missing key `{key}` in {v}");
}
assert_eq!(obj["api_key_source"], serde_json::Value::Null);
assert_eq!(obj["provider"], serde_json::Value::Null);
assert!(obj["aliases"]
.as_array()
.unwrap()
.contains(&serde_json::json!({
"alias": "opus",
"slug": "anthropic/claude-opus-4-8",
})));
assert_eq!(v["first_failing_tier"], "config");
assert_eq!(v["deep_probe_requested"], false);
let tiers = v["tiers"].as_array().expect("tiers is an array");
assert_eq!(tiers.len(), 3, "expected exactly 3 tiers, got {tiers:?}");
for name in ["config", "catalog", "deep"] {
assert!(
tiers.iter().any(|t| t["name"] == name),
"missing tier `{name}` in {tiers:?}"
);
}
}
#[test]
fn doctor_human_output_is_unchanged_without_the_flag() {
let home = fresh_home("doctor-human");
let out = run(&home, &["doctor"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("system health"));
assert!(stdout.contains("model"));
assert!(serde_json::from_str::<serde_json::Value>(&stdout).is_err());
}
#[test]
fn sessions_list_json_reports_a_real_saved_session() {
let home = fresh_home("sessions-list");
let store = SessionStore::open(home.join("sessions")).unwrap();
store
.save("demo-00000000000000000001", "demo session", "{}\n")
.unwrap();
let out = run(&home, &["sessions", "list", "--json"]);
let v = json_stdout(&out);
let arr = v.as_array().expect("sessions list --json is an array");
assert_eq!(arr.len(), 1);
let s = &arr[0];
for key in [
"name",
"short_id",
"title",
"archived",
"reduced",
"tier",
"full_bytes",
"view_bytes",
"stub_count",
"escalations",
"age",
] {
assert!(s.as_object().unwrap().contains_key(key), "missing `{key}`");
}
assert_eq!(s["name"], "demo-00000000000000000001");
assert_eq!(s["short_id"], "00000000000000000001");
assert_eq!(s["title"], "demo session");
assert_eq!(s["archived"], false);
}
#[test]
fn sessions_list_json_is_an_empty_array_with_no_sessions() {
let home = fresh_home("sessions-list-empty");
let out = run(&home, &["sessions", "list", "--json"]);
let v = json_stdout(&out);
assert_eq!(v.as_array().unwrap().len(), 0);
}
#[test]
fn sessions_list_human_output_is_unchanged_without_the_flag() {
let home = fresh_home("sessions-list-human");
let store = SessionStore::open(home.join("sessions")).unwrap();
store
.save("demo-00000000000000000002", "demo session", "{}\n")
.unwrap();
let out = run(&home, &["sessions", "list"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("sessions (1)"));
assert!(stdout.contains("demo session"));
assert!(serde_json::from_str::<serde_json::Value>(&stdout).is_err());
}
#[test]
fn inspect_json_regression_still_emits_a_session_key() {
let home = fresh_home("inspect");
let session_dir = home.join("src");
std::fs::create_dir_all(&session_dir).unwrap();
let file = session_dir.join("sample.jsonl");
std::fs::write(
&file,
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"hi"}]},"sessionId":"s1","uuid":"u1","timestamp":"2024-01-01T00:00:00Z"}
"#,
)
.unwrap();
let out = run(&home, &["inspect", file.to_str().unwrap(), "--json"]);
let v = json_stdout(&out);
assert!(
v.get("session").is_some(),
"inspect --json shape changed: {v}"
);
}
#[test]
fn inspect_json_saved_session_uses_the_same_session_envelope_as_a_file() {
let home = fresh_home("inspect-saved-envelope");
let store = SessionStore::open(home.join("sessions")).unwrap();
store
.save(
"saved-envelope-00000000000000000003",
"saved envelope",
"{\"role\":\"user\",\"content\":\"saved hello\"}\n",
)
.unwrap();
let out = run(
&home,
&["inspect", "saved-envelope-00000000000000000003", "--json"],
);
let v = json_stdout(&out);
let session = v
.get("session")
.expect("saved-session inspect must retain the documented envelope");
assert_eq!(session["message_count"], 1);
assert_eq!(session["messages"][0]["content_preview"], "saved hello");
assert!(
v.get("message_count").is_none(),
"saved sessions must not use a second incompatible root schema: {v}"
);
}
#[test]
fn inspect_json_reads_supercode_native_store_messages_instead_of_reporting_empty() {
let home = fresh_home("inspect-native-store");
let store = SessionStore::open(home.join("sessions")).unwrap();
store
.save(
"native-store-00000000000000000004",
"native store",
concat!(
"{\"role\":\"user\",\"content\":\"native prompt\"}\n",
"{\"role\":\"assistant\",\"content\":\"native reply\"}\n"
),
)
.unwrap();
let out = run(
&home,
&["inspect", "native-store-00000000000000000004", "--json"],
);
let v = json_stdout(&out);
assert_eq!(v["session"]["source"], "native");
assert_eq!(
v["session"]["session_id"],
"native-store-00000000000000000004"
);
assert_eq!(v["session"]["message_count"], 2);
assert_eq!(
v["session"]["messages"][0]["content_preview"],
"native prompt"
);
assert_eq!(
v["session"]["messages"][1]["content_preview"],
"native reply"
);
}
#[test]
fn convert_reads_supercode_native_store_messages_instead_of_writing_empty_output() {
let home = fresh_home("convert-native-store");
let store = SessionStore::open(home.join("sessions")).unwrap();
let name = "native-convert-00000000000000000006";
store
.save(
name,
"native convert",
concat!(
"{\"role\":\"user\",\"content\":\"convert prompt\"}\n",
"{\"role\":\"assistant\",\"content\":\"convert reply\"}\n"
),
)
.unwrap();
let converted = home.join("native.codex.jsonl");
let out = run(
&home,
&[
"convert",
name,
"--to",
"codex",
"--out",
converted.to_str().unwrap(),
],
);
assert!(
out.status.success(),
"convert failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(converted.exists());
let inspected = json_stdout(&run(
&home,
&["inspect", converted.to_str().unwrap(), "--json"],
));
assert_eq!(inspected["session"]["source"], "codex");
assert_eq!(inspected["session"]["message_count"], 2);
assert_eq!(
inspected["session"]["messages"][0]["content_preview"],
"convert prompt"
);
assert_eq!(
inspected["session"]["messages"][1]["content_preview"],
"convert reply"
);
}
#[test]
fn inspect_json_rejects_a_malformed_native_store_line_without_partial_output() {
let home = fresh_home("inspect-native-store-malformed");
let store = SessionStore::open(home.join("sessions")).unwrap();
store
.save(
"native-malformed-00000000000000000005",
"native malformed",
"{\"role\":\"user\",\"content\":\"good prefix\"}\nnot json\n",
)
.unwrap();
let out = run(
&home,
&["inspect", "native-malformed-00000000000000000005", "--json"],
);
assert!(!out.status.success());
assert!(
out.stdout.is_empty(),
"must not bless a partial store transcript"
);
assert!(
String::from_utf8_lossy(&out.stderr).contains("parsing stored session"),
"error must identify the corrupt saved transcript: {}",
String::from_utf8_lossy(&out.stderr)
);
let converted = home.join("must-not-exist.codex.jsonl");
let convert = run(
&home,
&[
"convert",
"native-malformed-00000000000000000005",
"--to",
"codex",
"--out",
converted.to_str().unwrap(),
],
);
assert!(!convert.status.success());
assert!(convert.stdout.is_empty());
assert!(
!converted.exists(),
"convert must not write a partial export for corrupt native data"
);
assert!(
String::from_utf8_lossy(&convert.stderr).contains("parsing stored session"),
"convert error must identify the corrupt saved transcript: {}",
String::from_utf8_lossy(&convert.stderr)
);
}
#[test]
fn model_list_json_is_a_stable_alias_inventory() {
let home = fresh_home("model-list");
let out = run(&home, &["model", "list", "--json"]);
let v = json_stdout(&out);
let arr = v.as_array().expect("model list --json is an array");
assert!(arr.len() >= 8, "expected the full alias table, got {arr:?}");
assert!(arr.contains(&serde_json::json!({
"alias": "sonnet",
"slug": "anthropic/claude-sonnet-4-6",
})));
for entry in arr {
assert!(entry.get("alias").and_then(|a| a.as_str()).is_some());
assert!(entry.get("slug").and_then(|s| s.as_str()).is_some());
}
}
#[test]
fn model_list_human_output_is_unchanged_without_the_flag() {
let home = fresh_home("model-list-human");
let out = run(&home, &["model", "list"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("models ("));
assert!(stdout.contains("opus"));
assert!(serde_json::from_str::<serde_json::Value>(&stdout).is_err());
}
#[test]
fn provider_list_json_is_derived_from_the_model_alias_table() {
let home = fresh_home("provider-list");
let model_out = run(&home, &["model", "list", "--json"]);
let models = json_stdout(&model_out);
let expected_providers: std::collections::BTreeSet<String> = models
.as_array()
.unwrap()
.iter()
.map(|m| {
m["slug"]
.as_str()
.unwrap()
.split('/')
.next()
.unwrap()
.to_string()
})
.collect();
let out = run(&home, &["provider", "list", "--json"]);
let v = json_stdout(&out);
let arr = v.as_array().expect("provider list --json is an array");
let got_providers: std::collections::BTreeSet<String> = arr
.iter()
.map(|p| p["name"].as_str().unwrap().to_string())
.collect();
assert_eq!(
expected_providers, got_providers,
"provider list must never drift from the model alias table"
);
assert!(got_providers.contains("anthropic"));
for p in arr {
let name = p["name"].as_str().unwrap();
let model_count = p["model_count"].as_u64().unwrap();
let aliases = p["aliases"].as_array().unwrap();
assert_eq!(model_count as usize, aliases.len());
for a in aliases {
let alias = a.as_str().unwrap();
let found = models
.as_array()
.unwrap()
.iter()
.find(|m| m["alias"] == alias)
.unwrap_or_else(|| {
panic!("alias `{alias}` from provider `{name}` not in model list")
});
assert!(found["slug"]
.as_str()
.unwrap()
.starts_with(&format!("{name}/")));
}
}
}
#[test]
fn provider_list_human_output_is_readable() {
let home = fresh_home("provider-list-human");
let out = run(&home, &["provider", "list"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("providers ("));
assert!(stdout.contains("anthropic"));
assert!(serde_json::from_str::<serde_json::Value>(&stdout).is_err());
}