use super::*;
use trusty_common::memory_core::palace::{Drawer, PalaceId};
use uuid::Uuid;
#[tokio::test]
async fn mark_dirty_is_idempotent() {
let tmp = tempfile::tempdir().expect("tempdir");
let state = AppState::new(tmp.path().to_path_buf());
assert!(dirty_palaces(&state).is_empty());
for _ in 0..40 {
mark_dirty(&state, "alpha");
}
mark_dirty(&state, "beta");
let mut queued = dirty_palaces(&state);
queued.sort();
assert_eq!(queued, vec!["alpha".to_string(), "beta".to_string()]);
}
#[test]
fn repair_interval_honours_env_override() {
let prev = std::env::var(ENV_REPAIR_INTERVAL_SECS).ok();
let cases: [(&str, Option<Duration>); 3] = [
("30", Some(Duration::from_secs(30))),
("0", None),
(
"banana",
Some(Duration::from_secs(DEFAULT_REPAIR_INTERVAL_SECS)),
),
];
for (raw, expected) in cases {
unsafe { std::env::set_var(ENV_REPAIR_INTERVAL_SECS, raw) };
assert_eq!(repair_interval(), expected, "value {raw:?}");
}
unsafe { std::env::remove_var(ENV_REPAIR_INTERVAL_SECS) };
assert_eq!(
repair_interval(),
Some(Duration::from_secs(DEFAULT_REPAIR_INTERVAL_SECS))
);
if let Some(v) = prev {
unsafe { std::env::set_var(ENV_REPAIR_INTERVAL_SECS, v) };
}
}
#[tokio::test]
async fn repair_sweep_is_a_noop_without_the_lane() {
let tmp = tempfile::tempdir().expect("tempdir");
let state = AppState::new(tmp.path().to_path_buf());
assert!(state.bm25.is_none());
spawn_repair_sweep(&state);
mark_dirty(&state, "alpha");
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(
dirty_palaces(&state),
vec!["alpha".to_string()],
"no sweep may run, so nothing may be drained"
);
}
fn create_on_disk(state: &AppState, id: &str) {
let handle = state
.registry
.create_palace(
&state.data_root,
trusty_common::memory_core::palace::Palace {
id: PalaceId::new(id),
name: id.to_string(),
description: None,
created_at: chrono::Utc::now(),
data_dir: state.data_root.join(id),
},
)
.expect("create palace on disk");
let drawer = Drawer::new(Uuid::new_v4(), "content worth indexing");
handle
.kg
.upsert_drawer_sync(&drawer)
.expect("persist drawer");
handle.drawers.write().push(drawer);
}
#[tokio::test]
async fn an_evicted_palace_is_rehydrated_not_dropped() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
create_on_disk(&AppState::new(root.clone()), "evicted");
let cold = AppState::new(root);
assert!(
cold.registry.list().is_empty(),
"precondition: the registry must be cold, so `get` would miss"
);
mark_dirty(&cold, "evicted");
let (attempted, repaired) = run_repair_pass(&cold).await;
assert_eq!(attempted, 1);
assert_eq!(repaired, 0, "the lane is off, so nothing can be repaired");
assert_eq!(
dirty_palaces(&cold),
vec!["evicted".to_string()],
"an evicted palace must be hydrated and kept queued, never dropped"
);
assert!(
cold.startup_gate.peak_concurrent() >= 1,
"and the pass must actually have taken a startup-budget slot to open it"
);
}
#[tokio::test]
async fn repair_pass_hands_back_the_palace_it_opened() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
create_on_disk(&AppState::new(root.clone()), "swept");
let cold = AppState::new(root);
assert!(
cold.registry.list().is_empty(),
"precondition: cold registry"
);
mark_dirty(&cold, "swept");
let (attempted, _) = run_repair_pass(&cold).await;
assert_eq!(attempted, 1);
assert!(
cold.registry.peek(&PalaceId::new("swept")).is_none(),
"#7106: a palace the repair pass itself opened must be handed back, \
not left resident for the rest of the process's life"
);
assert!(
cold.startup_gate.peak_concurrent() >= 1,
"#7106: the repair pass must open through the shared startup budget"
);
}
#[tokio::test]
async fn repair_pass_keeps_a_recently_used_palace() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
create_on_disk(&AppState::new(root.clone()), "in-session");
crate::palace_last_used::write(
&root.join("in-session"),
crate::palace_last_used::now_unix(),
)
.expect("stamp last_used");
let cold = AppState::new(root);
mark_dirty(&cold, "in-session");
let (attempted, _) = run_repair_pass(&cold).await;
assert_eq!(attempted, 1);
assert!(
cold.registry.peek(&PalaceId::new("in-session")).is_some(),
"#7087: a palace a client used inside the keep-recent window stays resident"
);
}
#[tokio::test]
async fn a_palace_absent_from_disk_is_dropped_from_the_queue() {
let tmp = tempfile::tempdir().expect("tempdir");
let state = AppState::new(tmp.path().to_path_buf());
mark_dirty(&state, "no-such-palace");
let (attempted, repaired) = run_repair_pass(&state).await;
assert_eq!(attempted, 1);
assert_eq!(repaired, 0);
assert!(
dirty_palaces(&state).is_empty(),
"a palace that no longer exists must not stay queued forever"
);
}
#[tokio::test]
async fn an_unrepairable_palace_stays_queued() {
let tmp = tempfile::tempdir().expect("tempdir");
let state = AppState::new(tmp.path().to_path_buf());
create_on_disk(&state, "resident");
mark_dirty(&state, "resident");
let (attempted, repaired) = run_repair_pass(&state).await;
assert_eq!(attempted, 1);
assert_eq!(repaired, 0, "the lane is off, so nothing can be repaired");
assert_eq!(
dirty_palaces(&state),
vec!["resident".to_string()],
"an unverified palace must stay queued for the next pass"
);
}