malvin 0.2.6

Non-interactive research and coding agent
use super::backend_error_tracker::{
    BackendErrorTracker, MAX_CONSECUTIVE_SAME_BACKEND_ERRORS,
    format_backend_consecutive_error_message,
};

#[test]
fn tracker_starts_with_zero_count_and_no_error() {
    let tracker = BackendErrorTracker::default();
    assert_eq!(tracker.consecutive_count(), 0);
    assert_eq!(tracker.last_error(), None);
    assert!(!tracker.should_stop_and_exit());
}

#[test]
fn tracker_increments_for_same_error_and_triggers_at_three() {
    let mut tracker = BackendErrorTracker::new();
    assert!(!tracker.record_error("timeout"));
    assert_eq!(tracker.consecutive_count(), 1);
    assert_eq!(tracker.last_error(), Some("timeout"));

    assert!(!tracker.record_error("timeout"));
    assert_eq!(tracker.consecutive_count(), 2);

    let stop = tracker.record_error("timeout");
    assert!(stop);
    assert_eq!(tracker.consecutive_count(), 3);
    assert!(tracker.should_stop_and_exit());
    assert_eq!(MAX_CONSECUTIVE_SAME_BACKEND_ERRORS, 3);
}

#[test]
fn tracker_resets_counter_on_different_error() {
    let mut tracker = BackendErrorTracker::new();
    assert!(!tracker.record_error("error_a"));
    assert_eq!(tracker.consecutive_count(), 1);
    assert!(!tracker.record_error("error_a"));
    assert_eq!(tracker.consecutive_count(), 2);

    assert!(!tracker.record_error("error_b"));
    assert_eq!(tracker.consecutive_count(), 1);
    assert_eq!(tracker.last_error(), Some("error_b"));
    assert!(!tracker.should_stop_and_exit());

    assert!(!tracker.record_error("error_b"));
    assert_eq!(tracker.consecutive_count(), 2);
    assert!(tracker.record_error("error_b"));
    assert_eq!(tracker.consecutive_count(), 3);
    assert!(tracker.should_stop_and_exit());
}

#[test]
fn tracker_resets_counter_on_success() {
    let mut tracker = BackendErrorTracker::new();
    assert!(!tracker.record_error("failure"));
    assert!(!tracker.record_error("failure"));
    assert_eq!(tracker.consecutive_count(), 2);

    tracker.record_success();
    assert_eq!(tracker.consecutive_count(), 0);
    assert_eq!(tracker.last_error(), None);
    assert!(!tracker.should_stop_and_exit());

    assert!(!tracker.record_error("failure"));
    assert_eq!(tracker.consecutive_count(), 1);
}

#[test]
fn tracker_treats_trimmed_whitespace_variation_as_same_error() {
    let mut tracker = BackendErrorTracker::new();
    assert!(!tracker.record_error("internal error\n"));
    assert_eq!(tracker.consecutive_count(), 1);
    assert!(!tracker.record_error("  internal error  "));
    assert_eq!(tracker.consecutive_count(), 2);
    assert!(tracker.record_error("internal error"));
    assert_eq!(tracker.consecutive_count(), 3);
    assert!(tracker.should_stop_and_exit());
}

#[test]
fn format_backend_consecutive_error_message_contains_details() {
    let msg = format_backend_consecutive_error_message("cursor", "connection lost", 3);
    assert!(msg.contains("cursor backend error repeated 3 times"));
    assert!(msg.contains("stopping and exiting"));
    assert!(msg.contains("connection lost"));
}

#[test]
fn tracker_respects_custom_max_consecutive() {
    let mut tracker = BackendErrorTracker::with_max_consecutive(2);
    assert_eq!(tracker.max_consecutive(), 2);
    assert!(!tracker.record_error("timeout"));
    assert_eq!(tracker.consecutive_count(), 1);
    assert!(tracker.record_error("timeout"));
    assert_eq!(tracker.consecutive_count(), 2);
    assert!(tracker.should_stop_and_exit());
}

#[tokio::test]
async fn client_error_tracking_stops_and_exits_on_consecutive_same_errors() {
    let model = crate::model_id::parse_model_id("cursor:auto").expect("model");
    let mut client = crate::agent_backend::new_cursor(model, crate::agent_backend::test_support::test_io());
    client.max_acp_retries = 5;

    for i in 1..5 {
        assert!(!client.record_backend_error("same error"));
        assert_eq!(client.backend_error_tracker().consecutive_count(), i);
        assert!(!client.backend_error_tracker().should_stop_and_exit());
    }
    assert!(client.record_backend_error("same error"));
    assert_eq!(client.backend_error_tracker().consecutive_count(), 5);
    assert!(client.backend_error_tracker().should_stop_and_exit());

    client.record_backend_success();
    assert_eq!(client.backend_error_tracker().consecutive_count(), 0);
    assert!(!client.backend_error_tracker().should_stop_and_exit());
}