mod common;
use std::sync::Arc;
use chrono::{DateTime, TimeZone, Utc};
use common::TestDb;
use serde_json::json;
use ulid::Ulid;
use venturi::postgres::PostgresStore;
use venturi::store::{HistoryFilter, JobRecord, NewJob, Store};
fn new_job(id: Ulid, kind: &str, created_at: DateTime<Utc>) -> NewJob {
NewJob {
id,
kind: kind.to_owned(),
payload: json!(null),
priority: 1,
created_at,
visible_at: created_at,
carry: json!(null),
dedup_key: None,
}
}
fn at(seconds: i64) -> DateTime<Utc> {
Utc.timestamp_opt(1_700_000_000 + seconds, 0)
.single()
.expect("valid fixed timestamp")
}
async fn page_all(store: &Arc<PostgresStore>, kind: Option<&str>, limit: i64) -> Vec<JobRecord> {
let mut collected: Vec<JobRecord> = Vec::new();
let mut cursor: Option<(DateTime<Utc>, Ulid)> = None;
loop {
let page = store
.query_jobs(&HistoryFilter {
kind: kind.map(str::to_owned),
created_before: cursor,
limit: Some(limit),
..Default::default()
})
.await
.expect("query page");
let Some(last) = page.last() else {
break;
};
cursor = Some((last.created_at, last.id));
collected.extend(page);
}
collected
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn keyset_pages_in_created_desc_order_without_gaps_or_repeats() {
let db = TestDb::start().await;
let store = Arc::new(db.store("venturi").await);
let mut ids = Vec::new();
for seconds in [3, 7, 1, 9, 5, 0, 8, 2, 6, 4] {
let id = Ulid::new();
ids.push((at(seconds), id));
store
.enqueue(&new_job(id, "alpha", at(seconds)))
.await
.expect("enqueue");
}
let mut expected: Vec<(DateTime<Utc>, Ulid)> = ids.clone();
expected.sort_by(|a, b| b.cmp(a));
let paged = page_all(&store, None, 3).await;
let got: Vec<(DateTime<Utc>, Ulid)> = paged.iter().map(|r| (r.created_at, r.id)).collect();
assert_eq!(
got, expected,
"paged order must be the full created_at desc, id desc order"
);
assert_eq!(got.len(), 10, "every row appears exactly once across pages");
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn keyset_breaks_ties_on_equal_created_at_by_id_desc() {
let db = TestDb::start().await;
let store = Arc::new(db.store("venturi").await);
let shared = at(42);
let mut ids: Vec<Ulid> = (0..5).map(|_| Ulid::new()).collect();
for id in &ids {
store
.enqueue(&new_job(*id, "alpha", shared))
.await
.expect("enqueue");
}
let paged = page_all(&store, None, 1).await;
let got: Vec<Ulid> = paged.iter().map(|r| r.id).collect();
ids.sort_by(|a, b| b.cmp(a)); assert_eq!(
got, ids,
"equal created_at rows must page in strict id desc order"
);
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn keyset_past_the_oldest_row_returns_an_empty_page() {
let db = TestDb::start().await;
let store = Arc::new(db.store("venturi").await);
let oldest_id = Ulid::new();
store
.enqueue(&new_job(oldest_id, "alpha", at(10)))
.await
.expect("enqueue");
store
.enqueue(&new_job(Ulid::new(), "alpha", at(20)))
.await
.expect("enqueue");
let page = store
.query_jobs(&HistoryFilter {
created_before: Some((at(10), oldest_id)),
limit: Some(50),
..Default::default()
})
.await
.expect("query");
assert!(page.is_empty(), "no row is older than the oldest cursor");
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn keyset_composes_with_a_kind_filter() {
let db = TestDb::start().await;
let store = Arc::new(db.store("venturi").await);
let mut alpha_keys = Vec::new();
for seconds in [1, 3, 5, 7, 9] {
let id = Ulid::new();
alpha_keys.push((at(seconds), id));
store
.enqueue(&new_job(id, "alpha", at(seconds)))
.await
.expect("enqueue");
}
for seconds in [2, 4, 6, 8] {
store
.enqueue(&new_job(Ulid::new(), "beta", at(seconds)))
.await
.expect("enqueue");
}
let mut expected = alpha_keys.clone();
expected.sort_by(|a, b| b.cmp(a));
let paged = page_all(&store, Some("alpha"), 2).await;
let got: Vec<(DateTime<Utc>, Ulid)> = paged.iter().map(|r| (r.created_at, r.id)).collect();
assert_eq!(
got, expected,
"filtered pages stay within the kind and in keyset order"
);
assert!(
paged.iter().all(|r| r.kind == "alpha"),
"no other kind leaks into the page"
);
}