use choreo_daemon::{RequestContext, SessionCommand, db, session_main};
use choreo_proto::DaemonMessage;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::mpsc;
mod common;
const CHANNEL_CAPACITY: usize = 128;
fn spawn_session(
db: Arc<redb::Database>,
session_id: u64,
) -> (mpsc::Sender<SessionCommand>, std::thread::JoinHandle<()>) {
let (daemon_tx, _daemon_rx) = mpsc::channel();
let (session_tx, session_rx) = mpsc::channel();
let tool_registry = choreo_daemon::tools::ToolRegistry::new().build();
let cmd_tx = session_tx.clone();
let handle = std::thread::spawn(move || {
session_main(
session_rx,
None,
None,
None,
RequestContext {
cmd_tx,
session_id,
db,
tool_registry,
daemon_tx,
max_turns: 0,
},
);
});
(session_tx, handle)
}
#[ignore]
#[test]
fn session_starts_and_accepts_commands() {
let db = Arc::new(common::test_db());
let (session_tx, _handle) = spawn_session(db, 1);
let (writer_tx, writer_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
let client_id = 42;
session_tx
.send(SessionCommand::Attach {
client_id,
tx: writer_tx,
})
.unwrap();
let msg = writer_rx.recv().unwrap();
assert!(matches!(msg, DaemonMessage::SessionState { .. }));
session_tx
.send(SessionCommand::SetModel {
model: "gpt-4".to_string(),
})
.unwrap();
let (reply_tx, reply_rx) = mpsc::channel();
session_tx
.send(SessionCommand::GetSummary { reply: reply_tx })
.unwrap();
let summary = reply_rx.recv().unwrap();
assert_eq!(summary.session_id, 1);
session_tx
.send(SessionCommand::Detach { client_id })
.unwrap();
drop(session_tx);
drop(writer_rx);
}
#[ignore]
#[test]
fn session_shutdown_exits_without_active_requests() {
let db = Arc::new(common::test_db());
let (session_tx, handle) = spawn_session(db, 1);
session_tx.send(SessionCommand::Shutdown).unwrap();
drop(session_tx);
handle.join().unwrap();
}
#[ignore]
#[test]
fn session_cancel_nonexistent_request_does_not_panic() {
let db = Arc::new(common::test_db());
let (session_tx, handle) = spawn_session(db, 1);
session_tx
.send(SessionCommand::Cancel { request_id: 999 })
.unwrap();
let (writer_tx, _writer_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
session_tx
.send(SessionCommand::Attach {
client_id: 10,
tx: writer_tx,
})
.unwrap();
session_tx.send(SessionCommand::Shutdown).unwrap();
drop(session_tx);
handle.join().unwrap();
}
#[ignore]
#[test]
fn session_config_tools_mutate_authoritative_state_and_persist() {
let db = Arc::new(common::test_db());
let (session_tx, handle) = spawn_session(db.clone(), 1);
let (wd_reply_tx, wd_reply_rx) = mpsc::channel();
session_tx
.send(SessionCommand::SetWorkingDir {
path: PathBuf::from("/tmp/new-wd"),
reply: wd_reply_tx,
})
.unwrap();
match wd_reply_rx.recv().unwrap() {
Ok(path) => assert_eq!(path, "/tmp/new-wd"),
Err(e) => panic!("set_working_dir rejected: {e}"),
}
let (lt_reply_tx, lt_reply_rx) = mpsc::channel();
session_tx
.send(SessionCommand::LoadTools {
groups: vec!["x".into()],
reply: lt_reply_tx,
})
.unwrap();
match lt_reply_rx.recv().unwrap() {
Ok(msg) => assert!(msg.contains("x"), "unexpected summary: {msg}"),
Err(e) => panic!("load_tools rejected: {e}"),
}
let (summary_tx, summary_rx) = mpsc::channel();
session_tx
.send(SessionCommand::GetSummary { reply: summary_tx })
.unwrap();
let summary = summary_rx.recv().unwrap();
assert_eq!(summary.working_dir.as_deref(), Some("/tmp/new-wd"));
assert!(summary.active_tool_groups.contains(&"x".to_string()));
session_tx.send(SessionCommand::Shutdown).unwrap();
drop(session_tx);
handle.join().unwrap();
let record = db::read_session(&db, 1).unwrap().expect("session record");
assert_eq!(record.working_dir.as_deref(), Some("/tmp/new-wd"));
assert!(record.active_tool_groups.contains(&"x".to_string()));
}