use super::recall_stream::{recall_streamed, RECALL_PALACE_BATCH};
use crate::AppState;
use anyhow::anyhow;
use chrono::Utc;
use std::cell::RefCell;
use std::path::Path;
use trusty_common::memory_core::palace::{Palace, PalaceId};
use trusty_common::memory_core::PalaceRegistry;
fn seed(data_root: &Path, n: usize) -> Vec<Palace> {
let registry = PalaceRegistry::new();
let mut out = Vec::with_capacity(n);
for i in 0..n {
let id = PalaceId::new(format!("unit-{i:02}"));
let palace = Palace {
id: id.clone(),
name: id.as_str().to_string(),
description: None,
created_at: Utc::now(),
data_dir: data_root.join(id.as_str()),
};
registry
.create_palace(data_root, palace.clone())
.unwrap_or_else(|e| panic!("create_palace({id}) failed: {e:#}"));
out.push(palace);
}
drop(registry);
out
}
#[tokio::test]
async fn recall_all_releases_every_batch_when_the_search_fails() {
let tmp = tempfile::tempdir().expect("tempdir");
let palaces = seed(tmp.path(), RECALL_PALACE_BATCH * 2);
let state = AppState::new(tmp.path().to_path_buf());
assert_eq!(state.registry.len(), 0, "baseline: nothing open");
let calls = RefCell::new(0usize);
let outcome = recall_streamed(&state, &palaces, "unit", 5, |handles| {
let mut n = calls.borrow_mut();
*n += 1;
let fail = *n == 2;
assert_eq!(
handles.len(),
RECALL_PALACE_BATCH,
"each batch must be exactly one chunk wide"
);
async move {
if fail {
Err(anyhow!("search exploded"))
} else {
Ok(Vec::new())
}
}
})
.await;
assert!(outcome.is_err(), "the search error must reach the caller");
assert_eq!(*calls.borrow(), 2, "the walk stops at the failing batch");
assert_eq!(
state.registry.len(),
0,
"a failing search must not leak the batch it was handed; {} handle(s) \
left resident",
state.registry.len()
);
}
#[tokio::test]
async fn recall_streamed_visits_every_palace_in_bounded_batches() {
let tmp = tempfile::tempdir().expect("tempdir");
let count = RECALL_PALACE_BATCH * 2 + 3;
let palaces = seed(tmp.path(), count);
let state = AppState::new(tmp.path().to_path_buf());
let seen: RefCell<Vec<String>> = RefCell::new(Vec::new());
let widths: RefCell<Vec<usize>> = RefCell::new(Vec::new());
let outcome = recall_streamed(&state, &palaces, "unit", 5, |handles| {
widths.borrow_mut().push(handles.len());
seen.borrow_mut()
.extend(handles.iter().map(|h| h.id.as_str().to_string()));
async move { Ok(Vec::new()) }
})
.await;
outcome.expect("a search that never fails must not error");
let mut visited = seen.into_inner();
visited.sort();
assert_eq!(visited.len(), count, "every palace is visited exactly once");
let mut expected: Vec<String> = palaces.iter().map(|p| p.id.as_str().to_string()).collect();
expected.sort();
assert_eq!(visited, expected, "and the set is the whole estate");
assert!(
widths.borrow().iter().all(|w| *w <= RECALL_PALACE_BATCH),
"no batch may exceed the residency bound: {:?}",
widths.borrow()
);
assert_eq!(state.registry.len(), 0, "nothing is left resident");
}