malvin 0.2.5

Non-interactive research and coding agent
use super::format_line_stdout_ansi;
use super::{
    WHO_B, WHO_U, init_stdout_style_for_test, print_outgoing_prompt_log, set_stdout_log_path,
};

#[test]
fn ansi_error_and_warning_color_entire_display_line() {
    use crate::terminal_palette::{ANSI_RESET, ansi_error, ansi_warning};

    let err = format_line_stdout_ansi(super::ERROR_WHO, "ACP failed");
    let coral_pos = err.find(ansi_error()).expect("coral");
    let reset_pos = err.rfind(ANSI_RESET).expect("reset");
    let payload_pos = err.find("ACP failed").expect("payload");
    assert_eq!(
        err.matches(ANSI_RESET).count(),
        1,
        "single trailing reset: {err:?}"
    );
    assert!(
        coral_pos < payload_pos && payload_pos < reset_pos,
        "error tag and payload must stay inside coral span: {err:?}"
    );
    let warn = format_line_stdout_ansi(super::WARNING_WHO, "disk low");
    let amber_pos = warn.find(ansi_warning()).expect("amber");
    let reset_pos = warn.rfind(ANSI_RESET).expect("reset");
    let payload_pos = warn.find("disk low").expect("payload");
    assert_eq!(
        warn.matches(ANSI_RESET).count(),
        1,
        "single trailing reset: {warn:?}"
    );
    assert!(
        amber_pos < payload_pos && payload_pos < reset_pos,
        "warning tag and payload must stay inside amber span: {warn:?}"
    );
}

#[test]
fn ansi_thought_tag_uses_uniform_dim_grey() {
    use crate::terminal_palette::{ANSI_DIM, ansi_tool_name, ansi_who_tag};

    let line = format_line_stdout_ansi(WHO_B, "fail with max_abs=1.0.");
    assert!(
        line.contains("b| "),
        "thought display must space after pipe; got {line:?}"
    );
    assert!(line.contains(ANSI_DIM));
    assert!(!line.contains(ansi_who_tag()));
    assert!(!line.contains(ansi_tool_name()));
    let dim_start = line.find(ANSI_DIM).expect("dim");
    assert!(line[dim_start..].contains("fail with max_abs=1.0."));
}

#[test]
fn outgoing_prompt_log_who_tag_uses_stem_bracket_keeps_md() {
    let _guard = super::STDOUT_LOG_TEST_LOCK
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner);
    let tmp = tempfile::tempdir().expect("tempdir");
    let path = tmp.path().join("stdout.log");
    set_stdout_log_path(Some(path.clone()));
    init_stdout_style_for_test(false);
    crate::output::enable_stdout_capture();
    print_outgoing_prompt_log("bug_fix", "bug_fix.md");
    let live = crate::output::take_captured_stdout();
    set_stdout_log_path(None);
    let text = std::fs::read_to_string(path).expect("read stdout log");
    let delim = super::format_who_tag_delim(WHO_U);
    assert!(
        text.contains(&format!("{delim}[bug_fix.md...]")),
        "expected user who tag and .md bracket payload: {text:?}"
    );
    assert!(
        !text.contains(">bug_fix"),
        "who tag must not include legacy directional stem: {text:?}"
    );
    assert!(
        live.is_empty(),
        "print_outgoing_prompt_log must not print live terminal lines; got {live:?}"
    );
}

#[test]
fn defer_stdout_hooks_route_through_active_sink() {
    use std::path::PathBuf;
    use std::sync::Arc;

    let _guard = super::STDOUT_LOG_TEST_LOCK
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner);
    let shared = Arc::new(std::sync::Mutex::new(
        crate::deferred_log::DeferredLogSink::for_prompt("fmt_hook".to_string(), PathBuf::new())
            .expect("defer sink"),
    ));
    crate::deferred_log::register_active_sink(Arc::clone(&shared));
    crate::deferred_log::install_stdout_hooks();
    assert!(super::try_defer_tagged_stdout("d", "l"));
    assert!(crate::output::stdout_defer::try_defer_heartbeat(
        "hb-d", "hb-l"
    ));
    crate::deferred_log::unregister_active_sink();
    assert!(!super::try_defer_tagged_stdout("d", "l"));
    assert!(!crate::output::stdout_defer::try_defer_heartbeat(
        "hb-d", "hb-l"
    ));
}