use zero_commands::{DispatchContext, OutputLine, RiskDirection, dispatch};
use zero_engine_client::{EngineState, HttpClient};
use zero_testkit::mock_engine::MockEngine;
async fn ctx_with_mock() -> (MockEngine, DispatchContext) {
let mock = MockEngine::spawn().await.expect("spawn mock");
let client = HttpClient::new(mock.base_url(), None).expect("client");
let ctx = DispatchContext::new(Some(client), EngineState::shared());
(mock, ctx)
}
fn first_line(out: &zero_commands::DispatchOutput) -> (&'static str, String) {
let line = out.lines.first().expect("dispatcher produced no lines");
match line {
OutputLine::System(s) => ("system", s.clone()),
OutputLine::Command(s) => ("command", s.clone()),
OutputLine::Warn(s) => ("warn", s.clone()),
OutputLine::Alert(s) => ("alert", s.clone()),
}
}
#[tokio::test]
async fn status_http_500_alerts_not_panics() {
let (mock, ctx) = ctx_with_mock().await;
mock.with_overrides(|o| o.force_server_error = true);
let out = dispatch(&ctx, "/status")
.await
.expect("dispatch returned error")
.expect("dispatch returned None");
assert_eq!(
out.risk,
Some(RiskDirection::Neutral),
"a failed read must still classify its risk direction"
);
let (kind, body) = first_line(&out);
assert_eq!(
kind, "alert",
"500 on /status must surface as Alert: {body}"
);
assert!(
body.to_lowercase().contains("status"),
"alert body must identify the failing surface: {body}"
);
mock.shutdown().await;
}
#[tokio::test]
async fn status_against_dead_host_alerts() {
let client = HttpClient::new("http://127.0.0.1:1", None).expect("client");
let ctx = DispatchContext::new(Some(client), EngineState::shared());
let out = dispatch(&ctx, "/status")
.await
.expect("dispatch returned error")
.expect("dispatch returned None");
let (kind, body) = first_line(&out);
assert_eq!(
kind, "alert",
"unreachable engine on /status must surface as Alert: {body}"
);
assert!(
body.to_lowercase().contains("status"),
"alert body must identify the failing surface: {body}"
);
}
#[tokio::test]
async fn status_401_alerts_without_leaking_token() {
let mock = MockEngine::spawn().await.expect("spawn mock");
let sentinel = "zk_sentinel_token_value_1234567890";
let client =
HttpClient::new(mock.base_url(), Some(sentinel.into())).expect("client with token");
let ctx = DispatchContext::new(Some(client), EngineState::shared());
mock.with_overrides(|o| o.force_unauthorized = true);
let out = dispatch(&ctx, "/status")
.await
.expect("dispatch returned error")
.expect("dispatch returned None");
let (kind, body) = first_line(&out);
assert_eq!(
kind, "alert",
"401 on /status must surface as Alert: {body}"
);
assert!(
!body.contains(sentinel),
"alert body must NOT echo the bearer token. got: {body}"
);
assert!(
!body.to_lowercase().contains("bearer "),
"alert body must NOT mention the Authorization scheme. got: {body}"
);
mock.shutdown().await;
}
#[tokio::test]
async fn status_transient_503_recovers_via_retry() {
let (mock, ctx) = ctx_with_mock().await;
mock.with_overrides(|o| o.transient_fail_count = 1);
let out = dispatch(&ctx, "/status")
.await
.expect("dispatch returned error")
.expect("dispatch returned None");
let (kind, body) = first_line(&out);
assert_eq!(
kind, "command",
"single 503 should be hidden by the retry budget and surface as Command: {body}"
);
mock.shutdown().await;
}
#[test]
fn ws_drop_and_reconnect_is_exercised_elsewhere() {
const CANONICAL_TEST: &str =
"zero-engine-client::tests::ws_integration::reconnects_after_peer_drop";
assert!(CANONICAL_TEST.contains("reconnects_after_peer_drop"));
}
#[test]
fn stale_stat_is_exercised_in_widget_layer() {
const CANONICAL_TEST: &str =
"zero-tui::widgets::statusbar::tests::stale_snapshot_gets_asterisk";
assert!(CANONICAL_TEST.contains("stale_snapshot_gets_asterisk"));
}