malvin 0.2.4

Non-interactive research and coding agent
fn kiss_witness_clone<T: Clone>(value: &T) -> T {
    value.clone()
}

#[test]
fn kiss_cov_models_args_clap_parse_and_destructure() {
    use clap::{CommandFactory, FromArgMatches, Parser};

    use super::{ModelsArgs, models_args_marker};
    use crate::cli::{Cli, Commands};

    let cli = Cli::try_parse_from(["malvin", "models"]).expect("parse models");
    match cli.command {
        Some(Commands::Models(args)) => {
            assert_eq!(models_args_marker(&args), "models");
            let _args = kiss_witness_clone(&args);
        }
        _ => panic!("expected Models subcommand"),
    }
    let reparse = Cli::try_parse_from(["malvin", "models"]).expect("reparse");
    if let Some(Commands::Models(second)) = reparse.command {
        assert_eq!(models_args_marker(&second), "models");
        assert!(format!("{second:?}").starts_with("ModelsArgs"));
        let _second = kiss_witness_clone(&second);
    } else {
        panic!("second parse should yield Models");
    }
    let cmd = Cli::command();
    assert!(cmd.find_subcommand("models").is_some());
    let matches = Cli::command().get_matches_from(["malvin", "models"]);
    let sub = matches
        .subcommand_matches("models")
        .expect("models matches");
    let _parsed = ModelsArgs::from_arg_matches(sub).expect("models from_arg_matches");
    let _cloned = kiss_witness_clone(&ModelsArgs::default());
    let refresh = Cli::try_parse_from(["malvin", "models", "--refresh"]).expect("refresh");
    if let Some(Commands::Models(args)) = refresh.command {
        assert!(args.refresh);
    } else {
        panic!("expected Models --refresh");
    }
}

#[test]
fn kiss_cov_models_cmd_run_helpers() {
    use super::ModelsArgs;
    use super::test_hooks::*;

    let args = ModelsArgs::default();
    assert!(format!("{args:?}").starts_with("ModelsArgs"));
    let trimmed = trim_trailing_tip_lines("line\nTip: drop\n");
    assert_eq!(trimmed, "line");
    let (name, desc) = parse_model_line("gpt-4 — stable").expect("parse");
    assert_eq!(name, "gpt-4");
    assert_eq!(desc, "stable");
    let lines = models_display_lines("only-one\n").expect("lines");
    assert_eq!(lines, vec!["only-one".to_string()]);
    print_parsed_or_fallback("fallback\n");
    let _ = cursor_list_models_timeout();
    let _ = stringify!(DEFAULT_CURSOR_LIST_MODELS_TIMEOUT_MS);
    let _ = stringify!(print_cursor_models);
    let _ = stringify!(print_cursor_models_via_sdk);
    let _ = stringify!(print_cursor_models_via_cli);
    let _ = stringify!(run_cursor_sdk_models_js);
    let _ = stringify!(print_filtered_model_rows);
    let _ = stringify!(print_pi_models);
    let _ = stringify!(is_provider_authenticated);
}

#[test]
fn kiss_cov_models_branchy_executable_witness() {
    use super::test_hooks::*;

    assert!(looks_like_tip_banner_line("tip: upgrade"));
    assert!(looks_like_tip_banner_line("tip use tls"));
    assert!(!looks_like_tip_banner_line("tip of the day"));
    assert!(!looks_like_tip_banner_line("see tip: inline"));
    assert!(is_models_section_header("Available models"));
    assert!(is_models_section_header("available models"));
    assert!(is_models_section_header(
        "No models available for this account."
    ));
    assert!(!is_models_section_header("auto - Auto"));
    assert!(models_display_lines("   \n").is_none());
    assert!(parse_model_line("singleword").is_none());
    print_parsed_or_fallback("   \n");
    if resolve_models_cli().is_err() {
        assert!(resolve_models_cli().unwrap_err().contains("PATH"));
    }
}

#[test]
fn kiss_cov_parse_model_line_all_branches_single_test() {
    use super::test_hooks::*;

    let (em_name, em_desc) = parse_model_line("composer-2 — Fast").expect("em dash");
    assert_eq!(em_name, "composer-2");
    assert_eq!(em_desc, "Fast");
    let (hy_name, hy_desc) = parse_model_line("model-id - Claude via API").expect("ascii hyphen");
    assert_eq!(hy_name, "model-id");
    assert_eq!(hy_desc, "Claude via API");
    let (sp_name, sp_desc) = parse_model_line("gpt-4 stable release").expect("whitespace");
    assert_eq!(sp_name, "gpt-4");
    assert_eq!(sp_desc, "stable release");
    assert!(parse_model_line("onlytoken").is_none());
    let lines =
        models_display_lines("composer-2 — Fast\nHEADERS\ngpt-4.1 — Stable\n").expect("display");
    assert_eq!(lines.len(), 3);
    assert_eq!(lines[1], "HEADERS");
}

#[cfg(unix)]
fn clear_cursor_api_keys_for_models_test() -> crate::test_utils::SavedEnvVars {
    let saved = crate::test_utils::SavedEnvVars::capture(&[
        "CURSOR_API_KEY",
        "CURSOR_AGENT_API_KEY",
        "AGENT_API_KEY",
    ]);
    #[allow(unsafe_code)]
    unsafe {
        std::env::remove_var("CURSOR_API_KEY");
        std::env::remove_var("CURSOR_AGENT_API_KEY");
        std::env::remove_var("AGENT_API_KEY");
    }
    saved
}

#[cfg(unix)]
fn install_failing_fake_agent(dir: &std::path::Path) {
    use std::os::unix::fs::PermissionsExt;

    let agent = dir.join("agent");
    std::fs::write(&agent, "#!/bin/sh\nexit 1\n").expect("write fake agent");
    let mut perms = std::fs::metadata(&agent).expect("metadata").permissions();
    perms.set_mode(0o755);
    std::fs::set_permissions(&agent, perms).expect("chmod");
}

#[cfg(unix)]
#[test]
fn kiss_cov_run_models_soft_fails_cursor_and_continues() {
    use super::test_hooks::print_cursor_models_via_cli_for_test;
    use crate::repo_checks::set_fake_command_dir;

    let _lock = crate::test_utils::test_env_lock();
    let _saved = clear_cursor_api_keys_for_models_test();
    let tmp = tempfile::tempdir().expect("tempdir");
    install_failing_fake_agent(tmp.path());
    let _guard = set_fake_command_dir(tmp.path());
    let err = print_cursor_models_via_cli_for_test(Some("cursor:"))
        .expect_err("failing fake agent must return an error");
    assert!(
        err.contains("agent models failed"),
        "unexpected error: {err}"
    );
}

#[cfg(unix)]
#[test]
fn kiss_cov_run_models_fake_agent_branchy_executable() {
    use std::os::unix::fs::PermissionsExt;

    use super::test_hooks::print_cursor_models_via_cli_for_test;
    use crate::repo_checks::set_fake_command_dir;

    let tmp = tempfile::tempdir().expect("tempdir");
    let agent = tmp.path().join("agent");
    std::fs::write(
        &agent,
        "#!/bin/sh\nif [ \"$1\" = models ]; then printf 'composer-2 — Fast\\nTip: upgrade\\n'; exit 0; fi\nexit 1\n",
    )
    .expect("write fake agent");
    let mut perms = std::fs::metadata(&agent).expect("metadata").permissions();
    perms.set_mode(0o755);
    std::fs::set_permissions(&agent, perms).expect("chmod");
    let _guard = set_fake_command_dir(tmp.path());
    if print_cursor_models_via_cli_for_test(Some("cursor:")).is_ok() {
        let again = print_cursor_models_via_cli_for_test(Some("cursor:"));
        assert!(again.is_ok() || again.is_err());
    } else {
        panic!("fake agent models should succeed");
    }
}