The flag overrides the kill refusal (#81): the session ends, the guarded worktree is retained, and the CLI prints where the work went — path, branch, force-killed — instead of leaving the operator to wonder. WORKTREES.md: write down that a forced kill of a tree whose guard is clear removes it as usual — force changes what happens to blocked work, not ordinary cleanup.
111 lines
3.1 KiB
Rust
111 lines
3.1 KiB
Rust
//! The CLI against a daemon. It had no such tests: `maestro list`'s formatting
|
|
//! was unit-tested, but nothing exercised the client, the error decoding, or
|
|
//! the unreachable-daemon path without compiling and forking a real daemon.
|
|
|
|
use maestro_protocol::{DaemonClient, DaemonInfo};
|
|
use maestro_testing::{session, MockDaemon};
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn list_reads_the_daemon_catalog() {
|
|
let mock = MockDaemon::start().await;
|
|
mock.add_session(session(Uuid::from_u128(1), "/home/mock/work", "claude"));
|
|
|
|
let sessions = mock.client().list_sessions().await.expect("list");
|
|
|
|
assert_eq!(sessions.len(), 1);
|
|
assert_eq!(sessions[0].command, "claude");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn info_carries_version_and_presets() {
|
|
let mock = MockDaemon::start().await;
|
|
mock.set_info(DaemonInfo {
|
|
hostname: "konishi".into(),
|
|
version: "9.9.9".into(),
|
|
home_dir: "/home/naps".into(),
|
|
presets: vec![maestro_protocol::SpawnPreset {
|
|
id: "claude".into(),
|
|
label: "Claude".into(),
|
|
command: "claude".into(),
|
|
}],
|
|
});
|
|
|
|
let info = mock.client().daemon_info().await.expect("info");
|
|
|
|
// The CLI's own DaemonInfo used to drop both of these fields.
|
|
assert_eq!(info.version, "9.9.9");
|
|
assert_eq!(info.presets.len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_structured_failure_surfaces_the_daemon_message() {
|
|
let mock = MockDaemon::start().await;
|
|
mock.force_error(502, "tmux_failed", "no server running");
|
|
|
|
let err = mock
|
|
.client()
|
|
.list_sessions()
|
|
.await
|
|
.expect_err("should fail");
|
|
|
|
assert_eq!(
|
|
err.daemon_message(),
|
|
Some("no server running"),
|
|
"the daemon's own message must survive: {err}"
|
|
);
|
|
assert!(!err.is_unreachable());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn an_unreachable_daemon_is_distinguishable_from_a_rejection() {
|
|
// Port 1 has nothing listening on it.
|
|
let client = DaemonClient::new("http://127.0.0.1:1");
|
|
|
|
let err = client.list_sessions().await.expect_err("should fail");
|
|
|
|
assert!(
|
|
err.is_unreachable(),
|
|
"connection refused must not read as an HTTP rejection: {err}"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn killing_an_unknown_session_reports_the_daemon_reason() {
|
|
let mock = MockDaemon::start().await;
|
|
|
|
let err = mock
|
|
.client()
|
|
.kill_session(Uuid::new_v4(), false)
|
|
.await
|
|
.expect_err("should fail");
|
|
|
|
assert_eq!(err.daemon_message(), Some("no such session on this daemon"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn spawn_round_trips_through_the_daemon() {
|
|
let mock = MockDaemon::start().await;
|
|
|
|
let created = mock
|
|
.client()
|
|
.create_session(&maestro_protocol::CreateSessionRequest {
|
|
cwd: "/home/mock".into(),
|
|
command: "bash".into(),
|
|
env: Default::default(),
|
|
metadata: Default::default(),
|
|
worktree: None,
|
|
})
|
|
.await
|
|
.expect("create");
|
|
|
|
assert_eq!(created.command, "bash");
|
|
assert_eq!(mock.session_count(), 1);
|
|
|
|
mock.client()
|
|
.kill_session(created.id, false)
|
|
.await
|
|
.expect("kill");
|
|
assert_eq!(mock.session_count(), 0);
|
|
}
|