ralph-workflow 0.7.18

PROMPT-driven multi-agent orchestrator for git repos
Documentation
use super::*;

#[test]
fn test_merge_with_content_resolves_named_chains_and_drain_bindings() {
    let global = UnifiedConfig::default();

    let local_toml = r#"
[agent_chains]
shared_dev = ["codex", "claude"]
shared_review = ["claude"]

[agent_drains]
planning = "shared_dev"
development = "shared_dev"
analysis = "shared_dev"
review = "shared_review"
fix = "shared_review"
commit = "shared_review"
"#;

    let local = UnifiedConfig::load_from_content(local_toml).unwrap();
    let merged = global.merge_with_content(local_toml, &local);
    let resolved = merged
        .resolve_agent_drains()
        .expect("named chains and drain bindings should resolve");

    assert_eq!(
        resolved
            .binding(crate::agents::AgentDrain::Planning)
            .expect("planning drain")
            .chain_name,
        "shared_dev"
    );
    assert_eq!(
        resolved
            .binding(crate::agents::AgentDrain::Fix)
            .expect("fix drain")
            .chain_name,
        "shared_review"
    );
}

#[test]
fn test_merge_with_content_general_retry_settings_override_global() {
    let global = UnifiedConfig {
        general: GeneralConfig {
            max_retries: 3,
            retry_delay_ms: 1_000,
            backoff_multiplier: 2.0,
            max_backoff_ms: 60_000,
            max_cycles: 3,
            ..Default::default()
        },
        ..Default::default()
    };

    let local_toml = r"
[general]
max_retries = 5
retry_delay_ms = 2500
max_cycles = 6
";

    let local = UnifiedConfig::load_from_content(local_toml).unwrap();
    let merged = global.merge_with_content(local_toml, &local);

    assert_eq!(merged.general.max_retries, 5);
    assert_eq!(merged.general.retry_delay_ms, 2_500);
    assert!((merged.general.backoff_multiplier - 2.0).abs() < f64::EPSILON);
    assert_eq!(merged.general.max_backoff_ms, 60_000);
    assert_eq!(merged.general.max_cycles, 6);
}

#[test]
fn test_resolve_agent_drains_checked_uses_general_provider_fallback() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chains]
shared_dev = ["codex"]
shared_review = ["claude"]

[agent_drains]
planning = "shared_dev"
development = "shared_dev"
analysis = "shared_dev"
review = "shared_review"
fix = "shared_review"
commit = "shared_review"

[general.provider_fallback]
opencode = ["-m opencode/glm-4.7-free"]
"#,
    )
    .unwrap();

    let resolved = config
        .resolve_agent_drains_checked()
        .expect("named drain config should resolve")
        .expect("resolved drains should exist");

    assert_eq!(
        resolved.provider_fallback.get("opencode"),
        Some(&vec!["-m opencode/glm-4.7-free".to_string()])
    );
}

#[test]
fn test_resolve_agent_drains_checked_accepts_removed_legacy_agent_chain() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chain]
developer = ["codex"]
"#,
    )
    .unwrap();

    let resolved = config
        .resolve_agent_drains_checked()
        .expect("legacy agent_chain should remain compatible")
        .expect("legacy agent_chain should resolve drains");

    assert_eq!(
        resolved
            .binding(crate::agents::AgentDrain::Planning)
            .expect("planning drain")
            .agents,
        vec!["codex"]
    );
}

#[test]
fn test_resolve_agent_drains_checked_suggests_agent_chains_for_singular_typo() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chain]
shared_dev = ["codex"]

[agent_drains]
planning = "shared_dev"
development = "shared_dev"
"#,
    )
    .unwrap();

    let error = config
        .resolve_agent_drains_checked()
        .expect_err("singular agent_chain typo should fail");

    assert!(
        matches!(
            error,
            crate::config::unified::types::ResolveDrainError::SingularAgentChainWithDrains
        ),
        "expected SingularAgentChainWithDrains variant, got: {error}"
    );
    assert!(error.to_string().contains("did you mean [agent_chains]?"));
}

#[test]
fn test_resolve_agent_drains_checked_rejects_conflicting_legacy_and_named_chain_names() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chain]
developer = ["codex"]

[agent_chains]
developer = ["claude"]

[agent_drains]
planning = "developer"
development = "developer"
analysis = "developer"
review = "developer"
fix = "developer"
commit = "developer"
"#,
    )
    .unwrap();

    let error = config
        .resolve_agent_drains_checked()
        .expect_err("conflicting chain names should fail");

    assert!(
        matches!(
            error,
            crate::config::unified::types::ResolveDrainError::ConflictingLegacyChainNames { .. }
        ),
        "expected ConflictingLegacyChainNames variant, got: {error}"
    );
    assert!(error
        .to_string()
        .contains("conflicting agent chain definitions"));
    assert!(error.to_string().contains("developer"));
}

#[test]
fn test_resolve_agent_drains_checked_rejects_legacy_role_combined_with_named_schema() {
    // [agent_chain] has legacy developer role AND [agent_chains] has non-conflicting
    // named keys → LegacyRoleCombinedWithNamedSchema
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chain]
developer = ["codex"]

[agent_chains]
shared_review = ["claude"]

[agent_drains]
review = "shared_review"
fix = "shared_review"
commit = "shared_review"
planning = "shared_review"
development = "shared_review"
analysis = "shared_review"
"#,
    )
    .unwrap();

    let error = config
        .resolve_agent_drains_checked()
        .expect_err("mixing legacy roles with named schema should fail");

    assert!(
        matches!(
            error,
            crate::config::unified::types::ResolveDrainError::LegacyRoleCombinedWithNamedSchema
        ),
        "expected LegacyRoleCombinedWithNamedSchema variant, got: {error}"
    );
}

#[test]
fn test_resolve_agent_drains_checked_rejects_unknown_builtin_drain() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chains]
shared_dev = ["codex"]

[agent_drains]
planning = "shared_dev"
not_a_real_drain = "shared_dev"
"#,
    )
    .unwrap();

    let error = config
        .resolve_agent_drains_checked()
        .expect_err("unknown drain name should fail");

    assert!(
        matches!(
            error,
            crate::config::unified::types::ResolveDrainError::UnknownBuiltinDrain { .. }
        ),
        "expected UnknownBuiltinDrain variant, got: {error}"
    );
}

#[test]
fn test_resolve_agent_drains_checked_rejects_unknown_chain_reference() {
    let config: UnifiedConfig = toml::from_str(
        r#"
[agent_chains]
shared_dev = ["codex"]

[agent_drains]
planning = "nonexistent_chain"
"#,
    )
    .unwrap();

    let error = config
        .resolve_agent_drains_checked()
        .expect_err("reference to missing chain should fail");

    assert!(
        matches!(
            error,
            crate::config::unified::types::ResolveDrainError::UnknownChainReference { .. }
        ),
        "expected UnknownChainReference variant, got: {error}"
    );
}