use std::path::{Path, PathBuf};
use supercode_harness::{SessionFollower, SessionSnapshotReason, SessionSource, SessionWatchEvent};
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("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-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn initial_snapshot_supports_all_four_harnesses_without_repolling_unchanged_sources() {
let cases = [
(
fixture("claude_code_session.jsonl"),
SessionSource::ClaudeCode,
),
(fixture("codex_session.jsonl"), SessionSource::Codex),
(fixture("pi_session_live_corpus.jsonl"), SessionSource::Pi),
(
fixture("opencode_fixture/opencode.db"),
SessionSource::OpenCode,
),
];
for (path, expected_source) in cases {
let mut follower = SessionFollower::open(&path, None).unwrap();
let first = follower.poll().unwrap().expect("initial event");
match first {
SessionWatchEvent::SessionSnapshot {
sequence,
reason,
session,
} => {
assert_eq!(sequence, 1, "{}", path.display());
assert_eq!(reason, SessionSnapshotReason::Initial, "{}", path.display());
assert_eq!(session.meta.source, expected_source, "{}", path.display());
assert!(!session.messages.is_empty(), "{}", path.display());
}
other => panic!(
"expected initial snapshot for {}: {other:?}",
path.display()
),
}
assert!(
follower.poll().unwrap().is_none(),
"an unchanged source must not be reparsed into another event: {}",
path.display()
);
}
}
#[test]
fn append_emits_only_new_messages_and_rewrite_emits_a_snapshot() {
let dir = fresh_dir("append-rewrite");
let path = dir.join("session.jsonl");
let first_line = concat!(
r#"{"type":"user","message":{"role":"user","content":"one"},"uuid":"u1","parentUuid":null,"sessionId":"s1"}"#,
"\n"
);
let second_line = concat!(
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"two"}]},"uuid":"a1","parentUuid":"u1","sessionId":"s1"}"#,
"\n"
);
std::fs::write(&path, first_line).unwrap();
let mut follower = SessionFollower::open(&path, None).unwrap();
assert_eq!(follower.poll().unwrap().unwrap().sequence(), 1);
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&path)
.unwrap();
file.write_all(second_line.as_bytes()).unwrap();
file.flush().unwrap();
match follower.poll().unwrap().expect("append event") {
SessionWatchEvent::MessagesAppended {
sequence,
session_id,
messages,
..
} => {
assert_eq!(sequence, 2);
assert_eq!(session_id.as_deref(), Some("s1"));
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content.as_deref(), Some("two"));
}
other => panic!("expected append event: {other:?}"),
}
std::fs::write(
&path,
r#"{"type":"user","message":{"role":"user","content":"replacement history is deliberately longer"},"uuid":"u2","parentUuid":null,"sessionId":"s1"}
"#,
)
.unwrap();
match follower.poll().unwrap().expect("rewrite event") {
SessionWatchEvent::SessionSnapshot {
sequence,
reason,
session,
} => {
assert_eq!(sequence, 3);
assert_eq!(reason, SessionSnapshotReason::HistoryRewritten);
assert_eq!(session.messages.len(), 1);
}
other => panic!("expected rewrite snapshot: {other:?}"),
}
std::fs::remove_dir_all(dir).ok();
}
#[test]
fn malformed_partial_write_is_recoverable_and_keeps_the_last_good_snapshot() {
let dir = fresh_dir("partial");
let path = dir.join("session.jsonl");
let first_line = concat!(
r#"{"type":"user","message":{"role":"user","content":"one"},"uuid":"u1","parentUuid":null,"sessionId":"s1"}"#,
"\n"
);
std::fs::write(&path, first_line).unwrap();
let mut follower = SessionFollower::open(&path, None).unwrap();
follower.poll().unwrap().unwrap();
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&path)
.unwrap();
file.write_all(b"{").unwrap();
file.flush().unwrap();
match follower.poll().unwrap().expect("parse error event") {
SessionWatchEvent::WatchError { sequence, message } => {
assert_eq!(sequence, 2);
assert!(message.contains("malformed or truncated"));
}
other => panic!("expected recoverable error: {other:?}"),
}
assert!(follower.poll().unwrap().is_none());
std::fs::remove_dir_all(dir).ok();
}
#[test]
fn claude_subagent_file_creation_invalidates_the_source_fingerprint() {
let dir = fresh_dir("subagent");
let path = dir.join("main.jsonl");
let main = concat!(
r#"{"type":"user","message":{"role":"user","content":"delegate"},"uuid":"u1","parentUuid":null,"sessionId":"main"}"#,
"\n"
);
std::fs::write(&path, main).unwrap();
let mut follower = SessionFollower::open(&path, None).unwrap();
follower.poll().unwrap().unwrap();
let subagent_dir = dir.join("main/subagents");
std::fs::create_dir_all(&subagent_dir).unwrap();
std::fs::write(
subagent_dir.join("agent-child.jsonl"),
concat!(
r#"{"type":"user","message":{"role":"user","content":"child work"},"uuid":"c1","parentUuid":null,"sessionId":"child","agentId":"child"}"#,
"\n"
),
)
.unwrap();
match follower.poll().unwrap().expect("subagent snapshot") {
SessionWatchEvent::SessionSnapshot {
reason, session, ..
} => {
assert_eq!(reason, SessionSnapshotReason::HistoryRewritten);
assert_eq!(session.subagents.len(), 1);
}
other => panic!("expected snapshot after subagent creation: {other:?}"),
}
std::fs::remove_dir_all(dir).ok();
}
#[test]
fn opencode_default_selection_is_pinned_when_an_unrelated_session_becomes_newest() {
const PRIMARY: &str = "ses_fixtureAAAAAAAAAAAAAAA1";
const SECONDARY: &str = "ses_fixtureBBBBBBBBBBBBBBB1";
let dir = fresh_dir("opencode-pin");
let path = dir.join("opencode.db");
std::fs::copy(fixture("opencode_fixture/opencode.db"), &path).unwrap();
let mut follower = SessionFollower::open(&path, None).unwrap();
match follower.poll().unwrap().unwrap() {
SessionWatchEvent::SessionSnapshot { session, .. } => {
assert_eq!(session.meta.session_id.as_deref(), Some(PRIMARY));
}
other => panic!("expected initial OpenCode snapshot: {other:?}"),
}
let connection = rusqlite::Connection::open(&path).unwrap();
connection
.execute(
"UPDATE session SET time_updated = time_updated + 999999999 WHERE id = ?1",
[SECONDARY],
)
.unwrap();
drop(connection);
assert!(
follower.poll().unwrap().is_none(),
"activity in another OpenCode session must not switch the pinned target"
);
std::fs::remove_dir_all(dir).ok();
}