//! §11.6 D3 integration test: group-shared durable queue store. //! //! Proves the competing-consumer primitive: N messages enqueued into one //! group-keyed store are claimed EXACTLY ONCE across concurrent claimers (the //! `FOR UPDATE SKIP LOCKED` guarantee that makes per-descendant materialized //! consumers safe). Also checks ack removes a row and nack re-defers it. //! //! Deterministic: drives `GroupQueueRepo` directly (no dispatcher). Skips when //! `DATABASE_URL` is unset. #![allow(clippy::too_many_lines, clippy::many_single_char_names)] use std::collections::HashSet; use picloud_manager_core::group_queue_repo::{ GroupQueueRepo, NewGroupQueueMessage, PostgresGroupQueueRepo, }; use picloud_shared::GroupId; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; use uuid::Uuid; async fn pool_or_skip() -> Option { let Ok(url) = std::env::var("DATABASE_URL") else { eprintln!("group_queue: DATABASE_URL unset — skipping"); return None; }; let pool = PgPoolOptions::new() .max_connections(6) .connect(&url) .await .expect("connect"); sqlx::migrate!("./migrations") .run(&pool) .await .expect("migrate"); Some(pool) } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn competing_consumers_claim_each_message_exactly_once() { let Some(pool) = pool_or_skip().await else { return; }; let sfx = Uuid::new_v4().simple().to_string(); let g: (Uuid,) = sqlx::query_as("INSERT INTO groups (slug, name) VALUES ($1, $1) RETURNING id") .bind(format!("gq-g-{sfx}")) .fetch_one(&pool) .await .unwrap(); let group = GroupId::from(g.0); let repo = PostgresGroupQueueRepo::new(pool.clone()); // Enqueue 50 distinct messages into the shared `tasks` queue. let n_msgs: usize = 50; for i in 0..n_msgs { repo.enqueue(NewGroupQueueMessage { group_id: group, collection: "tasks".into(), payload: serde_json::json!({ "i": i }), deliver_after: None, max_attempts: 3, enqueued_by_principal: None, }) .await .unwrap(); } // Four concurrent "consumers" claim until the queue is drained, ack-ing each. // Every claimed payload id must be unique — no message delivered twice. let claim_loop = |repo: PostgresGroupQueueRepo, group: GroupId| async move { let mut got: Vec = Vec::new(); while let Some(msg) = repo.claim(group, "tasks").await.unwrap() { got.push(msg.payload["i"].as_i64().unwrap()); assert!(repo.ack(msg.id, msg.claim_token).await.unwrap(), "ack ok"); } got }; let (a, b, c, d) = tokio::join!( claim_loop(PostgresGroupQueueRepo::new(pool.clone()), group), claim_loop(PostgresGroupQueueRepo::new(pool.clone()), group), claim_loop(PostgresGroupQueueRepo::new(pool.clone()), group), claim_loop(PostgresGroupQueueRepo::new(pool.clone()), group), ); let mut all: Vec = Vec::new(); all.extend(a); all.extend(b); all.extend(c); all.extend(d); let unique: HashSet = all.iter().copied().collect(); assert_eq!( all.len(), n_msgs, "every message delivered exactly once (no dupes)" ); assert_eq!(unique.len(), n_msgs, "all distinct ids covered"); assert_eq!( repo.depth(group, "tasks").await.unwrap(), 0, "queue drained" ); // nack re-defers a message (a later claim gets it back). let id = repo .enqueue(NewGroupQueueMessage { group_id: group, collection: "tasks".into(), payload: serde_json::json!({ "i": 999 }), deliver_after: None, max_attempts: 3, enqueued_by_principal: None, }) .await .unwrap(); let msg = repo.claim(group, "tasks").await.unwrap().expect("claimed"); assert_eq!(msg.id, id); repo.nack(msg.id, msg.claim_token, chrono::Duration::milliseconds(0)) .await .unwrap(); // After nack (0ms delay) it is claimable again. let again = repo.claim(group, "tasks").await.unwrap().expect("re-claim"); assert_eq!(again.id, id, "nacked message is re-delivered"); assert_eq!(again.attempt, 2, "attempt incremented on re-claim"); repo.ack(again.id, again.claim_token).await.unwrap(); // Cleanup (messages cascade on group delete anyway). let _ = sqlx::query("DELETE FROM group_queue_messages WHERE group_id = $1") .bind(g.0) .execute(&pool) .await; let _ = sqlx::query("DELETE FROM groups WHERE id = $1") .bind(g.0) .execute(&pool) .await; }