magi-code 0.77.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::support::*;
use super::*;
use crate::commands::BuiltInCommand;
use crate::output::ActivityStatus;
use crate::tui::input::submit_command::{
    is_new_session_command, is_quit_command, slash_command_token, unsupported_slash_command,
};

#[test]
fn summary_commands_remain_local_during_primary_run() {
    let temp = tempfile::TempDir::new().unwrap();
    let (sender, receiver) = bounded::<TuiEvent>(4);
    let mut app = test_app(&temp, sender);
    app.active_run = true;
    let mut ui_state = state::MissionControlState::default();
    for command in ["/summarize-start", "/summarize-stop"] {
        assert!(!tui_submit_command(command, &app.commands).requires_idle());
        assert!(!app.submit(command.into(), &mut ui_state, &receiver, test_area()));
        assert!(ui_state.is_summary_focused());
        assert!(app.active_run);
        assert_eq!(app.steering.pending_count(), 0);
        assert!(receiver.try_recv().is_err());
    }
    app.submit(
        "/summarize-start unexpected".into(),
        &mut ui_state,
        &receiver,
        test_area(),
    );
    assert!(ui_state.status.starts_with("usage:"));
}

#[test]
fn slash_command_recognizers_use_command_token_after_trim() {
    assert_eq!(slash_command_token("/new"), Some("/new"));
    assert_eq!(slash_command_token("  /new please  "), Some("/new"));
    assert_eq!(slash_command_token("/new\ntext"), Some("/new"));
    assert_eq!(slash_command_token("not /new"), None);
    assert!(is_new_session_command("/new"));
    assert!(is_new_session_command("  /new  "));
    assert!(is_new_session_command("/new please"));
    assert!(is_new_session_command("/new\ntext"));
    assert!(!is_new_session_command("/new-session"));
    assert!(!is_new_session_command("/setmodel"));
    assert!(is_quit_command(" /quit "));
    assert!(is_quit_command(" /quit now "));
}

#[test]
fn prune_sessions_tui_command_captures_args_and_requires_idle() {
    assert_eq!(
        tui_submit_command(" /prune-sessions 7 ", &CommandRegistry::mvp()),
        TuiSubmitCommand::PruneSessions(Some("7"))
    );
    assert_eq!(
        tui_submit_command("/prune-sessions", &CommandRegistry::mvp()),
        TuiSubmitCommand::PruneSessions(None)
    );
    let command = tui_submit_command("/prune-sessions 7", &CommandRegistry::mvp());
    assert!(command.requires_idle());
    assert_eq!(
        command.active_run_error(),
        Some("cannot prune sessions while a prompt is running")
    );
}

#[test]
fn unsupported_slash_command_recognizer_treats_all_builtins_as_supported() {
    for command in BuiltInCommand::ALL {
        let prompt = format!(" /{} ignored ", command.name());
        assert_eq!(unsupported_slash_command(&prompt), None, "{prompt}");
        assert!(
            !matches!(
                tui_submit_command(&prompt, &CommandRegistry::mvp()),
                TuiSubmitCommand::Unsupported(_)
            ),
            "{prompt}"
        );
    }
}

#[test]
fn unsupported_slash_command_recognizer_keeps_new_model_skills_system_prompt_and_quit_tokens() {
    assert_eq!(unsupported_slash_command("/compact keep file paths"), None);
    assert_eq!(unsupported_slash_command("/setmodel gpt-next"), None);
    assert_eq!(unsupported_slash_command("/skills"), None);
    assert_eq!(unsupported_slash_command("/subagents"), None);
    assert_eq!(unsupported_slash_command("/system-prompt"), None);
    assert_eq!(unsupported_slash_command("/system-prompt ignored"), None);
    assert_eq!(
        unsupported_slash_command("/skill:review"),
        Some("/skill:review")
    );
    assert_eq!(
        unsupported_slash_command("  /missing arg  "),
        Some("/missing")
    );
    assert_eq!(unsupported_slash_command("/new"), None);
    assert_eq!(unsupported_slash_command("/new extra"), None);
    assert_eq!(unsupported_slash_command("/quit"), None);
    assert_eq!(unsupported_slash_command("/quit extra"), None);
    assert_eq!(unsupported_slash_command("hello"), None);
}

#[test]
fn compact_command_uses_override_without_refreshing_missing_active_codex_auth() {
    let _env = crate::test_support::env::env_lock();
    let temp = tempfile::TempDir::new().unwrap();
    let session = SessionManager::new(temp.path().join("sessions"))
        .create()
        .unwrap();
    let (sender, receiver) = bounded::<TuiEvent>(4);
    let mut app = test_app_with_active_session(&temp, sender, Some(session.clone()));
    app.config.provider = Some(crate::providers::OPENAI_CODEX_PROVIDER.to_string());
    app.config.model = Some("active-model".to_string());
    app.config.auth = None;
    app.state.config = Some(app.config.clone());
    app.state.model = "active-model".to_string();
    app.state.auth_state = crate::config::AuthState::Missing {
        provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
    };
    let base_url = start_sse_server("tui override summary");
    write_compaction_override_settings(
        &app.config.paths,
        "local-provider",
        "summary-model",
        &base_url,
    );
    app.settings = crate::config::read_settings(&app.config.paths).unwrap();
    let mut ui_state = state::MissionControlState {
        provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
        model: "active-model".to_string(),
        provider_ready: false,
        ..Default::default()
    };

    app.start_compaction(None, &mut ui_state);

    assert!(app.worker.is_some());
    assert!(app.active_run);
    assert!(!ui_state.provider_ready);
    assert_eq!(ui_state.status, "compacting session…");
    let worker = app.worker.take().unwrap();
    worker.handle.join().unwrap();
    let result = drain_tui_events(&receiver, &mut ui_state);
    assert!(result.run_finished);
    assert!(
        ui_state.status.contains("compaction complete: session"),
        "{}",
        ui_state.status
    );
    let compaction_nodes = ui_state
        .nodes
        .values()
        .filter(|node| node.kind == crate::output::ActivityKind::Compaction)
        .collect::<Vec<_>>();
    assert_eq!(compaction_nodes.len(), 1);
    assert_eq!(compaction_nodes[0].status, ActivityStatus::Success);
    assert_eq!(compaction_nodes[0].preview, "tui override summary");
    let events = session.read_events().unwrap();
    let compaction = events
        .iter()
        .find(|event| event.event_type == "compaction")
        .unwrap();
    assert_eq!(compaction.payload["summary"], "tui override summary");
    assert_eq!(compaction.payload["provider"], "local-provider");
    assert_eq!(compaction.payload["model"], "summary-model");
}
#[test]
fn compaction_runtime_refresh_applies_refreshed_auth_to_active_runtime() {
    let temp = tempfile::TempDir::new().unwrap();
    let session = SessionManager::new(temp.path().join("sessions"))
        .create()
        .unwrap();
    let (sender, _receiver) = bounded::<TuiEvent>(2);
    let mut app = test_app_with_active_session(&temp, sender, Some(session.clone()));
    let provider = crate::providers::ANTHROPIC_PROVIDER.to_string();
    let model = "claude-test".to_string();
    let old_credential = crate::config::ProviderCredential::ApiKey {
        key: "old-key".to_string(),
    };
    app.config.provider = Some(provider.clone());
    app.config.model = Some(model.clone());
    app.config.auth = Some(old_credential.clone());
    app.state.config = Some(app.config.clone());
    app.state.model = model.clone();
    app.state.auth_state = crate::config::AuthState::Ready {
        provider: provider.clone(),
        credential: old_credential,
    };
    let mut ui_state = state::MissionControlState {
        provider: provider.clone(),
        model: model.clone(),
        provider_ready: false,
        ..Default::default()
    };

    let new_credential = crate::config::ProviderCredential::ApiKey {
        key: "new-key".to_string(),
    };
    let mut refreshed_config = app.config.clone();
    refreshed_config.auth = Some(new_credential);
    let changed = app.apply_compaction_runtime_refresh(
        &mut ui_state,
        CompactionRuntimeRefresh {
            config: refreshed_config.clone(),
            session_id: session.id().to_string(),
            provider: provider.clone(),
            model: model.clone(),
        },
    );

    assert!(changed);
    assert!(ui_state.provider_ready);
    assert_eq!(app.config, refreshed_config);
    assert_eq!(app.state.config.as_ref(), Some(&refreshed_config));
    assert!(matches!(
        &app.state.auth_state,
        crate::config::AuthState::Ready {
            provider: active_provider,
            credential: crate::config::ProviderCredential::ApiKey { key },
        } if active_provider == &provider && key == "new-key"
    ));
}

#[test]
fn compaction_settings_snapshot_keeps_tui_fast_mode_after_disk_change() {
    let temp = tempfile::TempDir::new().unwrap();
    let (sender, _receiver) = bounded::<TuiEvent>(4);
    let mut app = test_app(&temp, sender);
    app.settings.fast.enabled = true;
    std::fs::create_dir_all(&app.config.paths.root).unwrap();
    std::fs::write(
        &app.config.paths.settings_file,
        r#"{"fast":{"enabled":false}}"#,
    )
    .unwrap();

    let settings = app.compaction_settings_snapshot();

    assert!(settings.fast.enabled);
}

#[test]
fn submit_idle_only_commands_reject_while_provider_run_active() {
    let cases = [
        ("/compact", "cannot compact while a prompt is running"),
        ("/login", "cannot start login while a prompt is running"),
        ("/logout", "cannot logout while a prompt is running"),
        ("/setmodel", "cannot change model while a prompt is running"),
        (
            "/mcp",
            "cannot manage MCP servers while a prompt is running",
        ),
        ("/skills", "cannot manage skills while a prompt is running"),
        (
            "/subagents",
            "cannot manage subagents while a prompt is running",
        ),
        (
            "/sessions",
            "cannot switch sessions while a prompt is running",
        ),
        (
            "/new",
            "cannot start a new session while a prompt is running",
        ),
    ];

    for (prompt, expected) in cases {
        let temp = tempfile::TempDir::new().unwrap();
        let (sender, receiver) = bounded::<TuiEvent>(4);
        let mut app = test_app(&temp, sender);
        app.active_run = true;
        let mut ui_state = state::MissionControlState::default();

        app.submit(prompt.to_string(), &mut ui_state, &receiver, test_area());

        assert_eq!(ui_state.status, expected, "{prompt}");
        assert!(receiver.try_recv().is_err());
        assert!(app.worker.is_none());
        assert!(app.active_run, "{prompt}");
    }
}