hen 0.20.2

Run protocol-aware API request collections from the command line or through MCP.
Documentation
use serde_json::Value;

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

#[test]
fn run_outputs_ndjson_report() {
    let server_url = spawn_http_server(
        200,
        "OK",
        "application/json",
        r#"{"ok":true,"service":"hen"}"#,
    );
    let workspace = TestWorkspace::new();
    workspace.write_file(
        "collection.hen",
        &format!(
            r#"NDJSON Fixture

Exercises streaming machine-readable output.

---

Fetch fixture

GET {server_url}

^ & body.ok == true
[ true == false ] ^ & body.service == 'hen'
"#
        ),
    );

    let output = workspace.run_hen(["run", "collection.hen", "--output", "ndjson"]);

    assert_eq!(output.status_code, 0, "stderr: {}", output.stderr);
    assert!(output.stderr.is_empty(), "stderr: {}", output.stderr);

    let lines = output
        .stdout
        .lines()
        .map(|line| serde_json::from_str::<Value>(line).expect("line should be valid json"))
        .collect::<Vec<_>>();

    assert_eq!(lines.len(), 4);
    assert_eq!(lines[0]["type"], "run");
    assert_eq!(lines[0]["interrupted"], false);
    assert_eq!(lines[0]["interruptSignal"], Value::Null);
    assert_eq!(lines[0]["traceCount"], 2);
    assert_eq!(lines[1]["type"], "record");
    assert_eq!(lines[1]["status"], 200);
    assert_eq!(lines[1]["assertions"][0]["status"], "passed");
    assert_eq!(lines[1]["assertions"][1]["status"], "skipped");
    assert_eq!(lines[2]["type"], "trace");
    assert_eq!(lines[2]["kind"], "started");
    assert_eq!(lines[3]["type"], "trace");
    assert_eq!(lines[3]["kind"], "completed");
}

#[test]
fn run_outputs_ndjson_report_for_schema_failures() {
    let server_url = spawn_http_server(
        200,
        "OK",
        "application/json",
        r#"[{"id":"550e8400-e29b-41d4-a716-446655440000"},{"id":"not-a-uuid"}]"#,
    );
    let workspace = TestWorkspace::new();
    workspace.write_file(
        "collection.hen",
        &format!(
            r#"schema User {{
  id: UUID
}}

schema Users = User[]

---

Schema NDJSON Failure Fixture

GET {server_url}

^ & body === Users
"#
        ),
    );

    let output = workspace.run_hen(["run", "collection.hen", "--output", "ndjson"]);

    assert_eq!(output.status_code, 1, "stderr: {}", output.stderr);
    assert!(output.stderr.is_empty(), "stderr: {}", output.stderr);

    let lines = output
        .stdout
        .lines()
        .map(|line| serde_json::from_str::<Value>(line).expect("line should be valid json"))
        .collect::<Vec<_>>();

    assert_eq!(lines.len(), 4);
    assert_eq!(lines[0]["type"], "run");
    assert_eq!(lines[0]["traceCount"], 2);
    assert_eq!(lines[1]["type"], "failure");
    assert_eq!(lines[2]["type"], "trace");
    assert_eq!(lines[2]["kind"], "started");
    assert_eq!(lines[3]["type"], "trace");
    assert_eq!(lines[3]["kind"], "failed");

    let mismatch = &lines[1]["assertions"][0]["mismatch"];

    assert_eq!(mismatch["kind"], "schema");
    assert_eq!(mismatch["reason"], "array_item_mismatch");
    assert_eq!(mismatch["target"], "Users");
    assert_eq!(mismatch["path"], "body[1].id");
    assert_eq!(mismatch["actualPath"], "body");
    assert_eq!(mismatch["operator"], "===");
    assert_eq!(mismatch["actual"]["type"], "string");
    assert_eq!(mismatch["actual"]["value"], "not-a-uuid");
    assert_eq!(mismatch["expected"]["type"], "format");
    assert_eq!(mismatch["expected"]["value"], "UUID");
}