magi-code 0.80.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

#[test]
fn settings_deserialize_ignores_schema_metadata() {
    let settings: Settings = serde_json::from_str(
        r#"{"$schema":"./state/settings.schema.json","agent":{"model":{"provider":"openai-codex"}}}"#,
    )
    .unwrap();

    assert_eq!(
        settings.selected_model.provider.as_deref(),
        Some("openai-codex")
    );
}

#[test]
fn settings_default_serialization_omits_empty_groups() {
    let value = serde_json::to_value(Settings::default()).unwrap();
    assert_eq!(value, json!({"schema_version": 2}));
}

#[test]
fn hook_settings_preserve_unknown_fields_on_settings_update() {
    let temp = tempfile::TempDir::new().unwrap();
    let paths = McPaths::from_root(temp.path().join("mc"));
    fs::create_dir_all(&paths.root).unwrap();
    fs::write(
        &paths.settings_file,
        r#"{"automation":{"hooks":{"future":{"keep":true}}}}"#,
    )
    .unwrap();

    set_selected_model(&paths, "provider", "model").unwrap();

    let value = read_settings_value(&paths);
    assert_eq!(value["automation"]["hooks"]["future"]["keep"], true);
    assert_eq!(value["agent"]["model"]["provider"], "provider");
    assert_eq!(value["agent"]["model"]["model"], "model");
}

#[test]
fn hook_settings_default_without_unknown_fields_is_omitted_on_settings_update() {
    let temp = tempfile::TempDir::new().unwrap();
    let paths = McPaths::from_root(temp.path().join("mc"));
    fs::create_dir_all(&paths.root).unwrap();
    fs::write(&paths.settings_file, r#"{}"#).unwrap();

    set_selected_model(&paths, "provider", "model").unwrap();

    let value = read_settings_value(&paths);
    assert!(value.get("hooks").is_none());
}

#[test]
fn hook_settings_known_fields_override_old_raw_values_on_settings_update() {
    let temp = tempfile::TempDir::new().unwrap();
    let paths = McPaths::from_root(temp.path().join("mc"));
    fs::create_dir_all(&paths.root).unwrap();
    fs::write(
        &paths.settings_file,
        r#"{"automation":{"hooks":{"enabled":false,"future":{"keep":true}}}}"#,
    )
    .unwrap();

    update_settings_preserving_unknown_top_level_fields(&paths, |settings| {
        settings.hooks.enabled = true;
    })
    .unwrap();

    let value = read_settings_value(&paths);
    assert_eq!(value["automation"]["hooks"]["enabled"], true);
    assert_eq!(value["automation"]["hooks"]["future"]["keep"], true);
}
#[test]
fn settings_update_preserves_explicit_default_groups_and_null_values() {
    let mut raw = json!({
        "schema_version": 2,
        "agent": {
            "model": {"provider": "provider"},
            "fast": {"enabled": false},
            "context": null,
            "subagents": {}
        },
        "providers": {"custom": {}},
        "capabilities": {"tools": {"read": {}}, "mcp_approvals": {}},
        "interface": {
            "appearance": {"theme": "matrix-green", "future": null},
            "no_color": null
        },
        "future": null
    });
    let mut settings: Settings = serde_json::from_value(raw.clone()).unwrap();
    let before = settings.clone();
    settings.selected_model.model = Some("model".to_string());

    update_raw_from_settings(&mut raw, &before, &settings, SettingsScope::Global).unwrap();

    assert_eq!(raw["agent"]["model"]["model"], "model");
    assert_eq!(raw["agent"]["fast"], json!({"enabled": false}));
    assert_eq!(raw["agent"]["context"], serde_json::Value::Null);
    assert_eq!(raw["agent"]["subagents"], json!({}));
    assert_eq!(raw["providers"]["custom"], json!({}));
    assert_eq!(raw["capabilities"]["tools"]["read"], json!({}));
    assert_eq!(raw["capabilities"]["mcp_approvals"], json!({}));
    assert_eq!(
        raw["interface"]["appearance"]["future"],
        serde_json::Value::Null
    );
    assert_eq!(raw["interface"]["no_color"], serde_json::Value::Null);
    assert_eq!(raw["future"], serde_json::Value::Null);
}

#[test]
fn settings_update_removes_last_entries_from_dynamic_maps() {
    let mut raw = json!({
        "schema_version": 2,
        "agent": {"context": {
            "enabled": true,
            "max_tokens": 128000,
            "reserve_tokens": 16384,
            "keep_recent_tokens": 20000,
            "model_overrides": {
                "provider/model": {"max_tokens": 1000, "future": null}
            }
        }},
        "providers": {"custom": {
            "old": {
                "label": "Old",
                "base_url": "http://127.0.0.1:8080/v1",
                "future": {"keep": true}
            }
        }},
        "capabilities": {
            "mcp_approvals": {
                "/config/.mcp.json": {"old": true}
            },
            "lsp": {
                "enabled": true,
                "servers": {"rust": {"command": "rust-analyzer", "future": {"keep": true}}}
            }
        },
        "future_root": {"keep": true}
    });
    let mut settings: Settings = serde_json::from_value(raw.clone()).unwrap();
    let before = settings.clone();
    settings.custom_providers.clear();
    settings.mcp_approvals.clear();
    settings.lsp.servers.clear();
    settings.context.as_mut().unwrap().model_overrides.clear();

    update_raw_from_settings(&mut raw, &before, &settings, SettingsScope::Global).unwrap();

    assert!(raw.pointer("/providers/custom").is_none());
    assert!(raw.pointer("/capabilities/mcp_approvals").is_none());
    assert!(raw.pointer("/capabilities/lsp/servers").is_none());
    assert!(raw.pointer("/agent/context/model_overrides").is_none());
    assert_eq!(raw["future_root"]["keep"], true);
}

#[test]
fn shared_wire_preserves_null_empty_and_explicit_false_projections() {
    let cases = [
        (json!({}), json!({"schema_version": 2})),
        (
            json!({
                "agent": {"primary_agent": null, "context": null, "subagents": {"disabled": []}},
                "providers": {"anthropic": {"cache_ttl": null}, "custom": {}, "catalog": {"disabled": []}},
                "capabilities": {"tools": {"disabled": []}, "mcp": {}},
                "knowledge": {"skills": {"disabled": []}},
                "automation": {}, "sessions": {"retention_days": 7},
                "interface": {"no_color": null}
            }),
            json!({"schema_version": 2}),
        ),
        (
            json!({"interface": {"no_color": false, "tui": {"autocomplete": {"respects_gitignore": false}}}}),
            json!({"schema_version": 2, "interface": {"no_color": false, "tui": {"autocomplete": {"respects_gitignore": false}}}}),
        ),
        (
            json!({"interface": {"tui": {"autocomplete": {"respects_gitignore": true}}}}),
            json!({"schema_version": 2, "interface": {"tui": {}}}),
        ),
        (
            json!({"agent": {"subagents": {"execution": {"max_depth": 3}, "future": 42}},
                "interface": {"tui": {"future": true, "autocomplete": {"respects_gitignore": true, "future": 42}}}}),
            json!({"schema_version": 2, "agent": {"subagents": {"execution": {"absolute_paths": true, "max_depth": 3}, "future": 42}},
                "interface": {"tui": {"future": true, "autocomplete": {"future": 42}}}}),
        ),
    ];
    for (input, expected) in cases {
        let settings: Settings = serde_json::from_value(input.clone()).unwrap();
        assert_eq!(
            serde_json::to_value(settings).unwrap(),
            expected,
            "input: {input}"
        );
    }
}

#[test]
fn shared_wire_rejects_non_object_groups_and_non_optional_nulls() {
    for path in [
        vec!["agent"],
        vec!["providers"],
        vec!["capabilities"],
        vec!["knowledge"],
        vec!["automation"],
        vec!["sessions"],
        vec!["interface"],
        vec!["providers", "anthropic"],
        vec!["interface", "tui"],
        vec!["interface", "tui", "autocomplete"],
    ] {
        for value in [json!([]), json!(null), json!(false)] {
            let mut raw = json!({"schema_version": 2});
            super::super::json::set_path(&mut raw, &path, value).unwrap();
            assert!(
                serde_json::from_value::<Settings>(raw.clone()).is_err(),
                "accepted {raw}"
            );
        }
    }
    for raw in [
        json!({"agent": {"fast": null}}),
        json!({"agent": {"subagents": {"execution": null}}}),
        json!({"knowledge": {"skills": null}}),
        json!({"interface": {"tui": {"autocomplete": {"respects_gitignore": null}}}}),
    ] {
        assert!(
            serde_json::from_value::<Settings>(raw.clone()).is_err(),
            "accepted {raw}"
        );
    }
}

#[test]
fn settings_reject_unsupported_or_malformed_schema_versions() {
    for version in [
        json!(1),
        json!(0),
        json!(3),
        json!("2"),
        json!(null),
        json!(2.5),
    ] {
        let error =
            serde_json::from_value::<Settings>(json!({"schema_version": version})).unwrap_err();
        assert!(error.to_string().contains("schema_version"), "{error}");
    }
}

#[test]
fn settings_ignore_old_paths_instead_of_normalizing_them() {
    let settings: Settings = serde_json::from_value(json!({
        "schema_version": 2,
        "agent": {"model": {"provider": "openai-codex"}},
        "selected_model": {"model": "old-model"},
        "disabled_skills": ["old-skill"],
        "tools": {"ffgrep": {"absolute_paths": false}},
        "capabilities": {"tools": {
            "ffgrep": {"absolute_paths": false},
            "fffind": {"absolute_paths": false},
            "parallel_subagents": {"max_depth": 3}
        }}
    }))
    .unwrap();
    assert_eq!(
        settings.selected_model.provider.as_deref(),
        Some("openai-codex")
    );
    assert_eq!(settings.selected_model.model, None);
    assert!(settings.skills.disabled.is_empty());
    assert_eq!(settings.tools, ToolSettings::default());
}

#[test]
fn experimental_codex_reasoning_updates_require_explicit_opt_in() {
    assert!(
        !Settings::default()
            .openai_codex
            .experimental_reasoning_updates
    );
    let settings: Settings = serde_json::from_value(json!({
        "schema_version": 2,
        "providers": {"openai_codex": {"experimental_reasoning_updates": true}}
    }))
    .unwrap();
    assert!(settings.openai_codex.experimental_reasoning_updates);
    let roundtrip: Settings =
        serde_json::from_value(serde_json::to_value(settings).unwrap()).unwrap();
    assert!(roundtrip.openai_codex.experimental_reasoning_updates);
}