use std::future::IntoFuture;
use std::process::Command;
use tempfile::TempDir;
use trusty_agents_common::connectors::{
AgentSpec, BackendParams, ConnectorTestKit, CreateSessionReq,
};
use super::*;
async fn spawn_test_daemon() -> String {
use crate::daemon::{api, state::DaemonState};
let root = tempfile::tempdir().unwrap().keep();
let state = std::sync::Arc::new(DaemonState::with_root_isolated_managed(root).await);
let router = api::router(std::sync::Arc::clone(&state));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(
axum::serve(
listener,
router.into_make_service_with_connect_info::<std::net::SocketAddr>(),
)
.into_future(),
);
format!("http://{addr}")
}
fn local_bare_repo() -> (TempDir, String) {
let scratch = TempDir::new().expect("scratch tempdir");
let bare = scratch.path().join("origin.git");
let work = scratch.path().join("seed");
assert!(
Command::new("git")
.args(["init", "--bare", "-b", "main"])
.arg(&bare)
.status()
.map(|s| s.success())
.unwrap_or(false),
"git init --bare must succeed"
);
assert!(
Command::new("git")
.args(["clone"])
.arg(&bare)
.arg(&work)
.status()
.map(|s| s.success())
.unwrap_or(false),
"git clone (seed checkout) must succeed"
);
std::fs::write(work.join("README.md"), "seed").expect("write seed file");
for args in [
vec!["-C", work.to_str().unwrap(), "add", "."],
vec![
"-C",
work.to_str().unwrap(),
"-c",
"user.email=connector-test@example.com",
"-c",
"user.name=connector-test",
"commit",
"-m",
"seed",
],
vec!["-C", work.to_str().unwrap(), "push", "origin", "main"],
] {
assert!(
Command::new("git")
.args(&args)
.status()
.map(|s| s.success())
.unwrap_or(false),
"git {args:?} must succeed"
);
}
let url = format!("file://{}", bare.display());
(scratch, url)
}
#[tokio::test]
async fn create_session_wrong_backend_params_is_invalid_request() {
let connector = TmConnector::with_daemon_url("http://127.0.0.1:0");
let req = CreateSessionReq {
task: "irrelevant".into(),
name_hint: None,
agent: None,
backend: BackendParams::Tcode {
project: std::path::PathBuf::from("/tmp/proj"),
},
};
let err = connector.create_session(req).await.unwrap_err();
assert!(
matches!(err, ConnectorError::InvalidRequest(_)),
"expected InvalidRequest, got {err:?}"
);
}
#[tokio::test]
async fn list_sessions_empty_fleet_returns_empty_vec() {
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
let sessions = connector.list_sessions().await.expect("list_sessions");
assert!(sessions.is_empty(), "fresh daemon must have no sessions");
}
#[tokio::test]
async fn session_status_unknown_id_is_not_found() {
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
ConnectorTestKit::assert_status_not_found_for_unknown_id(
&connector,
"00000000-0000-0000-0000-000000000000",
)
.await;
}
#[tokio::test]
async fn send_input_unknown_id_is_not_found() {
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
ConnectorTestKit::assert_send_not_found_for_unknown_id(
&connector,
"00000000-0000-0000-0000-000000000000",
)
.await;
}
#[tokio::test]
async fn attach_unknown_id_is_not_found() {
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
let err = connector
.attach("00000000-0000-0000-0000-000000000000")
.await
.unwrap_err();
assert!(err.is_not_found(), "expected NotFound, got {err:?}");
}
#[tokio::test]
async fn delegate_unknown_session_is_backend_error() {
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
let spec = AgentSpec {
agent_name: "research".into(),
task: "find the bug".into(),
tier: None,
};
let err = connector
.delegate("00000000-0000-0000-0000-000000000000", &spec)
.await
.unwrap_err();
assert!(
matches!(err, ConnectorError::Backend(_)),
"expected Backend error for an unknown session, got {err:?}"
);
}
#[tokio::test]
async fn create_session_full_lifecycle() {
let (_scratch, repo_url) = local_bare_repo();
let url = spawn_test_daemon().await;
let connector = TmConnector::with_daemon_url(url);
let req = CreateSessionReq {
task: "list files".into(),
name_hint: None,
agent: None,
backend: BackendParams::Tm {
repo_url,
git_ref: "main".into(),
runtime: None,
ephemeral: true,
},
};
let info = ConnectorTestKit::assert_basic_lifecycle(&connector, req, "echo hello").await;
assert_eq!(
info.task.as_deref(),
Some("list files"),
"the kit's lifecycle assertion doesn't check task content — do it here"
);
let attach = connector.attach(&info.id).await.expect("attach");
match attach {
AttachHandle::ShellCommand(cmd) => {
assert!(
cmd.contains("tmux attach"),
"tm attach handle must be a tmux attach command, got {cmd:?}"
);
}
other => panic!("tm connector must return ShellCommand, got {other:?}"),
}
let spec = AgentSpec {
agent_name: "research".into(),
task: "find the bug".into(),
tier: Some("haiku".into()),
};
let handle = connector
.delegate(&info.id, &spec)
.await
.expect("delegate must succeed against a live session");
assert!(
!handle.delegate_id.is_empty(),
"delegate must return a non-empty delegate_id"
);
}