codex-helper-core 0.15.0

Core library for codex-helper.
Documentation
use super::*;

#[test]
fn retry_profile_defaults_to_balanced_when_unset() {
    let cfg = RetryConfig::default();
    let resolved = cfg.resolve();
    assert_eq!(resolved.upstream.strategy, RetryStrategy::SameUpstream);
    assert_eq!(resolved.upstream.max_attempts, 2);
    assert_eq!(resolved.upstream.backoff_ms, 200);
    assert_eq!(resolved.upstream.backoff_max_ms, 2_000);
    assert_eq!(resolved.upstream.jitter_ms, 100);
    assert_eq!(resolved.upstream.on_status, "429,500-599,524");
    assert!(
        resolved
            .upstream
            .on_class
            .iter()
            .any(|c| c == "upstream_transport_error")
    );

    assert_eq!(resolved.route.strategy, RetryStrategy::Failover);
    assert_eq!(resolved.route.max_attempts, 2);
    assert_eq!(resolved.route.on_status, "401,403,404,408,429,500-599,524");
    assert!(
        resolved
            .route
            .on_class
            .iter()
            .any(|c| c == "routing_mismatch_capability")
    );
    assert_eq!(resolved.never_on_status, "413,415,422");
    assert!(
        resolved
            .never_on_class
            .iter()
            .any(|c| c == "client_error_non_retryable")
    );
    assert_eq!(resolved.cloudflare_challenge_cooldown_secs, 300);
    assert_eq!(resolved.cloudflare_timeout_cooldown_secs, 60);
    assert_eq!(resolved.transport_cooldown_secs, 30);
    assert_eq!(resolved.cooldown_backoff_factor, 1);
    assert_eq!(resolved.cooldown_backoff_max_secs, 600);
    assert!(!resolved.allow_cross_station_before_first_output);
}

#[test]
fn retry_profile_cost_primary_sets_probe_back_defaults() {
    let cfg = RetryConfig {
        profile: Some(RetryProfileName::CostPrimary),
        ..RetryConfig::default()
    };
    let resolved = cfg.resolve();
    assert_eq!(resolved.route.strategy, RetryStrategy::Failover);
    assert_eq!(resolved.cooldown_backoff_factor, 2);
    assert_eq!(resolved.cooldown_backoff_max_secs, 900);
    assert_eq!(resolved.transport_cooldown_secs, 30);
    assert!(resolved.allow_cross_station_before_first_output);
}

#[test]
fn retry_profile_aggressive_failover_enables_broader_failover_with_guardrails() {
    let cfg = RetryConfig {
        profile: Some(RetryProfileName::AggressiveFailover),
        ..RetryConfig::default()
    };
    let resolved = cfg.resolve();
    assert_eq!(resolved.route.max_attempts, 3);
    assert_eq!(resolved.route.strategy, RetryStrategy::Failover);
    assert_eq!(resolved.route.on_status, "401,403,404,408,429,500-599,524");
    assert!(
        resolved
            .route
            .on_class
            .iter()
            .any(|c| c == "routing_mismatch_capability")
    );
    assert_eq!(resolved.never_on_status, "413,415,422");
    assert!(
        resolved
            .never_on_class
            .iter()
            .any(|c| c == "client_error_non_retryable")
    );
    assert!(resolved.allow_cross_station_before_first_output);
}

#[test]
fn retry_profile_allows_explicit_overrides() {
    let cfg = RetryConfig {
        profile: Some(RetryProfileName::SameUpstream),
        upstream: Some(RetryLayerConfig {
            max_attempts: Some(5),
            strategy: Some(RetryStrategy::Failover),
            ..RetryLayerConfig::default()
        }),
        ..RetryConfig::default()
    };
    let resolved = cfg.resolve();
    assert_eq!(resolved.upstream.max_attempts, 5);
    assert_eq!(resolved.upstream.strategy, RetryStrategy::Failover);
    assert!(!resolved.allow_cross_station_before_first_output);
}

#[test]
fn retry_profile_allows_explicit_cross_station_override() {
    let cfg = RetryConfig {
        profile: Some(RetryProfileName::AggressiveFailover),
        allow_cross_station_before_first_output: Some(false),
        ..RetryConfig::default()
    };
    let resolved = cfg.resolve();
    assert!(!resolved.allow_cross_station_before_first_output);
}

#[test]
fn retry_profile_parses_from_toml_kebab_case() {
    let text = r#"
version = 1

[retry]
profile = "cost-primary"
"#;
    let cfg = toml::from_str::<ProxyConfig>(text).expect("toml parse");
    assert_eq!(cfg.retry.profile, Some(RetryProfileName::CostPrimary));
}

#[test]
fn retry_config_rejects_retired_flat_fields() {
    let text = r#"
version = 1

[retry]
max_attempts = 5
"#;
    let err = toml::from_str::<ProxyConfig>(text).expect_err("retired flat retry field");

    assert!(err.to_string().contains("unknown field `max_attempts`"));
}