collet 0.1.0

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
use std::process::{Command, Stdio};

/// Get the path to the built binary.
fn binary_path() -> std::path::PathBuf {
    let mut path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .to_path_buf();
    path.push("collet");
    path
}

fn ensure_built() {
    let status = Command::new("cargo")
        .args(["build", "--quiet"])
        .current_dir(env!("CARGO_MANIFEST_DIR"))
        .status()
        .expect("cargo build failed");
    assert!(status.success(), "cargo build should succeed");
}

// ---------------------------------------------------------------------------
// help
// ---------------------------------------------------------------------------

#[test]
fn help_exits_successfully() {
    ensure_built();
    let output = Command::new(binary_path())
        .arg("help")
        .env_remove("COLLET_API_KEY")
        .env_remove("ZAI_API_KEY")
        .output()
        .expect("failed to run binary");

    assert!(output.status.success(), "help should exit 0");
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("USAGE") || stderr.contains("collet"));
}

#[test]
fn short_help_flag_exits_successfully() {
    ensure_built();
    let output = Command::new(binary_path())
        .arg("-h")
        .env_remove("COLLET_API_KEY")
        .env_remove("ZAI_API_KEY")
        .output()
        .expect("failed to run binary");

    assert!(output.status.success(), "-h should exit 0");
}

// ---------------------------------------------------------------------------
// status
// ---------------------------------------------------------------------------

#[test]
fn status_runs() {
    ensure_built();
    let output = Command::new(binary_path())
        .arg("status")
        .env_remove("COLLET_API_KEY")
        .env_remove("ZAI_API_KEY")
        .output()
        .expect("failed to run binary");

    assert!(output.status.success(), "status should exit 0");
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("collet status") || stderr.contains("config"));
}

// ---------------------------------------------------------------------------
// Missing API key (no args = TUI, but no key → error)
// ---------------------------------------------------------------------------

#[test]
fn missing_api_key_shows_error() {
    ensure_built();
    // Use an isolated COLLET_HOME so the test is not affected by the developer's
    // existing config.toml / encrypted API key. Without isolation the binary would
    // find a valid key, enter TUI mode, and block waiting for terminal input.
    let tmp_home = std::env::temp_dir().join(format!("collet_test_home_{}", std::process::id()));
    std::fs::create_dir_all(&tmp_home).expect("failed to create temp home");

    let output = Command::new(binary_path())
        .stdin(Stdio::null()) // prevent terminal inheritance — ensures is_terminal() == false
        .env("COLLET_HOME", &tmp_home)
        .env_remove("COLLET_API_KEY")
        .env_remove("ZAI_API_KEY")
        .output()
        .expect("failed to run binary");

    let _ = std::fs::remove_dir_all(&tmp_home);

    assert!(
        !output.status.success(),
        "missing API key should cause non-zero exit"
    );
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("API") || stderr.contains("key") || stderr.contains("ZAI"),
        "stderr should mention API key, got: {stderr}"
    );
}

// ---------------------------------------------------------------------------
// Prompt arg triggers headless (will fail on network, but shouldn't panic)
// ---------------------------------------------------------------------------

#[test]
fn prompt_arg_runs_headless() {
    ensure_built();
    let output = Command::new(binary_path())
        .arg("echo hello test")
        .env("ZAI_API_KEY", "test-key-no-network")
        .output()
        .expect("failed to run binary");

    // Will fail due to network, but should NOT panic or show usage error
    let stderr = String::from_utf8_lossy(&output.stderr);
    // Should not contain "Usage:" or "unknown command" — it should attempt headless
    assert!(
        !stderr.contains("Usage:"),
        "prompt should trigger headless, not show usage: {stderr}"
    );
}