devpulse 1.0.0

Developer diagnostics: HTTP timing, build artifact cleanup, environment health checks, port scanning, PATH analysis, and config format conversion
//! Integration tests for the devpulse CLI binary.
//!
//! These tests invoke the compiled binary with various arguments and verify
//! exit codes, stdout content, and JSON output structure.

use std::process::Command;

/// Helper to get the path to the devpulse binary.
fn devpulse_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_devpulse"))
}

// ─── Top-level CLI ───────────────────────────────────────────────────────────

#[test]
fn test_no_args_launches_tui() {
    // In integration tests, stdin is not a TTY, so devpulse falls back to
    // printing CLI help text instead of launching the interactive TUI.
    let output = devpulse_bin().output().expect("failed to run devpulse");
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("Usage") || stdout.contains("usage"),
        "Expected help/usage text in non-TTY mode, got: {stdout}"
    );
    assert!(output.status.success());
}

#[test]
fn test_version_flag() {
    let output = devpulse_bin()
        .arg("--version")
        .output()
        .expect("failed to run devpulse");
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("devpulse") && stdout.contains(env!("CARGO_PKG_VERSION")),
        "Expected version string, got: {stdout}"
    );
}

#[test]
fn test_help_flag() {
    let output = devpulse_bin()
        .arg("--help")
        .output()
        .expect("failed to run devpulse");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("http"),
        "Expected 'http' subcommand in help"
    );
    assert!(
        stdout.contains("sweep"),
        "Expected 'sweep' subcommand in help"
    );
    assert!(
        stdout.contains("doctor"),
        "Expected 'doctor' subcommand in help"
    );
    assert!(
        stdout.contains("ports"),
        "Expected 'ports' subcommand in help"
    );
    assert!(stdout.contains("env"), "Expected 'env' subcommand in help");
}

// ─── HTTP subcommand ─────────────────────────────────────────────────────────

#[test]
fn test_http_help() {
    let output = devpulse_bin()
        .args(["http", "--help"])
        .output()
        .expect("failed to run devpulse http --help");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("URL"), "Expected URL argument in http help");
}

#[test]
fn test_http_invalid_url() {
    let output = devpulse_bin()
        .args(["http", "not-a-url"])
        .output()
        .expect("failed to run devpulse http");
    assert!(!output.status.success(), "Should fail on invalid URL");
}

// ─── Sweep subcommand ────────────────────────────────────────────────────────

#[test]
fn test_sweep_help() {
    let output = devpulse_bin()
        .args(["sweep", "--help"])
        .output()
        .expect("failed to run devpulse sweep --help");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("PATH"),
        "Expected PATH argument in sweep help"
    );
}

#[test]
fn test_sweep_scan_nonexistent() {
    let output = devpulse_bin()
        .args(["sweep", "/nonexistent/path/that/does/not/exist"])
        .output()
        .expect("failed to run devpulse sweep");
    // Should fail because the path does not exist
    assert!(!output.status.success());
}

#[test]
fn test_sweep_json_output() {
    let output = devpulse_bin()
        .args(["--json", "sweep", "."])
        .output()
        .expect("failed to run devpulse sweep --json");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    // JSON output should be valid JSON with expected fields
    let parsed: serde_json::Value =
        serde_json::from_str(&stdout).expect("sweep JSON output should be valid JSON");
    assert!(
        parsed.get("total_bytes").is_some(),
        "Expected 'total_bytes' in sweep JSON"
    );
    assert!(
        parsed.get("entries").is_some(),
        "Expected 'entries' in sweep JSON"
    );
}

// ─── Doctor subcommand ───────────────────────────────────────────────────────

#[test]
fn test_doctor_help() {
    let output = devpulse_bin()
        .args(["doctor", "--help"])
        .output()
        .expect("failed to run devpulse doctor --help");
    assert!(output.status.success());
}

#[test]
fn test_doctor_json_output() {
    let output = devpulse_bin()
        .args(["--json", "doctor"])
        .output()
        .expect("failed to run devpulse doctor --json");
    let stdout = String::from_utf8_lossy(&output.stdout);
    let parsed: serde_json::Value =
        serde_json::from_str(&stdout).expect("doctor JSON output should be valid JSON");
    assert!(parsed.is_array(), "Expected JSON array from doctor");
    // Should have at least one check result
    let arr = parsed.as_array().unwrap();
    assert!(!arr.is_empty(), "Expected at least one check result");
    // Each result should have name, status, detail
    let first = &arr[0];
    assert!(
        first.get("name").is_some(),
        "Expected 'name' in check result"
    );
    assert!(
        first.get("status").is_some(),
        "Expected 'status' in check result"
    );
}

// ─── Ports subcommand ────────────────────────────────────────────────────────

#[test]
fn test_ports_help() {
    let output = devpulse_bin()
        .args(["ports", "--help"])
        .output()
        .expect("failed to run devpulse ports --help");
    assert!(output.status.success());
}

#[test]
fn test_ports_json_output() {
    let output = devpulse_bin()
        .args(["--json", "ports"])
        .output()
        .expect("failed to run devpulse ports --json");
    let stdout = String::from_utf8_lossy(&output.stdout);
    let parsed: serde_json::Value =
        serde_json::from_str(&stdout).expect("ports JSON output should be valid JSON");
    assert!(parsed.is_array(), "Expected JSON array from ports");
}

// ─── Env subcommand ──────────────────────────────────────────────────────────

#[test]
fn test_env_help() {
    let output = devpulse_bin()
        .args(["env", "--help"])
        .output()
        .expect("failed to run devpulse env --help");
    assert!(output.status.success());
}

#[test]
fn test_env_json_output() {
    let output = devpulse_bin()
        .args(["--json", "env"])
        .output()
        .expect("failed to run devpulse env --json");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    let parsed: serde_json::Value =
        serde_json::from_str(&stdout).expect("env JSON output should be valid JSON");
    assert!(
        parsed.get("path_entries").is_some(),
        "Expected 'path_entries' in env JSON"
    );
}

// ─── Color flag ──────────────────────────────────────────────────────────────

#[test]
fn test_color_never_flag() {
    let output = devpulse_bin()
        .args(["--color", "never", "doctor"])
        .output()
        .expect("failed to run devpulse --color never");
    // Should succeed — color never should not break anything
    let stdout = String::from_utf8_lossy(&output.stdout);
    // ANSI escape codes start with \x1b[ — should NOT be present
    assert!(
        !stdout.contains("\x1b["),
        "Expected no ANSI codes with --color never"
    );
}