tail-fin-cli 0.4.0

Multi-site browser automation CLI — attaches to Chrome or auto-launches a stealth browser to drive 14+ sites
//! End-to-end CLI integration tests. Each test spawns the real `tail-fin`
//! binary via assert_cmd. No browser, no network — tests exercise argument
//! parsing, error paths, and help output.
//!
//! Some tests assume the default feature set (which includes `sa`, `twitter`,
//! `grok`). Those tests are gated behind `#[cfg(feature = ...)]` and are
//! skipped when the crate is built with `--no-default-features`.

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

fn tail_fin() -> Command {
    Command::cargo_bin("tail-fin").expect("tail-fin binary must exist")
}

#[cfg(all(feature = "sa", feature = "twitter", feature = "grok"))]
#[test]
fn top_level_help_lists_subcommands() {
    let out = tail_fin().arg("--help").assert().success();
    let stdout = std::str::from_utf8(&out.get_output().stdout).unwrap();

    // Assert a handful of feature-gated subcommands appear. We don't assert
    // the full list because features can vary in different builds.
    for expected in ["sa", "twitter", "grok"] {
        assert!(
            stdout.contains(expected),
            "top-level --help should mention subcommand '{expected}'\n---\n{stdout}"
        );
    }
}

#[cfg(feature = "sa")]
#[test]
fn subcommand_help_works() {
    let out = tail_fin().args(["sa", "--help"]).assert().success();
    let stdout = std::str::from_utf8(&out.get_output().stdout).unwrap();

    for expected in ["quote", "income-statement", "cash-flow"] {
        assert!(
            stdout.contains(expected),
            "sa --help should list '{expected}'\n---\n{stdout}"
        );
    }
}

#[test]
fn unknown_subcommand_errors_nonzero() {
    tail_fin()
        .arg("bogus-nonexistent-cmd")
        .assert()
        .failure()
        .stderr(predicate::str::contains("unrecognized"));
}

#[test]
fn connect_and_cookies_are_mutually_exclusive() {
    tail_fin()
        .args([
            "--connect",
            "127.0.0.1:9222",
            "--cookies=auto",
            "sa",
            "quote",
            "AAPL",
        ])
        .assert()
        .failure()
        .stderr(predicate::str::contains("--connect").and(predicate::str::contains("--cookies")));
}

#[test]
fn repl_with_subcommand_rejects() {
    tail_fin()
        .args(["--repl", "sa", "quote", "AAPL"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("cannot be combined"));
}

#[test]
fn repl_quit_exits_clean() {
    tail_fin()
        .write_stdin("quit\n")
        .assert()
        .success()
        .stderr(predicate::str::is_empty());
}

#[test]
fn repl_unknown_cmd_continues_then_quits() {
    // Bogus line triggers a clap error (printed to stderr) but the REPL
    // does NOT exit — `quit` on the next line cleans up.
    tail_fin()
        .write_stdin("bogus-nonexistent-cmd\nquit\n")
        .assert()
        .success()
        .stderr(predicate::str::contains("unrecognized"));
}

#[cfg(all(feature = "sa", feature = "twitter"))]
#[test]
fn repl_help_subcommand_shows_commands() {
    // Inside the REPL, `help` (clap's auto-registered subcommand) prints the
    // subcommand list to stdout and exits 0 after `quit`.
    tail_fin()
        .write_stdin("help\nquit\n")
        .assert()
        .success()
        .stdout(predicate::str::contains("sa").and(predicate::str::contains("twitter")));
}