malvin 0.2.5

Non-interactive research and coding agent
use crate::acp::{
    agent_error_requires_coder_session_teardown, agent_string_is_cursor_agent_busy,
    agent_string_is_cursor_http2_transport_error, agent_string_is_stale_cursor_sdk_auth,
};

#[test]
fn child_health_transport_errors_require_coder_session_teardown() {
    let _guard = crate::test_utils::test_env_lock();
    crate::test_utils::clear_test_no_real_agent_env();
    for msg in [
        "acp child process appears hung",
        "acp child process is not running",
        "acp child process is zombie",
        "acp stdout closed",
        "acp: WritableIterable is closed",
        "bridge drain timed out waiting for run_done after 1s without a bridge event (bridge quiet; likely hung or stalled)",
        "bridge timed out waiting for run_done after 1s without a bridge event (bridge quiet; likely hung or stalled)",
        "pi rpc drain timed out waiting for agent_end after 45s of silence",
        "pi rpc timed out waiting for agent_end after 45s of silence",
        "pi rpc stdout closed",
        "pi rpc write: broken pipe",
        "codex timed out waiting for turn event after 1s of silence",
        "codex stdout closed",
        "codex write: broken pipe",
        "codex flush: broken pipe",
        "codex read: connection reset",
        "codex JSON-RPC parse: expected value",
        "Agent is currently streaming; specify streamingBehavior",
        "Error: T: Connection stalled",
        "Error: RetriableError: [unavailable] PING timed out",
        "Error: RetriableError: [canceled] http/2 stream closed with error code CANCEL (0x8)",
        "Authentication",
        "AuthenticationError: If you are logged in, try logging out and back in.",
        "ERROR_NOT_LOGGED_IN",
        "[unauthenticated] Error",
        "Agent agent-7b61bfe2-fa7a-47bd-8f5b-96c158067bc8 already has active run",
    ] {
        assert!(agent_error_requires_coder_session_teardown(msg), "{msg}");
    }
    assert!(!agent_error_requires_coder_session_teardown(
        "request timed out"
    ));
}

#[test]
fn live_drain_idle_prefixes_require_coder_session_teardown() {
    for prefix in [
        crate::acp::DRAIN_IDLE_PREFIX_BRIDGE,
        crate::acp::DRAIN_IDLE_PREFIX_PI,
        crate::acp::DRAIN_IDLE_PREFIX_CODEX,
    ] {
        let msg = format!(
            "{prefix} waiting for event after 1s without a bridge event (bridge quiet; likely hung or stalled)"
        );
        assert!(agent_error_requires_coder_session_teardown(&msg), "{msg}");
    }
}

#[test]
fn cursor_agent_busy_strings_are_detected() {
    assert!(agent_string_is_cursor_agent_busy(
        "Agent agent-7b61bfe2-fa7a-47bd-8f5b-96c158067bc8 already has active run"
    ));
    assert!(agent_string_is_cursor_agent_busy("ALREADY HAS ACTIVE RUN"));
    assert!(!agent_string_is_cursor_agent_busy("bridge stdout closed"));
}

#[test]
fn mock_agent_mode_keeps_session_after_ping_retriable_error() {
    let _guard = crate::test_utils::test_env_lock();
    crate::test_utils::enable_test_fast_teardown();
    assert!(!agent_error_requires_coder_session_teardown(
        "Error: RetriableError: [unavailable] PING timed out"
    ));
    assert!(agent_error_requires_coder_session_teardown(
        "acp child process is not running"
    ));
    crate::test_utils::clear_test_no_real_agent_env();
}

#[test]
fn cursor_http2_transport_errors_are_detected() {
    assert!(agent_string_is_cursor_http2_transport_error(
        "Error: RetriableError: [unavailable] PING timed out"
    ));
    assert!(agent_string_is_cursor_http2_transport_error(
        "Error: RetriableError: [canceled] http/2 stream closed with error code CANCEL (0x8)"
    ));
    assert!(agent_string_is_cursor_http2_transport_error(
        "\n\nError: RetriableError: [canceled] http/2 stream closed with error code CANCEL (0x8)"
    ));
    assert!(!agent_string_is_cursor_http2_transport_error(
        "wrote review_requirements.json"
    ));
    assert!(!agent_string_is_cursor_http2_transport_error("ok"));
}

#[test]
fn stale_cursor_sdk_auth_strings_are_detected() {
    assert!(agent_string_is_stale_cursor_sdk_auth("Authentication"));
    assert!(agent_string_is_stale_cursor_sdk_auth("ERROR_NOT_LOGGED_IN"));
    assert!(!agent_string_is_stale_cursor_sdk_auth("request timed out"));
}