agent-code 0.16.0

An AI-powered coding agent for the terminal, written in pure Rust
//! Integration tests for --output-format.
//!
//! These tests verify CLI flag parsing, error handling, and the
//! JSONL output contract. They do NOT require an API key — they
//! test error paths and argument validation only.

use assert_cmd::Command;
use predicates::prelude::*;

fn agent() -> Command {
    Command::cargo_bin("agent").expect("binary should exist")
}

// ── Flag parsing ──────────────────────────────────────────────────

#[test]
fn output_format_appears_in_help() {
    agent()
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("--output-format"));
}

#[test]
fn output_format_invalid_value_fails() {
    agent()
        .args(["--output-format", "xml", "--prompt", "hello"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("unknown output format"));
}

#[test]
fn output_format_json_without_prompt_fails() {
    // JSON mode only makes sense in non-interactive (--prompt) mode.
    agent()
        .args(["--output-format", "json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("requires --prompt"));
}

#[test]
fn output_format_text_is_default() {
    // With no --output-format flag but also no API key, the text path
    // should be taken. Without a key it will fail at API key validation,
    // but AFTER format parsing succeeds (no "unknown output format" error).
    let output = agent()
        .args(["--prompt", "hello"])
        .output()
        .expect("should execute");

    // It will fail (no API key), but the failure should NOT be about
    // output format parsing — it should be about API key.
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        !stderr.contains("unknown output format"),
        "text should be the default format, not trigger an error: {stderr}",
    );
}

#[test]
fn output_format_json_no_api_key_emits_session_events() {
    // Even when the API key is missing and the run fails, JSON mode
    // should still emit session_start and session_end events so
    // downstream consumers get a well-formed stream.
    let output = agent()
        .args(["--output-format", "json", "--prompt", "hello"])
        .env_remove("AGENT_CODE_API_KEY")
        .output()
        .expect("should execute");

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    // The binary may fail before reaching the JSON sink (at the API key
    // check, which happens before the output format branch). In that case,
    // stdout will be empty and the error is on stderr. That's acceptable —
    // we just verify it doesn't panic or produce malformed output.
    if !stdout.is_empty() {
        // If we got any stdout, verify it's valid JSONL.
        for line in stdout.lines() {
            if line.trim().is_empty() {
                continue;
            }
            let parsed: Result<serde_json::Value, _> = serde_json::from_str(line);
            assert!(
                parsed.is_ok(),
                "non-JSON line in JSONL output: {line}\nfull stdout: {stdout}",
            );
        }
    }

    // Either way, the process should not panic.
    assert!(!stderr.contains("panicked"), "agent panicked: {stderr}",);
}

// ── Case insensitivity ────────────────────────────────────────────

#[test]
fn output_format_case_insensitive() {
    // "JSON" (uppercase) should be accepted.
    let output = agent()
        .args(["--output-format", "JSON", "--prompt", "hello"])
        .env_remove("AGENT_CODE_API_KEY")
        .output()
        .expect("should execute");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        !stderr.contains("unknown output format"),
        "uppercase JSON should be accepted: {stderr}",
    );
}