This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-cli/tests/against_mock_daemon.rs
T
Miguel PalhasandClaude Opus 5 30bd9d9042
ci / web (pull_request) Successful in 29s
ci / rust (pull_request) Successful in 3m17s
ci / web (push) Successful in 30s
ci / rust (push) Successful in 2m39s
test(maestro): one mock daemon instead of three answers
There were three answers to "how do I test against a daemon", and the CLI
— the thinnest client — had none, so its formatting and error handling
were exercised by compiling and forking the real binary plus tmux.

Adds crates/maestro-testing with a MockDaemon that serves the real /api/*
surface from maestro-protocol types, backed by a Vec<Session>. It replaces
two ad-hoc stubs: maestro-web's, which returned [{"id":"session-1"}] —
not a valid Session — and maestro-tui's scripted socket with a hardcoded
timeline.

maestro-cli gets its first tests against a daemon: six cases covering the
catalog, that DaemonInfo carries the version and presets its old private
copy dropped, that a structured failure surfaces the daemon's own message,
and that connection-refused stays distinguishable from an HTTP rejection.

maestro-web's proxy suite gains an end-to-end check that an upstream
rejection reaches the browser as a 4502 close carrying the daemon's
message — upstream_close_reason had only unit tests.

e2e keeps the tmux-shaped tests. cli_list_reads_from_daemon still runs the
shipped binary as a subprocess but now against MockDaemon, and
tui_integration.rs is deleted: the real daemon's /api/events contract is
covered by maestro-daemon/tests/local_api.rs and the client reducer by
maestro-tui/tests/catalog_ws.rs. The e2e suite drops from ~32s to ~20s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:51:22 +01:00

108 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())
.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).await.expect("kill");
assert_eq!(mock.session_count(), 0);
}