use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../harness/tests/fixtures")
.join(name)
}
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-c1-{tag}-{}-{nanos}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn run(home: &Path, extra: &[&str]) -> Output {
let mut args = vec!["--api-key", "x", "--base-url", "http://127.0.0.1:1"];
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")
.args(&args)
.output()
.expect("failed to run the supercode binary")
}
fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn assert_reduced_banner(err: &str) {
let import_line = err
.lines()
.find(|l| l.contains("imported") && l.contains("session") && l.contains("messages)"))
.unwrap_or_else(|| panic!("no `imported ... session ... (N messages)` line in:\n{err}"));
let msg_count = import_line
.rsplit('(')
.next()
.and_then(|rest| rest.trim_end().strip_suffix("messages)"))
.map(str::trim);
assert!(
msg_count.is_some_and(|n| !n.is_empty() && n.chars().all(|c| c.is_ascii_digit())),
"import line missing a numeric `(N messages)`: {import_line}"
);
let tools_line = err
.lines()
.find(|l| l.contains("builtin schemas") && l.contains("MCP schemas deferred"))
.unwrap_or_else(|| panic!("no `tools ...` line in:\n{err}"));
assert!(
tools_line.trim_start().starts_with("tools"),
"tools line doesn't start with `tools`: {tools_line}"
);
let builtin_n = tools_line.trim_start().trim_start_matches("tools").trim();
assert!(
builtin_n
.split_whitespace()
.next()
.is_some_and(|n| !n.is_empty() && n.chars().all(|c| c.is_ascii_digit())),
"tools line missing a numeric builtin-schema count: {tools_line}"
);
assert!(
tools_line.split("· ").nth(1).is_some_and(|rest| rest
.split_whitespace()
.next()
.is_some_and(|n| n.chars().all(|c| c.is_ascii_digit()))),
"tools line missing a numeric MCP-deferred count: {tools_line}"
);
let context_line = err
.lines()
.find(|l| l.contains("tok (") && l.contains("stubs"))
.unwrap_or_else(|| panic!("no `context ...` line in:\n{err}"));
assert!(
context_line.trim_start().starts_with("context"),
"context line doesn't start with `context`: {context_line}"
);
assert!(
context_line.matches('~').count() >= 2,
"context line missing two `~`-prefixed estimates: {context_line}"
);
assert!(
context_line.contains('\u{2192}'),
"context line missing `\u{2192}`: {context_line}"
);
assert!(
context_line.contains("%)"),
"context line missing `(<pct>%)`: {context_line}"
);
assert!(
context_line.contains("stubs"),
"context line missing a stub count: {context_line}"
);
let request_line = err
.lines()
.find(|l| l.trim_start().starts_with("request") && l.contains("target ≤35%"))
.unwrap_or_else(|| panic!("no proactive reduction request line in:\n{err}"));
assert!(
request_line.contains('\u{2192}')
&& request_line.contains(" input (")
&& (request_line.ends_with(": met)") || request_line.ends_with(": missed)")),
"malformed proactive reduction request line: {request_line}"
);
}
fn sidecar_path_from_banner(err: &str) -> PathBuf {
let line = err
.lines()
.find(|l| l.contains("full copy:"))
.unwrap_or_else(|| panic!("no `full copy: <path>` line in:\n{err}"));
let path = line
.split("full copy:")
.nth(1)
.expect("malformed `full copy:` line")
.trim();
PathBuf::from(path)
}
#[test]
fn resume_reduced_prints_banner_and_creates_sidecar_claude_code() {
let home = fresh_home("cc-reduced");
let out = run(
&home,
&[
"resume",
fixture("claude_code_session.jsonl").to_str().unwrap(),
"--reduced",
"hi",
],
);
let err = stderr(&out);
assert_reduced_banner(&err);
assert!(
err.contains("imported Claude Code session"),
"expected the Claude Code source label:\n{err}"
);
let sidecar = sidecar_path_from_banner(&err);
assert!(
sidecar.exists(),
"sidecar file should exist at {}:\n{err}",
sidecar.display()
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_reduced_prints_banner_and_creates_sidecar_codex() {
let home = fresh_home("codex-reduced");
let out = run(
&home,
&[
"resume",
fixture("codex_session.jsonl").to_str().unwrap(),
"--reduced",
"hi",
],
);
let err = stderr(&out);
assert_reduced_banner(&err);
assert!(
err.contains("imported Codex session"),
"expected the Codex source label:\n{err}"
);
let sidecar = sidecar_path_from_banner(&err);
assert!(
sidecar.exists(),
"sidecar file should exist at {}:\n{err}",
sidecar.display()
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_reduced_prints_banner_and_creates_sidecar_pi() {
let home = fresh_home("pi-reduced");
let out = run(
&home,
&[
"resume",
fixture("pi_real_corpus_tool_call.jsonl").to_str().unwrap(),
"--reduced",
"hi",
],
);
let err = stderr(&out);
assert_reduced_banner(&err);
assert!(
err.contains("imported Pi session"),
"expected the Pi source label:\n{err}"
);
assert!(
sidecar_path_from_banner(&err).exists(),
"Pi reduced resume must persist its full-fidelity sidecar:\n{err}"
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_reduced_selects_non_primary_opencode_sqlite_session() {
let home = fresh_home("opencode-selected-reduced");
let db = fixture("opencode_fixture/opencode.db");
let selected = "ses_fixtureBBBBBBBBBBBBBBB1";
let out = run(
&home,
&[
"resume",
db.to_str().unwrap(),
"--opencode-session",
selected,
"--reduced",
"hi",
],
);
let err = stderr(&out);
assert_reduced_banner(&err);
assert!(
err.contains("imported OpenCode session") && err.contains(selected),
"resume must continue the explicitly selected non-primary OpenCode session:\n{err}"
);
assert!(
sidecar_path_from_banner(&err).exists(),
"selected OpenCode reduced resume must persist its sidecar:\n{err}"
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_opencode_sqlite_default_warns_that_it_is_resuming_primary() {
let home = fresh_home("opencode-default-warning");
let db = fixture("opencode_fixture/opencode.db");
let primary = "ses_fixtureAAAAAAAAAAAAAAA1";
let secondary = "ses_fixtureBBBBBBBBBBBBBBB1";
let out = run(&home, &["resume", db.to_str().unwrap(), "--reduced", "hi"]);
let err = stderr(&out);
assert!(
err.contains("holds 2 sessions")
&& err.contains(&format!("resuming {primary}"))
&& err.contains(secondary),
"default multi-session resume warning must name the action, chosen id, and alternatives:\n{err}"
);
assert!(
!err.contains("converting"),
"resume warning must not describe a convert operation:\n{err}"
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_help_advertises_all_harnesses_and_opencode_selector() {
let home = fresh_home("resume-help-formats");
let out = run(&home, &["resume", "--help"]);
assert!(
out.status.success(),
"resume --help failed: {}",
stderr(&out)
);
let text = String::from_utf8_lossy(&out.stdout);
for harness in ["Claude Code", "Codex", "OpenCode", "Pi"] {
assert!(
text.contains(harness),
"resume help omitted {harness}:\n{text}"
);
}
assert!(
text.contains("--opencode-session <ID>"),
"resume help omitted the multi-session SQLite selector:\n{text}"
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_opencode_selector_rejects_non_sqlite_input() {
let home = fresh_home("opencode-selector-wrong-input");
let out = Command::new(bin())
.env("SUPERCODE_HOME", &home)
.env_remove("OPENROUTER_API_KEY")
.env_remove("OPENAI_API_KEY")
.env_remove("ANTHROPIC_API_KEY")
.args([
"--bare",
"resume",
fixture("pi_real_corpus_plain_text.jsonl").to_str().unwrap(),
"--opencode-session",
"ses_not_applicable",
"hi",
])
.output()
.expect("failed to run selector validation");
assert!(!out.status.success());
let err = stderr(&out);
assert!(
err.contains("--opencode-session requires an OpenCode SQLite")
&& err.contains("is not one"),
"selector on a non-SQLite input must fail clearly before any model call:\n{err}"
);
assert!(
!err.to_ascii_lowercase().contains("api key"),
"selector/input error must precede credential validation:\n{err}"
);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_without_reduced_flag_persists_full_family_without_reduced_banner() {
let home = fresh_home("no-flag");
let out = run(
&home,
&[
"resume",
fixture("claude_code_session.jsonl").to_str().unwrap(),
"hi",
],
);
let err = stderr(&out);
assert!(
!err.contains("imported") && !err.contains("full copy:"),
"unexpected reduced-mode banner line(s):\n{err}"
);
let sessions = home.join("sessions");
let sidecars: Vec<PathBuf> = std::fs::read_dir(&sessions)
.unwrap()
.flatten()
.map(|entry| entry.path())
.filter(|path| path.to_string_lossy().ends_with(".sidecar.jsonl"))
.collect();
assert_eq!(
sidecars.len(),
1,
"expected one durable continuation family"
);
let name = sidecars[0]
.file_name()
.unwrap()
.to_str()
.unwrap()
.strip_suffix(".sidecar.jsonl")
.unwrap();
let meta: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(sessions.join(format!("{name}.meta.json"))).unwrap(),
)
.unwrap();
assert_eq!(meta["reduced"], false);
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn resume_config_reduce_true_enables_banner_and_no_reduced_flag_overrides_it() {
let home = fresh_home("config-reduce");
std::fs::write(home.join("config.toml"), "reduce = true\n").unwrap();
let out = run(
&home,
&[
"resume",
fixture("claude_code_session.jsonl").to_str().unwrap(),
"hi",
],
);
assert_reduced_banner(&stderr(&out));
let out2 = run(
&home,
&[
"resume",
fixture("claude_code_session.jsonl").to_str().unwrap(),
"--no-reduced",
"hi",
],
);
let err2 = stderr(&out2);
assert!(
!err2.contains("imported") && !err2.contains("full copy:"),
"expected --no-reduced to suppress the banner despite `reduce = true`:\n{err2}"
);
let _ = std::fs::remove_dir_all(&home);
}