use recursive::message::Message;
use recursive::session::{SessionReader, SessionWriter};
use recursive::test_util::IsolatedWorkspace;
use std::sync::{Arc, Mutex};
fn make_writer(ws: &IsolatedWorkspace) -> Arc<Mutex<SessionWriter>> {
Arc::new(Mutex::new(
SessionWriter::create(ws.path(), "test", "gpt-4o", "openai").unwrap(),
))
}
fn session_dir(sw: &Arc<Mutex<SessionWriter>>) -> std::path::PathBuf {
sw.lock().unwrap().session_dir().to_path_buf()
}
#[test]
fn uuid_chain_is_correct() {
let ws = IsolatedWorkspace::new();
let sw = make_writer(&ws);
let dir = session_dir(&sw);
{
let mut w = sw.lock().unwrap();
w.append(&Message::user("hello"), None, None).unwrap();
w.append(&Message::assistant("hi"), None, None).unwrap();
w.append(&Message::tool_result("call_1", "result"), None, None)
.unwrap();
w.finish("done").unwrap();
}
let entries = SessionReader::load_transcript(&dir).unwrap();
assert_eq!(entries.len(), 3);
for (i, e) in entries.iter().enumerate() {
assert_eq!(
e.uuid.len(),
36,
"entry {i} uuid should be 36 chars: {:?}",
e.uuid
);
}
assert_eq!(
entries[0].parent_uuid, None,
"root must have no parent_uuid"
);
assert_eq!(
entries[1].parent_uuid.as_deref(),
Some(entries[0].uuid.as_str()),
"entry[1].parent_uuid should be entry[0].uuid"
);
assert_eq!(
entries[2].parent_uuid.as_deref(),
Some(entries[1].uuid.as_str()),
"entry[2].parent_uuid should be entry[1].uuid"
);
let uuids: Vec<&str> = entries.iter().map(|e| e.uuid.as_str()).collect();
let mut unique = uuids.clone();
unique.dedup();
assert_eq!(uuids.len(), unique.len(), "all UUIDs should be distinct");
}
#[test]
fn load_transcript_indexed_builds_uuid_index() {
let ws = IsolatedWorkspace::new();
let sw = make_writer(&ws);
let dir = session_dir(&sw);
{
let mut w = sw.lock().unwrap();
w.append(&Message::user("q"), None, None).unwrap();
w.append(&Message::assistant("a"), None, None).unwrap();
w.finish("done").unwrap();
}
let (entries, index) = SessionReader::load_transcript_indexed(&dir).unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(index.len(), 2);
for entry in &entries {
let looked_up = index.get(&entry.uuid).expect("uuid should be in index");
assert_eq!(looked_up.uuid, entry.uuid);
assert_eq!(looked_up.content, entry.content);
}
}
#[test]
fn old_jsonl_without_uuid_loads_without_error() {
let ws = IsolatedWorkspace::new();
let sw = make_writer(&ws);
let dir = {
let guard = sw.lock().unwrap();
guard.session_dir().to_path_buf()
};
drop(sw);
let jsonl_path = dir.join("transcript.jsonl");
let legacy = r#"{"id":"msg_001","role":"user","content":"old message","timestamp":"2024-01-01T00:00:00Z"}
{"id":"msg_002","parent_id":"msg_001","role":"assistant","content":"old reply","timestamp":"2024-01-01T00:00:01Z"}
"#;
std::fs::write(&jsonl_path, legacy).unwrap();
let entries = SessionReader::load_transcript(&dir).unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(
entries[0].uuid, "",
"missing uuid should default to empty string"
);
assert_eq!(entries[0].content, "old message");
assert_eq!(entries[1].content, "old reply");
}
#[test]
fn tool_result_source_uuid_points_to_assistant() {
let ws = IsolatedWorkspace::new();
let sw = make_writer(&ws);
let dir = session_dir(&sw);
{
let mut w = sw.lock().unwrap();
w.append(&Message::user("run it"), None, None).unwrap();
w.append(&Message::assistant("calling tool"), None, None)
.unwrap();
w.append(&Message::tool_result("call_1", "output"), None, None)
.unwrap();
w.finish("done").unwrap();
}
let entries = SessionReader::load_transcript(&dir).unwrap();
assert_eq!(entries.len(), 3);
let assistant_uuid = &entries[1].uuid;
let tool_result = &entries[2];
assert_eq!(
tool_result.source_tool_assistant_uuid.as_deref(),
Some(assistant_uuid.as_str()),
"tool result should reference the assistant that issued the call"
);
}
#[test]
fn open_existing_continues_uuid_chain() {
let ws = IsolatedWorkspace::new();
let dir;
{
let mut w = SessionWriter::create(ws.path(), "test", "gpt-4o", "openai").unwrap();
dir = w.session_dir().to_path_buf();
w.append(&Message::user("first"), None, None).unwrap();
w.append(&Message::assistant("answer"), None, None).unwrap();
}
let first_entries = SessionReader::load_transcript(&dir).unwrap();
let last_uuid_before = first_entries.last().unwrap().uuid.clone();
let mut w2 = SessionWriter::open_existing(&dir).unwrap();
w2.append(&Message::user("resumed"), None, None).unwrap();
w2.finish("done").unwrap();
drop(w2);
let all_entries = SessionReader::load_transcript(&dir).unwrap();
assert_eq!(all_entries.len(), 3);
assert_eq!(
all_entries[2].parent_uuid.as_deref(),
Some(last_uuid_before.as_str()),
"resumed message parent_uuid should continue from previous session"
);
}
#[test]
fn builder_stores_parent_agent_last_uuid() {
use recursive::llm::MockProvider;
use recursive::AgentRuntime;
use std::sync::Arc;
let llm = Arc::new(MockProvider::new(vec![]));
let builder = AgentRuntime::builder()
.llm(llm)
.parent_agent_last_uuid("abc-123-def");
assert_eq!(
builder.parent_agent_last_uuid.as_deref(),
Some("abc-123-def")
);
}