use zero_engine_client::HttpClient;
use zero_testkit::mock_engine::MockEngine;
#[tokio::test]
async fn root_decodes_version_probe() {
let mock = MockEngine::spawn().await.expect("spawn mock");
let client = HttpClient::new(mock.base_url(), None).expect("client");
let root = client.root().await.expect("root");
assert_eq!(root.name, "ZERO OS");
assert_eq!(root.version, "1.2.3-mock");
assert_eq!(root.status, "running");
mock.shutdown().await;
}
#[tokio::test]
async fn health_decodes_components() {
let mock = MockEngine::spawn().await.expect("spawn mock");
let client = HttpClient::new(mock.base_url(), None).expect("client");
let health = client.health().await.expect("health");
assert!(health.is_ok());
let counts = health.component_counts();
assert_eq!(counts.healthy, 2);
assert_eq!(counts.dead, 0);
mock.shutdown().await;
}
#[tokio::test]
async fn health_degraded_surfaces() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.degrade_health = true);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let health = client.health().await.expect("health");
assert!(!health.is_ok());
mock.shutdown().await;
}
#[tokio::test]
async fn live_preflight_decodes_readiness_gate() {
let mock = MockEngine::spawn().await.expect("spawn mock");
let client = HttpClient::new(mock.base_url(), None).expect("client");
let preflight = client.live_preflight().await.expect("live preflight");
assert_eq!(preflight.schema_version, "zero.live_preflight.v1");
assert_eq!(preflight.exchange, "hyperliquid");
assert!(!preflight.ready);
assert!(preflight.controls_ready);
assert!(
preflight
.checks
.iter()
.any(|check| check.name == "live_executor" && check.status == "fail")
);
mock.shutdown().await;
}
#[tokio::test]
async fn unreachable_host_is_typed() {
let client = HttpClient::new("http://127.0.0.1:1", None).expect("client");
let err = client.root().await.expect_err("should fail");
assert!(
matches!(
err,
zero_engine_client::HttpError::Unreachable(_)
| zero_engine_client::HttpError::Timeout(_)
),
"unexpected error variant: {err:?}",
);
}
#[tokio::test]
async fn retry_recovers_after_one_transient_failure() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.transient_fail_count = 1);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let status = client.v2_status().await.expect("retry should recover");
assert_eq!(status.regime(), Some("TREND_LONG confirmed across majors."));
mock.shutdown().await;
}
#[tokio::test]
async fn retry_gives_up_after_second_transient_failure() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.transient_fail_count = 2);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let err = client.v2_status().await.expect_err("both attempts fail");
match err {
zero_engine_client::HttpError::Status { status, .. } => {
assert_eq!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE);
}
other => panic!("expected Status(503), got {other:?}"),
}
mock.shutdown().await;
}
#[tokio::test]
async fn unauthorized_status_maps_to_typed_error() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.force_unauthorized = true);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let err = client.positions().await.expect_err("401");
assert!(
matches!(err, zero_engine_client::HttpError::Unauthorized),
"expected Unauthorized, got {err:?}",
);
mock.shutdown().await;
}
#[tokio::test]
async fn not_found_maps_to_typed_error_with_path() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.force_not_found = true);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let err = client.risk().await.expect_err("404");
match err {
zero_engine_client::HttpError::NotFound { path } => {
assert_eq!(path, "/risk", "path must be preserved for diagnostics");
}
other => panic!("expected NotFound, got {other:?}"),
}
mock.shutdown().await;
}
#[tokio::test]
async fn non_retryable_500_fails_without_second_attempt() {
let mock = MockEngine::spawn().await.expect("spawn mock");
mock.with_overrides(|o| o.force_server_error = true);
let client = HttpClient::new(mock.base_url(), None).expect("client");
let err = client.regime(None).await.expect_err("500");
match err {
zero_engine_client::HttpError::Status { status, .. } => {
assert_eq!(status, reqwest::StatusCode::INTERNAL_SERVER_ERROR);
}
other => panic!("expected Status(500), got {other:?}"),
}
mock.shutdown().await;
}