hen 0.20.2

Run protocol-aware API request collections from the command line or through MCP.
Documentation
#[cfg(unix)]
use std::sync::mpsc;

use serde_json::Value;

use crate::{
    helpers::{run_until_signal, spawn_blocking_http_server, spawn_http_server},
    support::TestWorkspace,
};

#[cfg(unix)]
#[test]
fn run_outputs_partial_text_summary_when_interrupted_by_sigint() {
    let fast_server_url = spawn_http_server(200, "OK", "application/json", r#"{"step":1}"#);
    let (started_tx, started_rx) = mpsc::channel();
    let slow_server_url = spawn_blocking_http_server(started_tx);
    let workspace = TestWorkspace::new();
    workspace.write_file(
        "collection.hen",
        &format!(
            r#"Interrupt Fixture

Exercises partial text summaries on SIGINT.

---

First request

GET {fast_server_url}

---

Second request

GET {slow_server_url}
"#
        ),
    );

    let output = run_until_signal(
        &workspace,
        &["run", "collection.hen", "all", "--parallel"],
        "-INT",
        started_rx,
    );
    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf-8");
    let stderr = String::from_utf8(output.stderr).expect("stderr should be utf-8");

    assert_eq!(output.status.code(), Some(130), "stdout: {stdout}\nstderr: {stderr}");
    assert!(stdout.contains("[ok] #0"), "stdout: {stdout}");
    assert!(stdout.contains("Summary"), "stdout: {stdout}");
    assert!(stdout.contains("interrupted by SIGINT"), "stdout: {stdout}");
    assert!(!stdout.contains("[ok] #1"), "stdout: {stdout}");
    assert!(stderr.contains("Execution interrupted by SIGINT"), "stderr: {stderr}");
}

#[cfg(unix)]
#[test]
fn run_outputs_partial_json_report_when_interrupted_by_sigterm() {
    let fast_server_url = spawn_http_server(200, "OK", "application/json", r#"{"step":1}"#);
    let (started_tx, started_rx) = mpsc::channel();
    let slow_server_url = spawn_blocking_http_server(started_tx);
    let workspace = TestWorkspace::new();
    workspace.write_file(
        "collection.hen",
        &format!(
            r#"Interrupt JSON Fixture

Exercises partial machine-readable summaries on SIGTERM.

---

First request

GET {fast_server_url}

---

Second request

GET {slow_server_url}
"#
        ),
    );

    let output = run_until_signal(
        &workspace,
        &["run", "collection.hen", "all", "--parallel", "--output", "json"],
        "-TERM",
        started_rx,
    );
    let stderr = String::from_utf8(output.stderr).expect("stderr should be utf-8");

    assert_eq!(output.status.code(), Some(143), "stderr: {stderr}");
    assert!(stderr.is_empty(), "stderr: {stderr}");

    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf-8");
    let parsed: Value = serde_json::from_str(&stdout).expect("stdout should be valid json");
    assert_eq!(parsed["executionFailed"], true);
    assert_eq!(parsed["interrupted"], true);
    assert_eq!(parsed["interruptSignal"], "SIGTERM");
    assert_eq!(parsed["records"].as_array().map(Vec::len), Some(1));
    assert_eq!(parsed["records"][0]["status"], 200);
    assert_eq!(parsed["records"][0]["url"], fast_server_url);
    assert_eq!(parsed["failures"], serde_json::json!([]));
}