malvin 0.2.9

Non-interactive research and coding agent
use super::{
    assemble_user_identity, format_current_state, format_elapsed_line, format_local_datetime,
    format_retry_line, format_sandbox_memory_line, format_user_identity,
};
use std::time::Duration;

fn format_user_identity_includes_name() {
    let id = format_user_identity();
    assert!(!id.is_empty());
    #[cfg(unix)]
    {
        assert!(id.contains("uid "));
        assert!(super::effective_user_id().is_some());
    }
}

fn assemble_user_identity_with_full_name() {
    assert_eq!(
        assemble_user_identity("dsweet", Some(1000), Some("David Sweet")),
        "dsweet (uid 1000, David Sweet)"
    );
}

fn assemble_user_identity_omits_redundant_full_name() {
    assert_eq!(
        assemble_user_identity("dsweet", Some(1000), Some("dsweet")),
        "dsweet (uid 1000)"
    );
}

fn assemble_user_identity_omits_empty_full_name() {
    assert_eq!(
        assemble_user_identity("dsweet", Some(1000), Some("")),
        "dsweet (uid 1000)"
    );
}

fn assemble_user_identity_without_uid() {
    assert_eq!(
        assemble_user_identity("unknown", None, Some("Bob")),
        "unknown"
    );
}

#[cfg(unix)]
fn format_user_identity_includes_gecos_full_name_when_distinct() {
    let id = format_user_identity();
    let uid = super::effective_user_id().expect("uid");
    if let Some(full_name) = super::passwd_gecos_full_name(uid)
        && full_name != std::env::var("USER").unwrap_or_default()
    {
        assert!(
            id.contains(&full_name),
            "expected full name {full_name:?} in {id:?}"
        );
    }
}

#[cfg(unix)]
fn current_sandbox_rss_bytes_when_agent_registered() {
    crate::active_agent_heartbeat::clear_active_agent_process_groups_for_test();
    let pgid = std::process::id();
    let baseline = crate::acp::snapshot_pids();
    crate::active_agent_heartbeat::register_active_agent_process_group(Some(pgid), baseline);
    let line = format_sandbox_memory_line(std::path::Path::new("."));
    assert!(line.contains("in use"));
    crate::active_agent_heartbeat::unregister_active_agent_process_group(Some(pgid));
    crate::active_agent_heartbeat::clear_active_agent_process_groups_for_test();
}

fn infer_gate_retry_reasons_empty_without_artifacts() {
    assert!(super::infer_gate_retry_reasons(None, 2).is_empty());
}

fn infer_gate_retry_reasons_empty_for_first_iteration() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let artifacts = crate::artifacts::create_run_artifacts_from_text("code", Some(tmp.path()))
        .expect("artifacts");
    assert!(super::infer_gate_retry_reasons(Some(&artifacts), 1).is_empty());
}

fn gate_iteration_oom_killed_false_when_marker_missing() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let artifacts = crate::artifacts::create_run_artifacts_from_text("code", Some(tmp.path()))
        .expect("artifacts");
    assert!(!crate::sandbox_oom::gate_iteration_oom_killed(
        &artifacts, 1
    ));
}

fn format_local_datetime_is_non_empty() {
    assert!(!format_local_datetime().is_empty());
}

fn format_sandbox_memory_line_includes_limit_and_available() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let line = format_sandbox_memory_line(tmp.path());
    assert!(line.starts_with("Sandbox memory:"));
    assert!(line.contains("limit"));
    assert!(line.contains("available"));
}

fn format_retry_line_first_run_is_not_retry() {
    let line = format_retry_line(None, None);
    assert!(line.contains("not a retry"));
}

fn format_retry_line_first_gate_iteration_is_not_retry() {
    let line = format_retry_line(Some(1), None);
    assert!(line.contains("first outer gate-loop"));
}

fn format_current_state_joins_all_sections() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let text = format_current_state(tmp.path(), None, None);
    assert!(text.contains("User:"));
    assert!(text.contains("Date/time:"));
    assert!(text.contains("Elapsed since router session start:"));
    assert!(text.contains("Sandbox memory:"));
    assert!(text.contains("Retry:"));
}

fn format_elapsed_line_formats_hours_minutes_seconds() {
    let line = |secs| format_elapsed_line(Some(Duration::from_secs(secs)));
    assert_eq!(line(7), "Elapsed since router session start: 7s");
    assert_eq!(line(125), "Elapsed since router session start: 2m 05s");
    assert_eq!(line(3723), "Elapsed since router session start: 1h 02m 03s");
}

fn format_elapsed_line_unknown_without_start() {
    assert!(format_elapsed_line(None).contains("unknown"));
}

#[test]
fn kiss_bundled_current_state_tests() {
    format_user_identity_includes_name();
    assemble_user_identity_with_full_name();
    assemble_user_identity_omits_redundant_full_name();
    assemble_user_identity_omits_empty_full_name();
    assemble_user_identity_without_uid();
    format_user_identity_includes_gecos_full_name_when_distinct();
    current_sandbox_rss_bytes_when_agent_registered();
    infer_gate_retry_reasons_empty_without_artifacts();
    infer_gate_retry_reasons_empty_for_first_iteration();
    gate_iteration_oom_killed_false_when_marker_missing();
    format_local_datetime_is_non_empty();
    format_sandbox_memory_line_includes_limit_and_available();
    format_retry_line_first_run_is_not_retry();
    format_retry_line_first_gate_iteration_is_not_retry();
    format_current_state_joins_all_sections();
    format_elapsed_line_formats_hours_minutes_seconds();
    format_elapsed_line_unknown_without_start();
}