use pawan::agent::{Message, Role, ToolCallRequest, ToolResultMessage};
use pawan::agent::session::{Session, RetentionPolicy, search_sessions, prune_sessions};
use pawan::{Result, PawanError};
use std::thread;
use std::time::Duration;
use tempfile::tempdir;
use serial_test::serial;
#[test]
#[serial]
fn roundtrip_save_load() -> Result<()> {
let prev_home = std::env::var("HOME").ok();
let tmp = tempdir()?;
std::env::set_var("HOME", tmp.path());
let mut sess = Session::new("gpt-4");
sess.add_tag("test").ok(); sess.messages.push(Message {
role: Role::User,
content: "Hello".into(),
tool_calls: vec![],
tool_result: None,
});
let path = sess.save()?;
let loaded = Session::load(&sess.id)?;
assert_eq!(sess.id, loaded.id);
assert_eq!(sess.model, loaded.model);
assert_eq!(sess.messages, loaded.messages);
std::fs::remove_file(path).ok();
if let Some(h) = prev_home {
std::env::set_var("HOME", h);
} else {
std::env::remove_var("HOME");
}
Ok(())
}
#[test]
#[serial]
fn autosave_updates_same_file() -> Result<()> {
let prev_home = std::env::var("HOME").ok();
let tmp = tempdir()?;
std::env::set_var("HOME", tmp.path());
let mut sess = Session::new("gpt-4");
let path1 = sess.save()?;
sess.messages.push(Message {
role: Role::User,
content: "Second".into(),
tool_calls: vec![],
tool_result: None,
});
let path2 = sess.save()?;
assert_eq!(path1.file_name(), path2.file_name());
let entries = std::fs::read_dir(Session::sessions_dir()?)?.count();
assert_eq!(entries, 1);
std::fs::remove_file(path1).ok();
if let Some(h) = prev_home {
std::env::set_var("HOME", h);
} else {
std::env::remove_var("HOME");
}
Ok(())
}
#[test]
fn export_import_json_preserves_data() -> Result<()> {
let mut sess = Session::new("gpt-4");
sess.tags.push("alpha".into());
sess.messages.push(Message {
role: Role::User,
content: "Export test".into(),
tool_calls: vec![],
tool_result: None,
});
let json = serde_json::to_string(&sess).unwrap();
let dir = tempdir()?;
let file_path = dir.path().join("export.json");
std::fs::write(&file_path, json)?;
let imported = Session::from_json_file(&file_path)?;
assert_eq!(imported.model, sess.model);
assert_eq!(imported.tags, sess.tags);
assert_eq!(imported.messages, sess.messages);
Ok(())
}
#[test]
#[serial]
fn tool_call_preservation_roundtrip() -> Result<()> {
let prev_home = std::env::var("HOME").ok();
let tmp = tempdir()?;
std::env::set_var("HOME", tmp.path());
let mut sess = Session::new("gpt-4");
let tool_call = ToolCallRequest {
id: "call1".into(),
name: "bash".into(),
arguments: serde_json::json!({"cmd": "echo hi"}),
};
let tool_res = ToolResultMessage {
tool_call_id: tool_call.id.clone(),
content: serde_json::json!({"output": "hi"}),
success: true,
};
sess.messages.push(Message {
role: Role::Tool,
content: "".into(),
tool_calls: vec![tool_call.clone()],
tool_result: Some(tool_res.clone()),
});
let path = sess.save()?;
let loaded = Session::load(&sess.id)?;
assert_eq!(loaded.messages.len(), sess.messages.len());
let loaded_msg = &loaded.messages[0];
let orig_msg = &sess.messages[0];
assert_eq!(loaded_msg.role, orig_msg.role);
assert_eq!(loaded_msg.tool_calls.len(), orig_msg.tool_calls.len());
assert_eq!(loaded_msg.tool_calls[0].id, orig_msg.tool_calls[0].id);
assert_eq!(loaded_msg.tool_calls[0].name, orig_msg.tool_calls[0].name);
assert_eq!(loaded_msg.tool_calls[0].arguments, orig_msg.tool_calls[0].arguments);
assert_eq!(loaded_msg.tool_result.as_ref().unwrap().tool_call_id, orig_msg.tool_result.as_ref().unwrap().tool_call_id);
assert_eq!(loaded_msg.tool_result.as_ref().unwrap().content, orig_msg.tool_result.as_ref().unwrap().content);
assert_eq!(loaded_msg.tool_result.as_ref().unwrap().success, orig_msg.tool_result.as_ref().unwrap().success);
std::fs::remove_file(path).ok();
if let Some(h) = prev_home {
std::env::set_var("HOME", h);
} else {
std::env::remove_var("HOME");
}
Ok(())
}
#[test]
#[ignore]
fn timeout_enforcement_triggers_error() {
assert!(true);
}
#[test]
#[serial]
fn search_sessions_multiple_results() -> Result<()> {
let prev_home = std::env::var("HOME").ok();
let tmp = tempdir()?;
std::env::set_var("HOME", tmp.path());
let mut s1 = Session::new("m1");
s1.messages.push(Message { role: Role::User, content: "unique term alpha".into(), tool_calls: vec![], tool_result: None });
s1.save()?;
let mut s2 = Session::new("m2");
s2.messages.push(Message { role: Role::User, content: "beta content".into(), tool_calls: vec![], tool_result: None });
s2.save()?;
let results = search_sessions("alpha").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, s1.id);
if let Some(h) = prev_home {
std::env::set_var("HOME", h);
} else {
std::env::remove_var("HOME");
}
Ok(())
}
#[test]
#[serial]
fn prune_sessions_respects_policy() -> Result<()> {
let prev_home = std::env::var("HOME").ok();
let tmp = tempdir()?;
std::env::set_var("HOME", tmp.path());
let dir = Session::sessions_dir()?;
for i in 0..3 {
let mut s = Session::new("m");
s.id = format!("sess{}", i);
s.updated_at = format!("2020-01-0{}T00:00:00Z", i+1);
let path = dir.join(format!("{}.json", s.id));
std::fs::write(&path, serde_json::to_string_pretty(&s).map_err(|e| PawanError::Config(format!("JSON serialize error: {}", e)))?)?;
}
let policy = RetentionPolicy { max_age_days: None, max_sessions: Some(1), keep_tags: vec![] };
let deleted = prune_sessions(&policy)?;
assert_eq!(deleted, 2);
let list = Session::list()?;
assert_eq!(list.len(), 1);
if let Some(h) = prev_home {
std::env::set_var("HOME", h);
} else {
std::env::remove_var("HOME");
}
Ok(())
}