test(maestro): e2e for CLI broker path + registry compat fallback (NAP-244)
ci / rust (push) Successful in 10m42s

Drives the real `maestro` binary: one test points `MAESTRO_HOME_DAEMON_URL` at
a live daemon with no registry configured (proves the catalog read comes from
the home daemon), the other points it at a dead port and asserts the CLI warns
and falls back to the registry while still listing the session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 19:25:28 +00:00
co-authored by Claude Opus 4.8
parent 0df1d61f07
commit 643da30f63
+97
View File
@@ -694,6 +694,103 @@ async fn daemon_events_ws_streams_snapshot_then_session_added() {
client.kill_session(session.id).await.expect("kill");
}
// ---------------------------------------------------------------------------
// NAP-244 — CLI rewired through the home-daemon broker, with registry fallback
// ---------------------------------------------------------------------------
/// Run the real `maestro` binary with an isolated env and capture its output.
fn run_cli(args: &[&str], envs: &[(&str, &str)]) -> std::process::Output {
let bin = maestro_e2e::workspace_target_bin("maestro", "maestro");
let mut cmd = Command::new(&bin);
cmd.args(args);
cmd.env_clear().env(
"PATH",
std::env::var("PATH").unwrap_or_else(|_| "/usr/bin:/bin".into()),
);
for (k, v) in envs {
cmd.env(k, v);
}
cmd.output().expect("run maestro cli")
}
#[tokio::test(flavor = "multi_thread")]
async fn cli_list_reads_from_home_daemon() {
// Broker path: point the CLI at the daemon's local API and DON'T configure
// a registry. Success proves the catalog read came from the home daemon —
// the registry path would have errored with NoRegistryUrl.
let (_reg, daemons, client) = bring_up(&["alpha"]).await;
let cwd = TempDir::new().unwrap();
let session = client
.spawn_session(&create_req("alpha", cwd.path(), "sleep 30"))
.await
.expect("spawn");
let daemon_url = format!("http://{}", daemons[0].bind_addr);
let home = TempDir::new().unwrap();
let cfg_path = home.path().join("config.toml");
let out = run_cli(
&["list", "--json"],
&[
("HOME", home.path().to_str().unwrap()),
("MAESTRO_CONFIG", cfg_path.to_str().unwrap()),
("MAESTRO_HOME_DAEMON_URL", &daemon_url),
],
);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success(), "list failed: {stderr}\n{stdout}");
assert!(
stdout.contains(&session.id.to_string()),
"session not listed via home daemon: {stdout}"
);
assert!(
!stderr.contains("falling back"),
"unexpected registry fallback: {stderr}"
);
client.kill_session(session.id).await.ok();
}
#[tokio::test(flavor = "multi_thread")]
async fn cli_list_falls_back_to_registry_when_daemon_down() {
// Compat path: home daemon URL points at a dead port (connection refused).
// The CLI must warn and fall back to the configured registry, still
// returning the session.
let (reg, _daemons, client) = bring_up(&["alpha"]).await;
let cwd = TempDir::new().unwrap();
let session = client
.spawn_session(&create_req("alpha", cwd.path(), "sleep 30"))
.await
.expect("spawn");
let dead_daemon = format!("http://127.0.0.1:{}", maestro_e2e::util::free_port());
let registry_url = reg.http_base();
let home = TempDir::new().unwrap();
let cfg_path = home.path().join("config.toml");
let out = run_cli(
&["list", "--json"],
&[
("HOME", home.path().to_str().unwrap()),
("MAESTRO_CONFIG", cfg_path.to_str().unwrap()),
("MAESTRO_HOME_DAEMON_URL", &dead_daemon),
("MAESTRO_REGISTRY_URL", &registry_url),
],
);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success(), "list failed: {stderr}\n{stdout}");
assert!(
stdout.contains(&session.id.to_string()),
"session not listed via registry fallback: {stdout}"
);
assert!(
stderr.contains("falling back"),
"expected a fallback warning on stderr: {stderr}"
);
client.kill_session(session.id).await.ok();
}
async fn next_event<S>(ws: &mut S) -> maestro_protocol::RegistryEvent
where
S: StreamExt<Item = Result<WsMessage, tokio_tungstenite::tungstenite::Error>> + Unpin,