use fabro_test::{fabro_snapshot, json_elapsed_ms_snapshot_filters, test_context}; use httpmock::MockServer; use serde_json::json; use super::support::{ remote_run_summary_json, setup_seeded_completed_dry_run, setup_seeded_created_dry_run, }; use crate::support::{run_projection_json, unique_run_id}; fn remote_run_summary(run_id: &str, status: &serde_json::Value) -> serde_json::Value { remote_run_summary_json( run_id, "Blocked Remote Workflow", "blocked-remote-workflow", "Wait for approval", status, "2026-04-19T12:00:00Z", ) } #[test] fn help() { let context = test_context!(); let mut cmd = context.command(); cmd.args(["wait", "--help"]); fabro_snapshot!(context.filters(), cmd, @" success: true exit_code: 0 ----- stdout ----- Block until a workflow run completes Usage: fabro wait [OPTIONS] Arguments: Run ID prefix or workflow name (most recent run) Options: --json Output as JSON [env: FABRO_JSON=] --server Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=] --debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=] --timeout Maximum time to wait in seconds --interval Poll interval in milliseconds [default: 1000] --no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true] --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] -h, --help Print help ----- stderr ----- "); } #[test] fn wait_completed_run_prints_success_summary() { let context = test_context!(); let run = setup_seeded_completed_dry_run(&context); let mut filters = context.filters(); filters.push(( r"\b\d+(\.\d+)?(ms|s)\b".to_string(), "[DURATION]".to_string(), )); let mut cmd = context.command(); cmd.args(["wait", &run.run_id]); fabro_snapshot!(filters, cmd, @" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Succeeded [ULID] [DURATION] "); } #[test] fn wait_completed_run_reads_store_without_status_or_conclusion_files() { let context = test_context!(); let run = setup_seeded_completed_dry_run(&context); let mut filters = context.filters(); filters.push(( r"\b\d+(\.\d+)?(ms|s)\b".to_string(), "[DURATION]".to_string(), )); let mut cmd = context.command(); cmd.args(["wait", &run.run_id]); fabro_snapshot!(filters, cmd, @" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Succeeded [ULID] [DURATION] "); } #[test] fn wait_completed_run_json_outputs_status_and_duration() { let context = test_context!(); let run = setup_seeded_completed_dry_run(&context); let filters = json_elapsed_ms_snapshot_filters(context.filters()); let mut cmd = context.command(); cmd.args(["wait", "--json", &run.run_id]); fabro_snapshot!(filters, cmd, @r###" success: true exit_code: 0 ----- stdout ----- { "run_id": "[ULID]", "status": "succeeded", "timing": { "wall_time_ms": "[WALL_TIME_MS]", "inference_time_ms": "[INFERENCE_TIME_MS]", "tool_time_ms": "[TOOL_TIME_MS]", "active_time_ms": "[ACTIVE_TIME_MS]" } } ----- stderr ----- "###); } #[test] fn wait_submitted_run_times_out() { let context = test_context!(); let run = setup_seeded_created_dry_run(&context); let mut cmd = context.command(); cmd.args(["wait", "--timeout", "0", "--interval", "10", &run.run_id]); fabro_snapshot!(context.filters(), cmd, @" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × Timed out after 0s waiting for run '[ULID]' "); } #[test] fn wait_blocked_run_times_out_without_treating_it_as_terminal() { let context = test_context!(); let run_id = unique_run_id(); let server = MockServer::start(); let summary = remote_run_summary( run_id.as_str(), &json!({ "kind": "blocked", "blocked_reason": "human_input_required" }), ); let resolve_run = server.mock(|when, then| { when.method("GET") .path("/api/v1/runs/resolve") .query_param("selector", run_id.as_str()); then.status(200) .header("content-type", "application/json") .body(summary.clone().to_string()); }); let retrieve_run = server.mock(|when, then| { when.method("GET") .path(format!("/api/v1/runs/{}", run_id.as_str())); then.status(200) .header("content-type", "application/json") .body(summary.to_string()); }); let run_state = server.mock(|when, then| { when.method("GET") .path(format!("/api/v1/runs/{}/state", run_id.as_str())); then.status(200) .header("content-type", "application/json") .body( run_projection_json( run_id.as_str(), &json!({ "kind": "blocked", "blocked_reason": "human_input_required" }), ) .to_string(), ); }); let mut cmd = context.command(); cmd.args([ "wait", "--server", &format!("{}/api/v1", server.base_url()), "--timeout", "0", "--interval", "10", run_id.as_str(), ]); fabro_snapshot!(context.filters(), cmd, @" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × Timed out after 0s waiting for run '[ULID]' "); resolve_run.assert(); assert!( retrieve_run.calls() > 0, "wait should keep polling the blocked run summary until timeout" ); assert_eq!( run_state.calls(), 0, "wait should not fetch run state when the run never becomes terminal" ); }