use tempfile::TempDir;
use super::manager::{ManagedError, ManagedTmuxDriver};
use super::record::{ManagedSessionId, ManagedSessionState};
use super::rename::validate_session_name;
use super::tests::{make_manager, seed_record};
#[test]
fn validate_session_name_accepts_valid_and_trims() {
assert_eq!(
validate_session_name(" tm-quiet-falcon ").unwrap(),
"tm-quiet-falcon"
);
assert_eq!(
validate_session_name("tm_worker_01").unwrap(),
"tm_worker_01"
);
}
#[test]
fn validate_session_name_rejects_empty_and_bad_chars() {
assert!(
validate_session_name(" ").is_err(),
"empty/whitespace must reject"
);
assert!(
validate_session_name("has space").is_err(),
"whitespace must reject"
);
assert!(
validate_session_name("has.dot").is_err(),
"tmux-reserved '.' must reject"
);
assert!(
validate_session_name("has:colon").is_err(),
"tmux-reserved ':' must reject"
);
assert!(
validate_session_name(&"x".repeat(65)).is_err(),
"over-64-char name must reject"
);
}
#[tokio::test]
async fn rename_updates_name_and_persists() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Stopped, false).await;
let updated = mgr.rename(&id, "tm-renamed-01").await.expect("rename");
assert_eq!(updated.tmux_name, "tm-renamed-01");
let reread = mgr.get(&id).await.expect("get");
assert_eq!(reread.tmux_name, "tm-renamed-01");
}
#[tokio::test]
async fn rename_same_name_is_noop() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Stopped, false).await;
let current = mgr.get(&id).await.expect("get").tmux_name;
let same = mgr.rename(&id, ¤t).await.expect("rename same");
assert_eq!(same.tmux_name, current);
}
#[tokio::test]
async fn rename_rejects_collision_with_record() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let a = ManagedSessionId::new();
let b = ManagedSessionId::new();
seed_record(&mgr, &dir, a, ManagedSessionState::Stopped, false).await;
seed_record(&mgr, &dir, b, ManagedSessionState::Stopped, false).await;
let b_name = mgr.get(&b).await.expect("get b").tmux_name;
let err = mgr
.rename(&a, &b_name)
.await
.expect_err("must refuse a name another record already holds");
assert!(matches!(err, ManagedError::NameCollision(_)), "got {err:?}");
}
#[tokio::test]
async fn rename_rejects_collision_with_live_tmux() {
let dir = TempDir::new().unwrap();
let (mgr, fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Stopped, false).await;
fake.create_session("tm-foreign-live", "/tmp")
.expect("register foreign tmux");
let err = mgr
.rename(&id, "tm-foreign-live")
.await
.expect_err("must refuse a name a live tmux session holds");
assert!(matches!(err, ManagedError::NameCollision(_)), "got {err:?}");
}
#[tokio::test]
async fn rename_rejects_invalid_name() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Stopped, false).await;
let err = mgr
.rename(&id, "bad name!")
.await
.expect_err("invalid name must be rejected");
assert!(
matches!(err, ManagedError::InvalidState(_, _)),
"got {err:?}"
);
}
#[tokio::test]
async fn rename_rejects_terminal_record() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Decommissioned, false).await;
let err = mgr
.rename(&id, "tm-new-name")
.await
.expect_err("a terminal record must not be renamed");
assert!(
matches!(err, ManagedError::InvalidState(_, _)),
"got {err:?}"
);
}
#[tokio::test]
async fn rename_reuses_name_freed_by_a_deleted_record() {
let dir = TempDir::new().unwrap();
let (mgr, _fake) = make_manager(&dir).await;
let live = ManagedSessionId::new();
let gone = ManagedSessionId::new();
seed_record(&mgr, &dir, live, ManagedSessionState::Stopped, false).await;
seed_record(&mgr, &dir, gone, ManagedSessionState::Deleted, false).await;
let freed = mgr.get(&gone).await.expect("get gone").tmux_name;
let updated = mgr
.rename(&live, &freed)
.await
.expect("a deleted tombstone's name must be reusable");
assert_eq!(updated.tmux_name, freed);
}
#[tokio::test]
async fn rename_renames_live_tmux_session() {
let dir = TempDir::new().unwrap();
let (mgr, fake) = make_manager(&dir).await;
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Active, false).await;
let old_name = mgr.get(&id).await.expect("get").tmux_name;
mgr.rename(&id, "tm-live-renamed").await.expect("rename");
let renames = fake.rename_calls.lock().unwrap();
assert!(
renames
.iter()
.any(|(o, n)| o == &old_name && n == "tm-live-renamed"),
"expected rename_session({old_name} -> tm-live-renamed), got {renames:?}"
);
drop(renames);
assert!(
fake.session_exists("tm-live-renamed"),
"new name must be live"
);
assert!(!fake.session_exists(&old_name), "old name must be gone");
}