bijux-cli 0.3.6

Command-line runtime for automation, plugin-driven tools, and interactive workflows with structured output.
Documentation
#![forbid(unsafe_code)]
//! Binary-level memory parity tests.

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

use bijux_cli as _;
use libc as _;
use serde_json::Value;
use shlex as _;
use thiserror as _;

fn make_temp_dir(name: &str) -> PathBuf {
    let nanos = SystemTime::now().duration_since(UNIX_EPOCH).expect("clock").as_nanos();
    let path = std::env::temp_dir().join(format!("bijux-memory-bin-{name}-{nanos}"));
    fs::create_dir_all(&path).expect("mkdir");
    path
}

fn run_with_env(args: &[&str], envs: &[(&str, String)]) -> std::process::Output {
    let mut cmd = Command::new(env!("CARGO_BIN_EXE_bijux"));
    cmd.args(args);
    for (k, v) in envs {
        cmd.env(k, v);
    }
    cmd.output().expect("binary should execute")
}

fn python_cli() -> String {
    if let Ok(path) = std::env::var("BIJUX_REFERENCE_CLI") {
        if !path.trim().is_empty() {
            return path;
        }
    }

    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let root = manifest_dir.parent().and_then(|p| p.parent()).expect("workspace root");
    let legacy = root.join("bin").join("bijux");
    if legacy.exists() {
        return legacy.display().to_string();
    }

    env!("CARGO_BIN_EXE_bijux").to_string()
}

fn parse_json(bytes: &[u8]) -> Value {
    serde_json::from_slice(bytes).expect("json output")
}

#[test]
fn memory_root_and_list_support_json_yaml_and_text() {
    let temp = make_temp_dir("formats");
    let home = temp.join("home");
    let memory_file = home.join(".bijux").join(".memory.json");
    fs::create_dir_all(memory_file.parent().expect("parent")).expect("mkdir");
    fs::write(&memory_file, "{\"beta\":2,\"alpha\":1}").expect("write memory");
    let envs = [("HOME", home.display().to_string())];

    let root_json = run_with_env(&["memory", "--format", "json", "--no-pretty"], &envs);
    assert!(root_json.status.success());
    let root_payload = parse_json(&root_json.stdout);
    assert_eq!(root_payload["status"], "ok");
    assert_eq!(root_payload["count"], 2);

    let list_json = run_with_env(&["memory", "list", "--format", "json", "--no-pretty"], &envs);
    assert!(list_json.status.success());
    let list_payload = parse_json(&list_json.stdout);
    assert_eq!(list_payload["status"], "ok");
    assert_eq!(list_payload["keys"][0], "alpha");
    assert_eq!(list_payload["keys"][1], "beta");

    let list_yaml = run_with_env(&["memory", "list", "--format", "yaml", "--pretty"], &envs);
    assert!(list_yaml.status.success());
    let yaml = String::from_utf8(list_yaml.stdout).expect("yaml utf-8");
    assert_eq!(yaml, include_str!("../../../data/golden/cli_surface/memory_list_yaml.txt"));

    let list_text = run_with_env(&["memory", "list", "--format", "text"], &envs);
    assert!(list_text.status.success());
    let text = String::from_utf8(list_text.stdout).expect("text utf-8");
    assert_eq!(text, include_str!("../../../data/golden/cli_surface/memory_list_text.txt"));
}

#[test]
fn memory_missing_state_is_empty_and_quiet_mode_suppresses_output() {
    let temp = make_temp_dir("missing");
    let home = temp.join("home");
    fs::create_dir_all(home.join(".bijux")).expect("mkdir");

    let out = run_with_env(
        &["memory", "list", "--format", "json", "--no-pretty"],
        &[("HOME", home.display().to_string())],
    );
    assert!(out.status.success());
    let payload = parse_json(&out.stdout);
    assert_eq!(payload["count"], 0);

    let quiet =
        run_with_env(&["memory", "list", "--quiet"], &[("HOME", home.display().to_string())]);
    assert!(quiet.status.success());
    assert!(quiet.stdout.is_empty());
    assert!(quiet.stderr.is_empty());
}

#[test]
fn memory_non_object_json_state_fails_with_error_envelope() {
    let temp = make_temp_dir("non-object");
    let home = temp.join("home");
    let memory_file = home.join(".bijux").join(".memory.json");
    fs::create_dir_all(memory_file.parent().expect("parent")).expect("mkdir");
    fs::write(&memory_file, "[]").expect("write non-object json");

    let out = run_with_env(
        &["memory", "list", "--format", "json", "--no-pretty"],
        &[("HOME", home.display().to_string())],
    );
    assert_eq!(out.status.code(), Some(1));
    assert!(out.stdout.is_empty());
    let payload = parse_json(&out.stderr);
    assert_eq!(payload["status"], "error");
    assert_eq!(payload["code"], 1);
    assert_eq!(payload["command"], "memory list");
}

#[test]
fn memory_command_with_config_path_override_keeps_stable_json_contract() {
    let temp = make_temp_dir("config-env");
    let home = temp.join("home");
    let config_path = temp.join("custom.env");
    let memory_file = home.join(".bijux").join(".memory.json");
    fs::create_dir_all(memory_file.parent().expect("parent")).expect("mkdir");
    fs::write(&memory_file, "{\"home_only\":true}").expect("write memory");

    let args = [
        "memory",
        "list",
        "--format",
        "json",
        "--no-pretty",
        "--config-path",
        config_path.to_str().expect("utf-8"),
    ];
    let envs = [
        ("HOME", home.display().to_string()),
        ("BIJUXCLI_CONFIG", config_path.display().to_string()),
    ];

    let out = run_with_env(&args, &envs);
    let repeat = run_with_env(
        &[
            "memory",
            "list",
            "--format",
            "json",
            "--no-pretty",
            "--config-path",
            config_path.to_str().expect("utf-8"),
        ],
        &envs,
    );
    assert!(out.status.success());
    assert!(repeat.status.success());
    assert_eq!(out.stdout, repeat.stdout);
    let payload = parse_json(&out.stdout);
    assert_eq!(payload["status"], "ok");
    assert!(payload.get("keys").and_then(Value::as_array).is_some());
}

#[test]
fn memory_root_parity_with_python_summary_command() {
    let temp = make_temp_dir("python-parity");
    let home = temp.join("home");
    let memory_file = home.join(".bijux").join(".memory.json");
    fs::create_dir_all(memory_file.parent().expect("parent")).expect("mkdir");
    fs::write(&memory_file, "{\"alpha\":1,\"beta\":2}").expect("write memory");

    let py = Command::new(python_cli())
        .args(["memory", "--format", "json", "--no-pretty"])
        .env("HOME", home.display().to_string())
        .output()
        .expect("python cli");

    let rs = run_with_env(
        &["memory", "--format", "json", "--no-pretty"],
        &[("HOME", home.display().to_string())],
    );

    assert_eq!(py.status.code(), rs.status.code());
    let py_json = parse_json(&py.stdout);
    let rs_json = parse_json(&rs.stdout);
    assert_eq!(py_json["status"], rs_json["status"]);
    assert_eq!(py_json["count"], rs_json["count"]);
}