use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}
fn core_fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../harness/tests/fixtures")
.join(name)
}
fn fresh_dir(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-watch-cli-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn watch_prints_one_clean_initial_ndjson_event_for_every_supported_harness() {
let cases = [
("claude_code_session.jsonl", "claude_code"),
("codex_session.jsonl", "codex"),
("pi_session_live_corpus.jsonl", "pi"),
("opencode_fixture/opencode.db", "opencode"),
];
for (fixture, source) in cases {
let output = Command::new(bin())
.arg("watch")
.arg(core_fixture(fixture))
.args(["--max-events", "1", "--poll-ms", "10"])
.stdin(Stdio::null())
.output()
.unwrap();
assert!(
output.status.success(),
"{}\nstdout: {}\nstderr: {}",
fixture,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output.stderr.is_empty(), "{fixture}");
let line = String::from_utf8(output.stdout).unwrap();
assert_eq!(line.lines().count(), 1, "{fixture}");
let event: serde_json::Value = serde_json::from_str(&line).unwrap();
assert_eq!(event["type"], "session_snapshot", "{fixture}");
assert_eq!(event["sequence"], 1, "{fixture}");
assert_eq!(event["session"]["source"], source, "{fixture}");
}
}
#[test]
fn a_running_watch_process_observes_a_concurrent_append() {
let dir = fresh_dir("live");
let path = dir.join("session.jsonl");
std::fs::write(
&path,
concat!(
r#"{"type":"user","message":{"role":"user","content":"one"},"uuid":"u1","parentUuid":null,"sessionId":"s1"}"#,
"\n"
),
)
.unwrap();
let mut child = Command::new(bin())
.arg("watch")
.arg(&path)
.args(["--max-events", "2", "--poll-ms", "10"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let mut lines = BufReader::new(stdout).lines();
let initial: serde_json::Value = serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
assert_eq!(initial["type"], "session_snapshot");
assert_eq!(initial["sequence"], 1);
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&path)
.unwrap();
file.write_all(
concat!(
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"two"}]},"uuid":"a1","parentUuid":"u1","sessionId":"s1"}"#,
"\n"
)
.as_bytes(),
)
.unwrap();
file.flush().unwrap();
let appended: serde_json::Value =
serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
assert_eq!(appended["type"], "messages_appended");
assert_eq!(appended["sequence"], 2);
assert_eq!(appended["messages"][0]["content"], "two");
let status = child.wait().unwrap();
assert!(status.success());
std::fs::remove_dir_all(dir).ok();
}