#[test]
fn sidecar_name_derives_from_the_full_file_name() {
let path = events_db_path_beside(Path::new("/data/khive.db"));
assert!(path.ends_with("khive.db.events.db"), "got {path:?}");
}
#[test]
fn databases_sharing_a_stem_get_distinct_sidecars() {
let a = events_db_path_beside(Path::new("/data/a.db"));
let b = events_db_path_beside(Path::new("/data/a.sqlite"));
assert_ne!(a, b, "a.db and a.sqlite must not share an event plane");
assert!(a.ends_with("a.db.events.db"), "got {a:?}");
assert!(b.ends_with("a.sqlite.events.db"), "got {b:?}");
}
#[cfg(unix)]
#[test]
fn directory_aliases_resolve_to_one_sidecar() {
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real");
std::fs::create_dir(&real).unwrap();
let alias = dir.path().join("alias");
std::os::unix::fs::symlink(&real, &alias).unwrap();
assert_eq!(
events_db_path_beside(&real.join("khive.db")),
events_db_path_beside(&alias.join("khive.db")),
"a symlinked spelling of one directory must not mint a second sidecar"
);
}
#[cfg(unix)]
#[test]
fn final_component_aliases_of_one_database_share_one_sidecar() {
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real.db");
std::fs::write(&real, b"").unwrap();
let alias = dir.path().join("alias.db");
std::os::unix::fs::symlink(&real, &alias).unwrap();
let real_sidecar = events_db_path_beside(&real);
let alias_sidecar = events_db_path_beside(&alias);
assert_eq!(
real_sidecar, alias_sidecar,
"a symlink alias of one database file must not mint a second event store"
);
assert_eq!(
events_socket_path_beside(&real_sidecar),
events_socket_path_beside(&alias_sidecar),
);
let other = dir.path().join("other.db");
std::fs::write(&other, b"").unwrap();
assert_ne!(events_db_path_beside(&other), real_sidecar);
}
#[cfg(unix)]
#[test]
fn dangling_alias_derives_the_target_sidecar_on_cold_start() {
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real.db");
let alias = dir.path().join("alias.db");
std::os::unix::fs::symlink(&real, &alias).unwrap();
assert_eq!(
events_db_path_beside(&alias),
events_db_path_beside(&real),
"a dangling alias must derive the same sidecar its target will use"
);
let chain = dir.path().join("chain.db");
std::os::unix::fs::symlink(&alias, &chain).unwrap();
assert_eq!(events_db_path_beside(&chain), events_db_path_beside(&real));
assert_ne!(
events_db_path_beside(&dir.path().join("unrelated.db")),
events_db_path_beside(&real)
);
}
#[cfg(unix)]
#[test]
fn planted_symlink_at_the_sidecar_path_is_refused() {
let dir = tempfile::tempdir().unwrap();
let _registry_guard = TestRegistryGuard::new(dir.path());
let victim = dir.path().join("victim.txt");
std::fs::write(&victim, b"victim-bytes").unwrap();
let mode_before = victim.metadata().unwrap().permissions();
let sidecar = dir.path().join("khive.db.events.db");
std::os::unix::fs::symlink(&victim, &sidecar).unwrap();
let err = match direct_backend_for(&sidecar) {
Ok(_) => panic!("a planted symlink at the sidecar path must be refused"),
Err(e) => e,
};
assert!(err.to_string().contains("symlink"), "got: {err}");
assert_eq!(std::fs::read(&victim).unwrap(), b"victim-bytes");
assert_eq!(victim.metadata().unwrap().permissions(), mode_before);
let ghost_target = dir.path().join("ghost.db");
let dangling = dir.path().join("other.db.events.db");
std::os::unix::fs::symlink(&ghost_target, &dangling).unwrap();
assert!(direct_backend_for(&dangling).is_err());
assert!(
!ghost_target.exists(),
"refusal must not create the redirect target"
);
let regular = dir.path().join("plain.db.events.db");
direct_backend_for(®ular).expect("regular sidecar path must open");
assert!(regular.exists());
}