index-cli 1.0.0

Command-line prototype for Index.
//! Integration coverage tests for the `index` binary entrypoint.

use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

fn unique_temp_dir(label: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_or(0_u128, |value| value.as_nanos());
    std::env::temp_dir().join(format!("index-cli-main-entry-{label}-{nanos}"))
}

fn index_command(label: &str) -> Command {
    let base = unique_temp_dir(label);
    let mut command = Command::new(env!("CARGO_BIN_EXE_index"));
    command
        .env("XDG_CONFIG_HOME", base.join("config").as_os_str())
        .env("XDG_CACHE_HOME", base.join("cache").as_os_str())
        .env("XDG_STATE_HOME", base.join("state").as_os_str());
    command
}

fn output_text(bytes: &[u8]) -> String {
    String::from_utf8_lossy(bytes).to_string()
}

#[test]
fn entrypoint_emits_help_text() -> Result<(), Box<dyn std::error::Error>> {
    let output = index_command("help").arg("--help").output()?;

    assert!(output.status.success());
    let stdout = output_text(&output.stdout);
    assert!(stdout.contains("Index terminal browser prototype"));
    assert!(stdout.contains("index --plain <url-or-local-html-file>"));
    assert!(output.stderr.is_empty());
    Ok(())
}

#[test]
fn entrypoint_emits_version_text() -> Result<(), Box<dyn std::error::Error>> {
    let output = index_command("version").arg("--version").output()?;

    assert!(output.status.success());
    let stdout = output_text(&output.stdout);
    assert!(stdout.starts_with("index "));
    assert!(output.stderr.is_empty());
    Ok(())
}

#[test]
fn entrypoint_renders_plain_output_from_local_file() -> Result<(), Box<dyn std::error::Error>> {
    let html_path = unique_temp_dir("plain").with_extension("html");
    fs::write(
        &html_path,
        "<html><body><h1>Main Entry Test</h1><p>plain output</p></body></html>",
    )?;

    let output = index_command("plain")
        .args([
            OsString::from("--plain"),
            html_path.clone().into_os_string(),
        ])
        .output()?;

    assert!(output.status.success());
    let stdout = output_text(&output.stdout);
    assert!(stdout.contains("Main Entry Test"));
    assert!(stdout.contains("plain output"));
    assert!(output.stderr.is_empty());

    let _ = fs::remove_file(html_path);
    Ok(())
}

#[test]
fn entrypoint_reports_argument_errors() -> Result<(), Box<dyn std::error::Error>> {
    let output = index_command("error")
        .args(["--extract", "xml", "-"])
        .output()?;

    assert!(!output.status.success());
    assert!(output.stdout.is_empty());
    let stderr = output_text(&output.stderr);
    assert!(stderr.contains("unsupported extraction format: xml"));
    Ok(())
}

#[test]
fn entrypoint_reports_tui_runtime_errors_without_tty() -> Result<(), Box<dyn std::error::Error>> {
    let output = index_command("tui-no-tty")
        .args(["https://example.org"])
        .output()?;

    assert!(!output.status.success());
    let stderr = output_text(&output.stderr);
    assert!(stderr.contains("failed to run terminal UI"));
    Ok(())
}