use std::sync::Arc;
use tempfile::TempDir;
use super::manager::{ManagedError, ManagedTmuxDriver, SessionManager};
use super::record::ManagedSessionState;
use super::tests::make_manager;
#[tokio::test]
async fn mark_runtime_exited_stopped_rejects_concurrently_decommissioned() {
let dir = TempDir::new().unwrap();
let workspace_dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let record = mgr
.create(
"task".into(),
Some(workspace_dir.path().to_owned()),
None,
Some(workspace_dir.path().to_owned()),
None,
None,
)
.await
.expect("create");
mgr.set_workspace(
&record.id,
workspace_dir.path().to_owned(),
ManagedSessionState::Active,
)
.await
.expect("set Active");
mgr.set_workspace(
&record.id,
workspace_dir.path().to_owned(),
ManagedSessionState::Decommissioned,
)
.await
.expect("set Decommissioned");
let result = mgr.mark_runtime_exited_stopped(&record.id).await;
assert!(
matches!(result, Err(ManagedError::InvalidState(_, _))),
"must reject a non-Active record instead of clobbering it: {result:?}"
);
let after = mgr
.get(&record.id)
.await
.expect("get after rejected reconcile");
assert_eq!(
after.state,
ManagedSessionState::Decommissioned,
"the concurrent decommission must win the race, not be overwritten back to Stopped"
);
}
#[tokio::test]
async fn mark_runtime_exited_stopped_still_succeeds_when_active() {
let dir = TempDir::new().unwrap();
let workspace_dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let record = mgr
.create(
"task".into(),
Some(workspace_dir.path().to_owned()),
None,
Some(workspace_dir.path().to_owned()),
None,
None,
)
.await
.expect("create");
mgr.set_workspace(
&record.id,
workspace_dir.path().to_owned(),
ManagedSessionState::Active,
)
.await
.expect("set Active");
let stopped = mgr
.mark_runtime_exited_stopped(&record.id)
.await
.expect("mark_runtime_exited_stopped on a genuinely Active record");
assert_eq!(stopped.state, ManagedSessionState::Stopped);
}
struct SiblingActiveTmuxDriver;
impl ManagedTmuxDriver for SiblingActiveTmuxDriver {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Ok(())
}
fn kill_session(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_line(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Ok(())
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(String::new())
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
Ok(Vec::new())
}
fn get_pane_id(&self, _name: &str) -> Option<String> {
Some("%9-sibling-active".to_string())
}
}
#[tokio::test]
async fn mark_runtime_exited_stopped_never_overwrites_known_pane_id() {
let dir = TempDir::new().unwrap();
let driver: Arc<dyn ManagedTmuxDriver> = Arc::new(SiblingActiveTmuxDriver);
let mgr = SessionManager::new(dir.path(), driver)
.await
.expect("manager");
let record = mgr
.create(
"task".into(),
Some(std::path::PathBuf::from("/tmp/sibling-active-test")),
None,
None,
None,
None,
)
.await
.expect("create");
let original_pane_id = "%0-original".to_string();
{
let mut seeded = mgr.get(&record.id).await.expect("get");
seeded.pane_id = Some(original_pane_id.clone());
seeded.state = ManagedSessionState::Active;
mgr.store
.write()
.await
.upsert(seeded)
.await
.expect("seed known-good pane_id");
}
let stopped = mgr
.mark_runtime_exited_stopped(&record.id)
.await
.expect("mark_runtime_exited_stopped");
assert_eq!(
stopped.pane_id.as_deref(),
Some(original_pane_id.as_str()),
"a known-good pane_id must NEVER be overwritten by the session-scoped \
re-derive, even when a sibling window is tmux-active at reconcile time \
(driver reports a different id: {:?})",
SiblingActiveTmuxDriver.get_pane_id(&stopped.tmux_name)
);
}
#[tokio::test]
async fn mark_runtime_exited_stopped_backfills_when_pane_id_absent() {
let dir = TempDir::new().unwrap();
let driver: Arc<dyn ManagedTmuxDriver> = Arc::new(SiblingActiveTmuxDriver);
let mgr = SessionManager::new(dir.path(), driver)
.await
.expect("manager");
let record = mgr
.create(
"task".into(),
Some(std::path::PathBuf::from("/tmp/backfill-test")),
None,
None,
None,
None,
)
.await
.expect("create");
mgr.set_workspace(
&record.id,
std::path::PathBuf::from("/tmp/backfill-test"),
ManagedSessionState::Active,
)
.await
.expect("set Active");
let stopped = mgr
.mark_runtime_exited_stopped(&record.id)
.await
.expect("mark_runtime_exited_stopped");
assert_eq!(
stopped.pane_id.as_deref(),
Some("%9-sibling-active"),
"an absent pane_id must still be backfilled from the driver"
);
}