use std::fs;
use tempfile::TempDir;
use vtcode_exec_events::{ThreadEvent, TurnCompletedEvent, TurnStartedEvent, Usage};
use crate::event_log::SessionEventLog;
use crate::migration::migrate_legacy;
use crate::query::{query_facts, recent_sessions};
use crate::{open, retention::apply_retention, sessions_root};
fn sample_turn() -> Vec<ThreadEvent> {
vec![
ThreadEvent::TurnStarted(TurnStartedEvent::default()),
ThreadEvent::TurnCompleted(TurnCompletedEvent {
usage: Usage::default(),
}),
]
}
#[test]
fn append_and_reconstruct_roundtrip() {
let dir = TempDir::new().expect("tempdir");
let log = open(dir.path(), "sess-1").expect("open");
for _ in 0..3 {
for e in &sample_turn() {
log.append(e).expect("append");
}
}
assert_eq!(log.turn_count(), 3);
let rebuilt = log.reconstruct_turn(2).expect("reconstruct");
assert_eq!(rebuilt.len(), 2);
assert!(matches!(rebuilt[0], ThreadEvent::TurnStarted(_)));
assert!(matches!(rebuilt[1], ThreadEvent::TurnCompleted(_)));
}
#[test]
fn index_rebuilt_on_reopen() {
let dir = TempDir::new().expect("tempdir");
{
let log = open(dir.path(), "sess-2").expect("open");
for e in &sample_turn() {
log.append(e).expect("append");
}
log.complete().expect("complete");
}
let log = SessionEventLog::open(dir.path(), "sess-2").expect("reopen");
assert_eq!(log.turn_count(), 1);
let rebuilt = log.reconstruct_turn(1).expect("reconstruct after reopen");
assert_eq!(rebuilt.len(), 2);
assert!(log.manifest().status == "completed");
}
#[test]
fn migrate_legacy_imports_history_and_trajectory() {
let dir = TempDir::new().expect("tempdir");
let vt = dir.path().join(".vtcode");
fs::create_dir_all(vt.join("history")).expect("mk history");
fs::create_dir_all(vt.join("logs")).expect("mk logs");
let memory = serde_json::json!({
"session_id": "session-foo",
"schema_version": 2,
"summary": "did a thing",
"grounded_facts": [{"fact": "the widget is blue"}],
});
fs::write(
vt.join("history").join("session-foo.memory.json"),
serde_json::to_string_pretty(&memory).expect("ser"),
)
.expect("write memory");
fs::write(
vt.join("logs").join("trajectory-20260101T000000Z.jsonl"),
"{\"kind\":\"llm_retry_metrics\",\"turn\":1}\n",
)
.expect("write traj");
let report = migrate_legacy(dir.path(), false).expect("migrate");
assert_eq!(report.sessions_created, 2);
assert_eq!(report.memory_imported, 1);
assert_eq!(report.trajectory_imported, 1);
let facts = query_facts(dir.path(), 10).expect("facts");
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].fact, "the widget is blue");
assert!(vt.join("history").exists());
assert!(vt.join("logs").exists());
let sessions = recent_sessions(dir.path(), 10);
assert_eq!(sessions.len(), 2);
}
#[test]
fn retention_removes_oldest_sessions() {
let dir = TempDir::new().expect("tempdir");
for i in 0..5u64 {
let log = open(dir.path(), &format!("sess-{i}")).expect("open");
for e in &sample_turn() {
log.append(e).expect("append");
}
log.complete().expect("complete");
let mpath = sessions_root(dir.path())
.join(format!("sess-{i}"))
.join("manifest.json");
let mut m: crate::SessionManifest =
serde_json::from_str(&fs::read_to_string(&mpath).expect("read manifest"))
.expect("parse");
if i < 3 {
m.updated_at = format!("2020-01-{:02}T00:00:00Z", i + 1);
fs::write(&mpath, serde_json::to_string_pretty(&m).expect("ser"))
.expect("write manifest");
}
}
let removed = apply_retention(
dir.path(),
crate::retention::RetentionPolicy {
max_sessions: 4,
max_age_days: 30,
},
)
.expect("retain");
assert_eq!(removed, 3);
let remaining = recent_sessions(dir.path(), 100);
assert_eq!(remaining.len(), 2);
}
#[test]
fn retention_evicts_old_sessions_even_when_under_count_cap() {
let dir = TempDir::new().expect("tempdir");
for i in 0..3u64 {
let log = open(dir.path(), &format!("sess-{i}")).expect("open");
for e in &sample_turn() {
log.append(e).expect("append");
}
log.complete().expect("complete");
if i == 0 {
let mpath = sessions_root(dir.path())
.join("sess-0")
.join("manifest.json");
let mut m: crate::SessionManifest =
serde_json::from_str(&fs::read_to_string(&mpath).expect("read manifest"))
.expect("parse");
m.updated_at = "2020-01-01T00:00:00Z".to_string();
fs::write(&mpath, serde_json::to_string_pretty(&m).expect("ser"))
.expect("write manifest");
}
}
let removed = apply_retention(
dir.path(),
crate::retention::RetentionPolicy {
max_sessions: 10,
max_age_days: 30,
},
)
.expect("retain");
assert_eq!(removed, 1);
let remaining = recent_sessions(dir.path(), 100);
assert_eq!(remaining.len(), 2);
}