use super::support::*;
use super::*;
#[test]
fn fast_mode_submit_commands_gate_only_mutations() {
let commands = CommandRegistry::mvp();
assert!(matches!(
tui_submit_command("/fast", &commands),
TuiSubmitCommand::Fast(crate::commands::FastModeCommand::Toggle)
));
assert!(matches!(
tui_submit_command("/fast on", &commands),
TuiSubmitCommand::Fast(crate::commands::FastModeCommand::On)
));
assert!(matches!(
tui_submit_command("/fast off", &commands),
TuiSubmitCommand::Fast(crate::commands::FastModeCommand::Off)
));
assert!(matches!(
tui_submit_command("/fast status", &commands),
TuiSubmitCommand::Fast(crate::commands::FastModeCommand::Status)
));
assert!(matches!(
tui_submit_command("/fast later", &commands),
TuiSubmitCommand::FastUsage
));
assert!(TuiSubmitCommand::Fast(crate::commands::FastModeCommand::Toggle).requires_idle());
assert!(!TuiSubmitCommand::Fast(crate::commands::FastModeCommand::Status).requires_idle());
assert!(!TuiSubmitCommand::FastUsage.requires_idle());
}
#[test]
fn tui_fast_mode_mutations_are_idle_only_but_status_and_usage_work_during_run() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
crate::config::set_fast_mode(&app.config.paths, true).unwrap();
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
app.active_run = true;
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
..Default::default()
};
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(
ui_state.status,
"cannot change fast mode while a prompt is running"
);
assert!(receiver.try_recv().is_err());
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
app.submit(
"/fast status".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(
ui_state.status,
"Fast mode off. Magi-code will not request Fast processing."
);
assert!(receiver.try_recv().is_err());
app.active_run = false;
app.submit("/fast".to_string(), &mut ui_state, &receiver, test_area());
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
assert!(ui_state.fast_mode_enabled);
app.submit(
"/fast later".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(ui_state.status, crate::commands::FAST_MODE_USAGE);
assert!(receiver.try_recv().is_err());
}
#[test]
fn tui_fast_mode_persistence_failure_leaves_memory_unchanged() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
std::fs::create_dir_all(&app.config.paths.root).unwrap();
std::fs::create_dir(&app.config.paths.settings_file).unwrap();
let mut ui_state = state::MissionControlState::default();
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(ui_state.status.starts_with("failed to save fast mode:"));
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
assert!(app.config.paths.settings_file.is_dir());
assert!(receiver.try_recv().is_err());
}
#[test]
fn tui_fast_mode_pending_rejects_second_mutation_and_restores_prompt() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
let mut ui_state = state::MissionControlState {
prompt_editor: state::PromptEditor::new("draft", 5),
..Default::default()
};
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert!(!app.settings.fast.enabled);
app.submit(
"/fast off".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(ui_state.status, "fast mode persistence already in progress");
assert_eq!(ui_state.prompt_plain_text(), "/fast off");
assert_eq!(ui_state.prompt_cursor(), "/fast off".len());
assert!(!app.settings.fast.enabled);
app.submit(
"ordinary prompt".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(
ui_state.status,
"fast mode persistence pending; action blocked"
);
assert_eq!(ui_state.prompt_plain_text(), "ordinary prompt");
app.submit(
"/fast status".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(
ui_state.status,
"Fast mode off. Magi-code will not request Fast processing."
);
app.submit(
"/fast invalid".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(ui_state.status, crate::commands::FAST_MODE_USAGE);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
}
#[test]
fn tui_fast_mode_toggle_uses_memory_snapshot_when_disk_differs() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
crate::config::set_fast_mode(&app.config.paths, true).unwrap();
assert!(!app.settings.fast.enabled);
let mut ui_state = state::MissionControlState::default();
app.submit("/fast".to_string(), &mut ui_state, &receiver, test_area());
assert!(!app.settings.fast.enabled);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
}
#[test]
fn tui_fast_mode_stale_or_mismatched_completion_is_ignored() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
let mut ui_state = state::MissionControlState::default();
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
let mut stale = DrainResult::default();
stale.fast_mode_persisted.push((999, true, Ok(())));
assert!(!app.handle_fast_mode_persistence_drain(&mut ui_state, &stale));
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
let mut mismatched = DrainResult::default();
mismatched.fast_mode_persisted.push((1, false, Ok(())));
assert!(!app.handle_fast_mode_persistence_drain(&mut ui_state, &mismatched));
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
assert!(ui_state.fast_mode_enabled);
}
#[test]
fn cleanup_settles_fast_mode_persistence_before_exit() {
let temp = tempfile::TempDir::new().unwrap();
let (sender, receiver) = bounded::<TuiEvent>(1);
let mut app = test_app(&temp, sender);
app.events.send(TuiEvent::Done).unwrap();
let mut ui_state = state::MissionControlState::default();
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
app.cleanup_after_run().unwrap();
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
}
#[test]
fn tui_fast_mode_startup_uses_global_snapshot_for_enabled_and_effective() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, true).unwrap();
let (sender, _receiver) = bounded::<TuiEvent>(4);
let app = ready_codex_app(&temp, sender);
assert!(app.settings.fast.enabled);
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
..Default::default()
};
app.refresh_fast_mode_state(&mut ui_state);
assert!(ui_state.fast_mode_enabled);
assert!(ui_state.fast_mode_effective);
}
#[test]
fn tui_fast_mode_success_updates_disk_memory_and_effective_state() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, false).unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = ready_codex_app(&temp, sender);
let mut entry = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.4");
entry.service_tiers = Some(vec![crate::model_catalog::ModelCatalogServiceTier {
id: "priority".to_string(),
name: "fast".to_string(),
description: None,
}]);
crate::model_catalog::write_catalog_cache(
&app.config.paths,
crate::providers::OPENAI_CODEX_PROVIDER,
std::slice::from_ref(&entry),
)
.unwrap();
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
pending_model_catalog_request_id: Some(1),
..Default::default()
};
let mut catalog_drain = DrainResult::default();
catalog_drain.model_catalog_loaded.push((
1,
Ok(crate::model_catalog::CatalogForUi {
entries: vec![entry],
stale: false,
notice: None,
}),
5,
));
assert!(app.handle_model_catalog_drain(&mut ui_state, &catalog_drain));
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert!(!app.settings.fast.enabled);
assert!(!ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
assert!(ui_state.fast_mode_enabled);
assert!(ui_state.fast_mode_effective);
assert!(crate::config::fast_mode_enabled(&app.config.paths).unwrap());
assert_eq!(
ui_state.status,
"Fast mode on. openai-codex/gpt-5.4 will request Fast processing.\nEligible requests may have higher provider pricing."
);
}
#[test]
fn tui_fast_mode_off_completion_clears_observations() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, true).unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = ready_codex_app(&temp, sender);
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
..Default::default()
};
ui_state.apply_output_event(&OutputEvent::FastObservation {
provider_id: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
requested_service_tier: "priority".to_string(),
outcome: crate::fast::FastOutcome::Confirmed,
request_sequence: 1,
run_order: Some(1),
});
assert!(ui_state.selected_fast_observation().is_some());
app.submit(
"/fast off".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(!app.settings.fast.enabled);
assert!(ui_state.selected_fast_observation().is_none());
assert_eq!(
ui_state.status,
"Fast mode off. Magi-code will not request Fast processing."
);
}
#[test]
fn tui_fast_mode_completion_reprojects_current_model_after_switch() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, false).unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = ready_codex_app(&temp, sender);
let mut supported = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.4");
supported.service_tiers = Some(vec![crate::model_catalog::ModelCatalogServiceTier {
id: "priority".to_string(),
name: "fast".to_string(),
description: None,
}]);
let mut unsupported = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.5");
unsupported.service_tiers = Some(Vec::new());
crate::model_catalog::write_catalog_cache(
&app.config.paths,
crate::providers::OPENAI_CODEX_PROVIDER,
&[supported, unsupported],
)
.unwrap();
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
..Default::default()
};
app.submit(
"/fast on".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
ui_state.model = "gpt-5.5".to_string();
apply_fast_mode_completion(&mut app, &mut ui_state, &receiver);
assert!(app.settings.fast.enabled);
assert_eq!(ui_state.model, "gpt-5.5");
assert!(ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
assert_eq!(
ui_state.status,
"Fast remains on, but openai-codex/gpt-5.5 has no configured Fast support."
);
}
#[test]
fn tui_fast_mode_model_switch_uses_provider_cache_without_aggregate() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, true).unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = ready_codex_app(&temp, sender);
let mut target = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.5");
target.service_tiers = Some(Vec::new());
crate::model_catalog::write_catalog_cache(
&app.config.paths,
crate::providers::OPENAI_CODEX_PROVIDER,
std::slice::from_ref(&target),
)
.unwrap();
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.4".to_string(),
..Default::default()
};
app.refresh_fast_mode_state(&mut ui_state);
assert!(ui_state.fast_mode_enabled);
assert!(ui_state.fast_mode_effective);
app.select_model("openai-codex/gpt-5.5", &mut ui_state);
reap_model_selection_worker(&mut app, &mut ui_state, &receiver);
assert_eq!(ui_state.provider, crate::providers::OPENAI_CODEX_PROVIDER);
assert_eq!(ui_state.model, "gpt-5.5");
assert!(ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
assert!(app.settings.fast.enabled);
}
#[test]
fn tui_fast_mode_catalog_refresh_keeps_enabled_and_effective_synchronized() {
let temp = tempfile::TempDir::new().unwrap();
let paths = test_paths(temp.path());
crate::config::set_fast_mode(&paths, true).unwrap();
let (sender, _receiver) = bounded::<TuiEvent>(4);
let mut app = ready_codex_app(&temp, sender);
let mut ui_state = state::MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5.5".to_string(),
pending_model_catalog_request_id: Some(1),
..Default::default()
};
app.refresh_fast_mode_state(&mut ui_state);
assert!(ui_state.fast_mode_enabled);
assert!(ui_state.fast_mode_effective);
let mut standard = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.5");
standard.service_tiers = Some(Vec::new());
crate::model_catalog::write_catalog_cache(
&app.config.paths,
crate::providers::OPENAI_CODEX_PROVIDER,
std::slice::from_ref(&standard),
)
.unwrap();
let mut standard_drain = DrainResult::default();
standard_drain.model_catalog_loaded.push((
1,
Ok(crate::model_catalog::CatalogForUi {
entries: vec![standard],
stale: false,
notice: None,
}),
5,
));
assert!(app.handle_model_catalog_drain(&mut ui_state, &standard_drain));
assert!(ui_state.fast_mode_enabled);
assert!(!ui_state.fast_mode_effective);
let mut fast = crate::model_catalog::ModelCatalogEntry::new_codex("gpt-5.5");
fast.service_tiers = Some(vec![crate::model_catalog::ModelCatalogServiceTier {
id: "priority".to_string(),
name: "fast".to_string(),
description: None,
}]);
crate::model_catalog::write_catalog_cache(
&app.config.paths,
crate::providers::OPENAI_CODEX_PROVIDER,
std::slice::from_ref(&fast),
)
.unwrap();
let mut fast_drain = DrainResult::default();
fast_drain.model_catalog_loaded.push((
2,
Ok(crate::model_catalog::CatalogForUi {
entries: vec![fast],
stale: false,
notice: None,
}),
5,
));
assert!(app.handle_model_catalog_drain(&mut ui_state, &fast_drain));
assert!(ui_state.fast_mode_enabled);
assert!(ui_state.fast_mode_effective);
}