use super::support::*;
use super::*;
use crate::tui::sessions::commands::execute_switch_session_command;
#[test]
fn sessions_command_opens_modal_without_provider_submission() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let session = manager.open("saved-session").unwrap();
session
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
session.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({}),
))
.unwrap();
let (sender, receiver) = bounded::<TuiEvent>(4);
let mut app = test_app(&temp, sender);
let mut ui_state = state::MissionControlState::default();
app.submit(
"/sessions".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert!(ui_state.session_picker_visible());
assert!(matches!(
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
TuiEvent::WorkerOutcomeReady
));
reap_owned_worker(
&mut app,
&mut ui_state,
MissionControlApp::reap_session_maintenance,
);
assert_eq!(
ui_state.modals.session_picker.as_ref().unwrap().rows.len(),
1
);
assert!(app.worker.is_none());
assert!(!app.active_run);
assert!(matches!(
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
TuiEvent::SessionPreviewLoaded { session_id, .. } if session_id == "saved-session"
));
}
#[test]
fn sessions_command_rejects_arguments_without_provider_submission() {
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(
"/sessions extra".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(ui_state.status, "usage: /sessions");
assert!(!ui_state.session_picker_visible());
assert!(app.worker.is_none());
assert!(!app.active_run);
assert!(receiver.try_recv().is_err());
}
#[test]
fn sessions_command_rejects_while_run_active() {
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(
"/sessions".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(
ui_state.status,
"cannot switch sessions while a prompt is running"
);
assert!(receiver.try_recv().is_err());
}
#[test]
fn cancelling_session_picker_preserves_active_session() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let active = manager.open("active-session").unwrap();
let target = manager.open("target-session").unwrap();
active
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
active.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({}),
))
.unwrap();
target
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
target.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({}),
))
.unwrap();
let (sender, _receiver) = bounded::<TuiEvent>(4);
let app = test_app_with_active_session(&temp, sender, Some(active.clone()));
let mut ui_state = state::MissionControlState::default();
ui_state.open_session_picker(
vec![state::SessionPickerRow {
id: target.id().to_string(),
label: target.id().to_string(),
is_current: false,
preview: None,
}],
1,
);
let action = input::handle_key(
crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Esc,
crossterm::event::KeyModifiers::NONE,
),
&mut ui_state,
);
assert_eq!(action, input::InputAction::None);
assert!(!ui_state.session_picker_visible());
assert_eq!(app.state.active_session_id(), Some(active.id()));
}
#[test]
fn rewind_result_is_ignored_after_active_session_changes() {
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));
let mut ui_state = state::MissionControlState::default();
app.submit(
"/changes".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
app.state.current_session = None;
reap_rewind_worker(&mut app, &mut ui_state, &receiver);
assert_eq!(ui_state.status, "rewind result ignored; session changed");
assert!(!ui_state.rewind_modal_visible());
assert!(receiver.try_recv().is_err());
}
#[test]
fn export_command_writes_archive_for_persisted_session() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let session = manager.create().unwrap();
session
.append(&crate::sessions::SessionEvent::new(
"user_input",
session.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({"text": "hello"}),
))
.unwrap();
let (sender, receiver) = bounded::<TuiEvent>(8);
let mut app = test_app_with_active_session(&temp, sender, Some(session.clone()));
let mut ui_state = state::MissionControlState::default();
app.submit("/export".to_string(), &mut ui_state, &receiver, test_area());
assert_eq!(ui_state.status, "exporting session…");
reap_export_worker(&mut app, &mut ui_state, &receiver);
assert!(
ui_state
.status
.starts_with("UNENCRYPTED — NOT SAFE TO SHARE; session export complete: ")
);
let exports = std::fs::read_dir(app.config.paths.root.join("exports"))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(exports.len(), 1);
assert!(exports[0].path().is_file());
}
#[test]
fn export_command_rejects_arguments_and_duplicate_requests() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let session = manager.create().unwrap();
session
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
session.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({}),
))
.unwrap();
let (sender, receiver) = bounded::<TuiEvent>(8);
let mut app = test_app_with_active_session(&temp, sender, Some(session));
let mut ui_state = state::MissionControlState::default();
app.submit(
"/export extra".to_string(),
&mut ui_state,
&receiver,
test_area(),
);
assert_eq!(ui_state.status, "usage: /export");
assert!(receiver.try_recv().is_err());
app.submit("/export".to_string(), &mut ui_state, &receiver, test_area());
app.submit("/export".to_string(), &mut ui_state, &receiver, test_area());
assert_eq!(ui_state.status, "export request already in progress");
reap_export_worker(&mut app, &mut ui_state, &receiver);
}
#[test]
fn export_result_is_ignored_after_active_session_changes() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let session = manager.create().unwrap();
session
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
session.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({}),
))
.unwrap();
let (sender, receiver) = bounded::<TuiEvent>(8);
let mut app = test_app_with_active_session(&temp, sender, Some(session));
let mut ui_state = state::MissionControlState::default();
app.submit("/export".to_string(), &mut ui_state, &receiver, test_area());
app.state.current_session = None;
reap_export_worker(&mut app, &mut ui_state, &receiver);
assert_eq!(ui_state.status, "export result ignored; session changed");
}
#[test]
fn selected_session_is_used_for_next_provider_run() {
let temp = tempfile::TempDir::new().unwrap();
let manager = SessionManager::new(temp.path().join("sessions"));
let active = manager.open("active-session").unwrap();
let target = manager.open("target-session").unwrap();
active
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
active.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({"seed":"active"}),
))
.unwrap();
target
.append(&crate::sessions::SessionEvent::new(
"diagnostic",
target.id().to_string(),
temp.path().to_path_buf(),
serde_json::json!({"seed":"target"}),
))
.unwrap();
let active_before = active.read_events().unwrap().len();
let target_before = target.read_events().unwrap().len();
let (sender, receiver) = bounded::<TuiEvent>(8);
let mut app = test_app_with_active_session(&temp, sender.clone(), Some(active.clone()));
let mut ui_state = state::MissionControlState::default();
execute_switch_session_command(
&mut app.state,
&mut ui_state,
target.id(),
&receiver,
&mut std::collections::VecDeque::new(),
)
.unwrap();
assert!(app.submit(
"next prompt".to_string(),
&mut ui_state,
&receiver,
test_area(),
));
assert!(app.worker.is_some());
assert!(app.active_run);
while app
.worker
.as_ref()
.is_some_and(|worker| !worker.handle.is_finished())
{
std::thread::yield_now();
}
assert!(drain_tui_events(&receiver, &mut ui_state).run_finished);
assert!(app.finish_worker_if_ready(&mut ui_state));
assert_eq!(app.state.active_session_id(), Some(target.id()));
assert_eq!(active.read_events().unwrap().len(), active_before);
let target_events = target.read_events().unwrap();
assert_eq!(target_events.len(), target_before + 1);
assert_eq!(target_events.last().unwrap().session_id, target.id());
assert_eq!(target_events.last().unwrap().event_type, "diagnostic");
}