jevia 0.1.5

Outcome-aware model routing CLI
use std::{
    fs,
    io::{Read, Write},
    net::TcpListener,
    thread,
};

use assert_cmd::Command;
use tempfile::tempdir;

#[test]
fn runs_show_and_recover_preserve_the_task_and_record_interruption() {
    let directory = tempdir().unwrap();
    let state = directory.path().join(".jevia");
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .success();
    let record = serde_json::json!({
        "schema_version": 2, "run_id": "interrupted-test", "tier": "fast", "suggested_tier": "fast",
        "confidence": 0.9, "probabilities": {}, "fallback_applied": false, "jev_model": "test",
        "created_at_ms": 1, "task": "do not rerun me", "outcome": "unknown",
        "lifecycle": {"state": "running", "started_at_ms": 1, "finished_at_ms": null}
    });
    fs::write(state.join("runs.jsonl"), format!("{record}\n")).unwrap();
    let output = Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "show", "interrupted-test", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let shown: serde_json::Value = serde_json::from_slice(&output).unwrap();
    assert_eq!(shown["lifecycle"]["state"], "running");
    use sha2::{Digest, Sha256};
    let leases = state.join("run-leases");
    fs::create_dir_all(&leases).unwrap();
    let name = Sha256::digest(b"interrupted-test")
        .iter()
        .map(|byte| format!("{byte:02x}"))
        .collect::<String>();
    let lock = fs::File::create(leases.join(format!("{name}.lock"))).unwrap();
    lock.lock().unwrap();
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "recover", "interrupted-test"])
        .assert()
        .failure();
    drop(lock);
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "recover", "interrupted-test"])
        .assert()
        .success();
    let recovered: serde_json::Value =
        serde_json::from_str(&fs::read_to_string(state.join("runs.jsonl")).unwrap()).unwrap();
    assert_eq!(recovered["lifecycle"]["state"], "interrupted");
    assert_eq!(recovered["outcome"], "unknown");
    assert_eq!(recovered["task"], "do not rerun me");
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "recover", "interrupted-test"])
        .assert()
        .failure();
}

#[test]
fn help_is_available() {
    let mut command = Command::cargo_bin("jevia").expect("binary is built");
    command.arg("--help").assert().success();
}

#[test]
fn execution_deadlines_require_explicit_non_interactive_mode() {
    Command::cargo_bin("jevia")
        .unwrap()
        .args(["run", "agent", "task", "--timeout-seconds", "5"])
        .assert()
        .failure();
    Command::cargo_bin("jevia")
        .unwrap()
        .args([
            "run",
            "agent",
            "task",
            "--non-interactive",
            "--timeout-seconds",
            "0",
        ])
        .assert()
        .failure();
}

#[test]
fn history_maintenance_previews_before_applying_and_ignores_private_snapshots() {
    let directory = tempdir().unwrap();
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .success();
    let state = directory.path().join(".jevia");
    let original = b"{\"schema_version\":";
    fs::write(state.join("runs.jsonl"), original).unwrap();
    let preview = Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "repair", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let preview: serde_json::Value = serde_json::from_slice(&preview).unwrap();
    assert_eq!(preview["would_change"], true);
    assert_eq!(preview["applied"], false);
    assert_eq!(fs::read(state.join("runs.jsonl")).unwrap(), original);
    assert!(!state.join("history-backups").exists());
    let applied = Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "repair", "--apply", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let applied: serde_json::Value = serde_json::from_slice(&applied).unwrap();
    assert_eq!(applied["applied"], true);
    assert_eq!(
        fs::read(applied["backup"].as_str().unwrap()).unwrap(),
        original
    );
    assert!(fs::read(state.join("runs.jsonl")).unwrap().is_empty());
    Command::cargo_bin("jevia")
        .unwrap()
        .current_dir(directory.path())
        .args(["runs", "archive", "--keep", "1", "--json"])
        .assert()
        .success();
    let ignore = fs::read_to_string(state.join(".gitignore")).unwrap();
    assert!(ignore.lines().any(|line| line == "history-backups/"));
    assert!(ignore.lines().any(|line| line == "history-archives/"));
}

#[test]
fn init_creates_a_valid_project_configuration() {
    let directory = tempdir().expect("temporary directory");
    let mut command = Command::cargo_bin("jevia").expect("binary is built");
    command
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .success();

    let config_path = directory.path().join(".jevia/config.toml");
    let config = fs::read_to_string(config_path).expect("config is readable");
    let parsed = jevia_core::Config::from_toml(&config).expect("config is valid");

    assert_eq!(parsed.router.fallback_tier, "strong");
    assert!(directory.path().join(".jevia/.gitignore").is_file());
    let ignore = fs::read_to_string(directory.path().join(".jevia/.gitignore"))
        .expect("ignore file is readable");
    assert!(ignore.lines().any(|line| line == "runs.lock"));
    assert!(ignore.lines().any(|line| line == "cache.jsonl"));
    assert!(ignore.lines().any(|line| line == "cache.lock"));
}

#[test]
fn init_does_not_overwrite_configuration_without_force() {
    let directory = tempdir().expect("temporary directory");
    let mut first = Command::cargo_bin("jevia").expect("binary is built");
    first
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .success();

    let mut second = Command::cargo_bin("jevia").expect("binary is built");
    second
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .failure();
}

#[test]
fn init_preserves_existing_ignore_rules() {
    let directory = tempdir().expect("temporary directory");
    let jevia_directory = directory.path().join(".jevia");
    fs::create_dir_all(&jevia_directory).expect("Jevia directory is created");
    fs::write(jevia_directory.join(".gitignore"), "custom-rule\n")
        .expect("custom ignore file is written");

    let mut command = Command::cargo_bin("jevia").expect("binary is built");
    command
        .current_dir(directory.path())
        .arg("init")
        .assert()
        .success();

    let ignore =
        fs::read_to_string(jevia_directory.join(".gitignore")).expect("ignore file is readable");
    assert!(ignore.lines().any(|line| line == "custom-rule"));
    assert!(ignore.lines().any(|line| line == "cache.jsonl"));
}

#[test]
fn cache_commands_are_available() {
    let directory = tempdir().expect("temporary directory");
    let mut init = Command::cargo_bin("jevia").expect("binary is built");
    init.current_dir(directory.path())
        .arg("init")
        .assert()
        .success();

    let mut status = Command::cargo_bin("jevia").expect("binary is built");
    status
        .current_dir(directory.path())
        .args(["cache", "status"])
        .assert()
        .success();

    let mut clear = Command::cargo_bin("jevia").expect("binary is built");
    clear
        .current_dir(directory.path())
        .args(["cache", "clear"])
        .assert()
        .success();
}

#[test]
fn check_completes_a_live_round_trip_without_storing_a_run() {
    let directory = tempdir().expect("temporary directory");
    let jevia_directory = directory.path().join(".jevia");
    fs::create_dir_all(&jevia_directory).expect("Jevia directory is created");

    let listener = TcpListener::bind("127.0.0.1:0").expect("test server binds");
    let address = listener.local_addr().expect("test server address");
    let server = thread::spawn(move || {
        let (mut stream, _) = listener.accept().expect("test request arrives");
        let mut request = [0_u8; 8192];
        let _ = stream.read(&mut request).expect("test request is readable");
        let body = r#"{"model":"jev-test","answers":{"tier":{"type":"choice","choice":"fast","confidence":0.94,"probabilities":{"fast":0.94,"balanced":0.05,"strong":0.01}}}}"#;
        let response = format!(
            "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",
            body.len()
        );
        stream
            .write_all(response.as_bytes())
            .expect("test response is written");
    });

    let mut config = jevia_core::Config::default();
    config.jev.base_url = format!("http://{address}");
    fs::write(
        jevia_directory.join("config.toml"),
        config.to_toml().expect("config serializes"),
    )
    .expect("config is written");

    let mut check = Command::cargo_bin("jevia").expect("binary is built");
    let output = check
        .current_dir(directory.path())
        .env("TYPESAFE_API_KEY", "test-key")
        .arg("check")
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let output = String::from_utf8(output).expect("stdout is UTF-8");

    server.join().expect("test server exits");
    assert!(output.contains("jev api: ok (model=jev-test, tier=fast, confidence=0.94)"));
    assert!(output.contains("jevia: ready"));
    assert!(!jevia_directory.join("runs.jsonl").exists());
}

#[test]
fn run_reports_when_a_harness_is_not_configured() {
    let directory = tempdir().expect("temporary directory");
    let mut init = Command::cargo_bin("jevia").expect("binary is built");
    init.current_dir(directory.path())
        .arg("init")
        .assert()
        .success();

    let mut run = Command::cargo_bin("jevia").expect("binary is built");
    let output = run
        .current_dir(directory.path())
        .args(["run", "missing", "test task"])
        .assert()
        .failure()
        .get_output()
        .stderr
        .clone();
    let error = String::from_utf8(output).expect("stderr is UTF-8");

    assert!(error.contains("harness `missing` is not configured"));
    assert!(error.contains("available harnesses: none"));
}

#[test]
fn checked_in_harness_example_remains_valid() {
    let example = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/jevia.toml");
    let input = fs::read_to_string(example).expect("example config is readable");
    let config = jevia_core::Config::from_toml(&input).expect("example config is valid");

    let harness = config
        .harnesses
        .get("agent")
        .expect("example defines the agent harness");
    assert_eq!(harness.command, "my-agent");
    assert_eq!(harness.models.len(), config.tiers.len());
    assert_eq!(
        harness
            .verification
            .as_ref()
            .expect("example defines verification")
            .command,
        "cargo"
    );
}

#[test]
fn doctor_reports_malformed_history_without_rewriting_it() {
    let directory = tempdir().expect("temporary directory");
    let mut init = Command::cargo_bin("jevia").expect("binary is built");
    init.current_dir(directory.path())
        .arg("init")
        .assert()
        .success();
    let history = directory.path().join(".jevia/runs.jsonl");
    let malformed = "{not valid json}\n";
    fs::write(&history, malformed).expect("malformed history is written");

    let mut doctor = Command::cargo_bin("jevia").expect("binary is built");
    let output = doctor
        .current_dir(directory.path())
        .env("TYPESAFE_API_KEY", "test-key")
        .arg("doctor")
        .assert()
        .failure()
        .get_output()
        .stderr
        .clone();
    let error = String::from_utf8(output).expect("stderr is UTF-8");

    assert!(error.contains("invalid run record on line 1"));
    assert_eq!(
        fs::read_to_string(history).expect("history remains readable"),
        malformed
    );
}