use crate::db::Database;
use crate::db::repository::NotifyQueueRepository;
use uuid::Uuid;
const SERVICE_SRC: &str = include_str!("../brain/agent/service/notify_queue.rs");
async fn setup() -> (NotifyQueueRepository, Database) {
let db = Database::connect_in_memory()
.await
.expect("in-memory database");
db.run_migrations().await.expect("migrations");
(NotifyQueueRepository::new(db.pool().clone()), db)
}
#[tokio::test]
async fn redelivery_read_is_age_blind_so_only_call_order_protects_an_old_live_row() {
let (repo, db) = setup().await;
let now = 1_700_000_000i64;
let session = Uuid::new_v4();
let id = Uuid::new_v4();
let sess_str = session.to_string();
let id_str = id.to_string();
raw(&db, move |conn| {
conn.execute(
"INSERT INTO notify_queue \
(id, session_id, context_text, display_text, origin, bg_meta, created_at) \
VALUES (?1, ?2, 'four days parked', 'four days parked', 'session_notify', NULL, ?3)",
rusqlite::params![id_str, sess_str, now - 100 * 3600],
)
})
.await;
let offered = repo.all().await.expect("all");
assert_eq!(
offered.len(),
1,
"all() must surface the 100h row for redelivery, it is age-blind by design"
);
assert_eq!(offered[0].id, id);
let reaped = repo
.reap_stale_unclaimed(now - 72 * 3600)
.await
.expect("reap");
assert_eq!(reaped.len(), 1, "after the offer, the same row is reapable");
assert_eq!(reaped[0].id, id);
}
#[test]
fn reap_call_sits_after_the_deliver_or_park_loop() {
let deliver = SERVICE_SRC
.find("deliver_or_park(row.session_id, msg)")
.expect("redeliver_persisted must still offer rows through deliver_or_park");
let reap = SERVICE_SRC
.find("reap_stale_after_redelivery(&repo).await")
.expect("redeliver_persisted must still call the age reap");
assert!(
reap > deliver,
"the age reap must be called AFTER the redelivery loop: moved before it, \
a 72h+ row for a live session is deleted instead of delivered"
);
}
#[test]
fn dead_session_reap_stays_before_the_loop() {
let dead = SERVICE_SRC
.find("repo.clear_dead_sessions().await")
.expect("redeliver_persisted must still reap rows for dead sessions");
let read = SERVICE_SRC
.find("repo.all().await")
.expect("redeliver_persisted must still read the rows it offers");
assert!(
dead < read,
"the dead-session reap belongs before the read: its rows are unclaimable"
);
}
async fn raw<F>(db: &Database, f: F)
where
F: FnOnce(&rusqlite::Connection) -> rusqlite::Result<usize> + Send + 'static,
{
db.pool()
.get()
.await
.expect("pool connection")
.interact(move |conn| f(conn))
.await
.expect("interact")
.expect("raw sql");
}