fundaia 0.10.0

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
//! Budgets on the compiled binary, spawned the way an agent spawns it.
//!
//! Startup is the one cost every invocation pays: an AI agent driving this tool
//! runs it dozens of times per task, and a CLI that takes half a second to say
//! `--version` taxes every one of them. These tests measure the real
//! executable — `CARGO_BIN_EXE_fundaia` is the binary this same profile just
//! built — so a dependency that drags startup down fails the pipeline instead
//! of being discovered by somebody's stopwatch.
//!
//! The budget comes from `FUNDAIA_PERF_BUDGET_MS` so the pipeline can hold the
//! release build to a tighter number than a debug build on a laptop; the
//! default is far above what the binary does today. The median of several runs
//! rather than the worst: one descheduled process on a busy runner must not
//! paint the build red.

use std::process::{Command, Output};
use std::time::{Duration, Instant};

fn budget() -> Duration {
    let millis = std::env::var("FUNDAIA_PERF_BUDGET_MS")
        .ok()
        .and_then(|value| value.parse().ok())
        .unwrap_or(500);
    Duration::from_millis(millis)
}

/// The binary with nothing configured: no profile on disk, no token in the
/// environment, and the update check off so no run touches the network.
fn fundaia(arguments: &[&str]) -> Command {
    let mut command = Command::new(env!("CARGO_BIN_EXE_fundaia"));
    command
        .args(arguments)
        .env("FUNDAIA_CONFIG", "/nonexistent/fundaia-perf/config.toml")
        .env("FUNDAIA_NO_UPDATE_CHECK", "1")
        .env_remove("FUNDAIA_URL")
        .env_remove("FUNDAIA_TOKEN")
        .env_remove("FUNDAIA_PROFILE")
        .env_remove("FUNDAIA_JSON");
    command
}

fn run(arguments: &[&str]) -> Output {
    fundaia(arguments)
        .output()
        .expect("the binary this test suite was built alongside runs")
}

fn median_run_millis(arguments: &[&str]) -> u128 {
    // One unmeasured run first, so the page cache is warm for every sample the
    // way it is for every real invocation but the machine's first.
    run(arguments);

    let mut samples: Vec<u128> = (0..9)
        .map(|_| {
            let started = Instant::now();
            run(arguments);
            started.elapsed().as_millis()
        })
        .collect();
    samples.sort_unstable();

    samples[samples.len() / 2]
}

#[test]
fn it_should_print_the_version_within_the_startup_budget() {
    assert!(median_run_millis(&["--version"]) < budget().as_millis());
}

#[test]
fn it_should_print_the_help_within_the_startup_budget() {
    assert!(median_run_millis(&["--help"]) < budget().as_millis());
}

/// The failure path is a startup path too: an unconfigured `projects` must be
/// refused before any network is touched, so an agent with a bad environment
/// learns it immediately rather than after a connect timeout.
#[test]
fn it_should_refuse_an_unconfigured_command_within_the_startup_budget() {
    assert!(median_run_millis(&["projects"]) < budget().as_millis());
}

#[test]
fn it_should_exit_nonzero_when_nothing_is_configured() {
    assert!(!run(&["projects"]).status.success());
}

#[test]
fn it_should_ask_for_a_login_when_nothing_is_configured() {
    let stderr = String::from_utf8_lossy(&run(&["projects"]).stderr).to_string();
    assert!(stderr.contains("login"));
}

/// The agent contract: under `--json` a failure is one JSON object on stderr,
/// so the reader branches on `error.code` instead of parsing Spanish.
#[test]
fn it_should_answer_an_error_as_json_in_json_mode() {
    let output = run(&["--json", "projects"]);
    let stderr = String::from_utf8_lossy(&output.stderr);

    let parsed: serde_json::Value =
        serde_json::from_str(stderr.trim()).expect("stderr is exactly one JSON object");

    assert_eq!(parsed["error"]["code"], "cli_error");
}

#[test]
fn it_should_keep_stdout_empty_when_a_json_command_fails() {
    assert!(run(&["--json", "projects"]).stdout.is_empty());
}

#[test]
fn it_should_write_a_completion_script_on_stdout() {
    let stdout = String::from_utf8_lossy(&run(&["completions", "bash"]).stdout).to_string();
    assert!(stdout.contains("fundaia"));
}