use chrono::Utc;
use qrusty::message::{Message, Priority, PriorityOrdering, QueueConfig};
use qrusty::storage::{RenameQueueError, Storage};
use rocksdb::{Options, DB};
use std::sync::Arc;
use tempfile::TempDir;
fn create_test_storage() -> (Storage, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let storage =
Storage::new(temp_dir.path().to_str().unwrap()).expect("Failed to create test storage");
(storage, temp_dir)
}
fn create_test_message(queue: &str, priority: u64, payload: &str) -> Message {
Message {
id: uuid::Uuid::new_v4().to_string(),
queue: queue.to_string(),
priority: Priority::Numeric(priority),
payload: payload.to_string(),
created_at: Utc::now(),
locked_until: None,
locked_by: None,
retry_count: 0,
max_retries: 3,
payload_ref: None,
payload_hash: None,
}
}
#[test]
fn test_storage_new() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path().to_str().unwrap());
assert!(storage.is_ok());
}
#[test]
fn test_push_message() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, r#"{"test": "data"}"#);
let message_id = message.id.clone();
let result = storage.push(message);
assert!(result.is_ok());
assert_eq!(result.unwrap(), message_id);
}
#[test]
fn test_push_and_pop_message() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, r#"{"test": "data"}"#);
let message_id = message.id.clone();
let payload = message.payload.clone();
storage.push(message).unwrap();
let popped = storage.pop("test_queue", "consumer1", 30).unwrap();
assert!(popped.is_some());
let popped_msg = popped.unwrap();
assert_eq!(popped_msg.id, message_id);
assert_eq!(popped_msg.payload, payload);
assert_eq!(popped_msg.retry_count, 1); assert!(popped_msg.locked_until.is_some());
assert_eq!(popped_msg.locked_by, Some("consumer1".to_string()));
}
#[test]
fn test_priority_ordering() {
let (storage, _temp_dir) = create_test_storage();
let low_priority = create_test_message("test_queue", 10, "low");
let high_priority = create_test_message("test_queue", 100, "high");
let medium_priority = create_test_message("test_queue", 50, "medium");
storage.push(low_priority).unwrap();
storage.push(high_priority).unwrap();
storage.push(medium_priority).unwrap();
let first = storage.pop("test_queue", "consumer1", 30).unwrap().unwrap();
assert_eq!(first.payload, "high");
assert_eq!(first.priority, Priority::Numeric(100));
let second = storage.pop("test_queue", "consumer2", 30).unwrap().unwrap();
assert_eq!(second.payload, "medium");
assert_eq!(second.priority, Priority::Numeric(50));
let third = storage.pop("test_queue", "consumer3", 30).unwrap().unwrap();
assert_eq!(third.payload, "low");
assert_eq!(third.priority, Priority::Numeric(10));
}
#[test]
fn test_fifo_ordering_ignores_priority() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::Fifo,
..Default::default()
};
storage.configure_queue("fifo_queue", config);
let now = Utc::now();
let mut first = create_test_message("fifo_queue", 999, "first");
first.created_at = now - chrono::Duration::seconds(2);
let mut second = create_test_message("fifo_queue", 0, "second");
second.created_at = now - chrono::Duration::seconds(1);
storage.push(first).unwrap();
storage.push(second).unwrap();
let msg1 = storage.pop("fifo_queue", "consumer1", 30).unwrap().unwrap();
assert_eq!(msg1.payload, "first");
let msg2 = storage.pop("fifo_queue", "consumer2", 30).unwrap().unwrap();
assert_eq!(msg2.payload, "second");
}
#[test]
fn test_fifo_preserves_position_on_nack_retry() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::Fifo,
..Default::default()
};
storage.configure_queue("fifo_retry_queue", config);
let now = Utc::now();
let mut first = create_test_message("fifo_retry_queue", 100, "first");
first.created_at = now - chrono::Duration::seconds(2);
let first_id = first.id.clone();
let mut second = create_test_message("fifo_retry_queue", 100, "second");
second.created_at = now - chrono::Duration::seconds(1);
let second_id = second.id.clone();
storage.push(first).unwrap();
storage.push(second).unwrap();
let locked = storage
.pop("fifo_retry_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(locked.id, first_id);
let success = storage
.nack("fifo_retry_queue", &first_id, "consumer1")
.unwrap();
assert!(success);
let next = storage
.pop("fifo_retry_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(next.id, first_id);
let later = storage
.pop("fifo_retry_queue", "consumer3", 30)
.unwrap()
.unwrap();
assert_eq!(later.id, second_id);
}
#[test]
fn test_queue_isolation() {
let (storage, _temp_dir) = create_test_storage();
let msg1 = create_test_message("queue1", 100, "queue1_data");
let msg2 = create_test_message("queue2", 100, "queue2_data");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let popped1 = storage.pop("queue1", "consumer1", 30).unwrap().unwrap();
assert_eq!(popped1.payload, "queue1_data");
let popped2 = storage.pop("queue2", "consumer2", 30).unwrap().unwrap();
assert_eq!(popped2.payload, "queue2_data");
let empty = storage.pop("queue1", "consumer3", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_message_locking() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, "test_data");
storage.push(message).unwrap();
let locked_msg = storage.pop("test_queue", "consumer1", 30).unwrap().unwrap();
assert!(locked_msg.locked_until.is_some());
assert_eq!(locked_msg.locked_by, Some("consumer1".to_string()));
let empty = storage.pop("test_queue", "consumer2", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_ack_message() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, "test_data");
let message_id = message.id.clone();
storage.push(message).unwrap();
let _locked_msg = storage.pop("test_queue", "consumer1", 30).unwrap().unwrap();
let ack_result = storage.ack("test_queue", &message_id, "consumer1").unwrap();
assert!(ack_result);
let empty = storage.pop("test_queue", "consumer2", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_ack_wrong_consumer() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, "test_data");
let message_id = message.id.clone();
storage.push(message).unwrap();
storage.pop("test_queue", "consumer1", 30).unwrap();
let ack_result = storage.ack("test_queue", &message_id, "consumer2").unwrap();
assert!(!ack_result);
let empty = storage.pop("test_queue", "consumer3", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_nack_message_retry() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, "test_data");
let message_id = message.id.clone();
storage.push(message).unwrap();
storage.pop("test_queue", "consumer1", 30).unwrap();
let nack_result = storage
.nack("test_queue", &message_id, "consumer1")
.unwrap();
assert!(nack_result);
let retry_msg = storage.pop("test_queue", "consumer2", 30).unwrap().unwrap();
assert_eq!(retry_msg.id, message_id);
assert_eq!(retry_msg.retry_count, 2); assert!(
retry_msg.locked_until.is_none() || retry_msg.locked_by == Some("consumer2".to_string())
);
}
#[test]
fn test_nack_max_retries_dead_letter() {
let (storage, _temp_dir) = create_test_storage();
let mut message = create_test_message("test_queue", 100, "test_data");
message.max_retries = 2;
message.retry_count = 2; let message_id = message.id.clone();
storage.push(message).unwrap();
storage.pop("test_queue", "consumer1", 30).unwrap();
let nack_result = storage
.nack("test_queue", &message_id, "consumer1")
.unwrap();
assert!(nack_result);
let empty = storage.pop("test_queue", "consumer2", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_nack_wrong_consumer() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("test_queue", 100, "test_data");
let message_id = message.id.clone();
storage.push(message).unwrap();
storage.pop("test_queue", "consumer1", 30).unwrap();
let nack_result = storage
.nack("test_queue", &message_id, "consumer2")
.unwrap();
assert!(!nack_result);
}
#[test]
fn test_empty_queue_pop() {
let (storage, _temp_dir) = create_test_storage();
let result = storage.pop("empty_queue", "consumer1", 30).unwrap();
assert!(result.is_none());
}
#[test]
fn test_concurrent_operations() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
for i in 0..10 {
let message = create_test_message("concurrent_queue", i * 10, &format!("data_{}", i));
storage.push(message).unwrap();
}
let mut handles = vec![];
for i in 0..5 {
let storage_clone = storage.clone();
let consumer_id = format!("consumer_{}", i);
let handle = std::thread::spawn(move || {
let mut consumed = vec![];
while let Some(msg) = storage_clone
.pop("concurrent_queue", &consumer_id, 30)
.unwrap()
{
consumed.push(msg);
std::thread::sleep(std::time::Duration::from_millis(10));
}
consumed
});
handles.push(handle);
}
let mut all_messages = vec![];
for handle in handles {
let consumed = handle.join().unwrap();
all_messages.extend(consumed);
}
assert_eq!(all_messages.len(), 10);
let mut ids: Vec<String> = all_messages.iter().map(|m| m.id.clone()).collect();
ids.sort();
ids.dedup();
assert_eq!(ids.len(), 10);
}
#[test]
fn test_message_persistence() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
let message_id = {
let storage = Storage::new(data_path).unwrap();
let message = create_test_message("persistent_queue", 100, "persistent_data");
let id = message.id.clone();
storage.push(message).unwrap();
id
};
let storage = Storage::new(data_path).unwrap();
let recovered_msg = storage
.pop("persistent_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(recovered_msg.id, message_id);
assert_eq!(recovered_msg.payload, "persistent_data");
}
#[test]
fn test_queue_configuration_max_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
..Default::default()
};
storage.configure_queue("max_first_queue", config);
let low_priority = create_test_message("max_first_queue", 10, "low");
let high_priority = create_test_message("max_first_queue", 100, "high");
let medium_priority = create_test_message("max_first_queue", 50, "medium");
storage.push(low_priority).unwrap();
storage.push(high_priority).unwrap();
storage.push(medium_priority).unwrap();
let first = storage
.pop("max_first_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(first.payload, "high");
assert_eq!(first.priority, Priority::Numeric(100));
let second = storage
.pop("max_first_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(second.payload, "medium");
assert_eq!(second.priority, Priority::Numeric(50));
let third = storage
.pop("max_first_queue", "consumer3", 30)
.unwrap()
.unwrap();
assert_eq!(third.payload, "low");
assert_eq!(third.priority, Priority::Numeric(10));
}
#[test]
fn test_queue_configuration_min_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.configure_queue("min_first_queue", config);
let low_priority = create_test_message("min_first_queue", 10, "low");
let high_priority = create_test_message("min_first_queue", 100, "high");
let medium_priority = create_test_message("min_first_queue", 50, "medium");
storage.push(low_priority).unwrap();
storage.push(high_priority).unwrap();
storage.push(medium_priority).unwrap();
let first = storage
.pop("min_first_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(first.payload, "low");
assert_eq!(first.priority, Priority::Numeric(10));
let second = storage
.pop("min_first_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(second.payload, "medium");
assert_eq!(second.priority, Priority::Numeric(50));
let third = storage
.pop("min_first_queue", "consumer3", 30)
.unwrap()
.unwrap();
assert_eq!(third.payload, "high");
assert_eq!(third.priority, Priority::Numeric(100));
}
#[test]
fn test_queue_configuration_default_behavior() {
let (storage, _temp_dir) = create_test_storage();
let low_priority = create_test_message("default_queue", 10, "low");
let high_priority = create_test_message("default_queue", 100, "high");
storage.push(low_priority).unwrap();
storage.push(high_priority).unwrap();
let first = storage
.pop("default_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(first.payload, "high");
assert_eq!(first.priority, Priority::Numeric(100));
let second = storage
.pop("default_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(second.payload, "low");
assert_eq!(second.priority, Priority::Numeric(10));
}
#[test]
fn test_mixed_queue_configurations() {
let (storage, _temp_dir) = create_test_storage();
let max_first_config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
..Default::default()
};
let min_first_config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.configure_queue("urgent_queue", max_first_config);
storage.configure_queue("batch_queue", min_first_config);
storage
.push(create_test_message("urgent_queue", 10, "urgent_low"))
.unwrap();
storage
.push(create_test_message("urgent_queue", 100, "urgent_high"))
.unwrap();
storage
.push(create_test_message("batch_queue", 10, "batch_low"))
.unwrap();
storage
.push(create_test_message("batch_queue", 100, "batch_high"))
.unwrap();
let urgent_first = storage
.pop("urgent_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(urgent_first.payload, "urgent_high");
assert_eq!(urgent_first.priority, Priority::Numeric(100));
let batch_first = storage
.pop("batch_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(batch_first.payload, "batch_low");
assert_eq!(batch_first.priority, Priority::Numeric(10));
}
#[test]
fn test_create_queue_method() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.create_queue("created_queue", config);
let retrieved_config = storage.get_queue_config("created_queue");
assert_eq!(retrieved_config.ordering, PriorityOrdering::MinFirst);
storage
.push(create_test_message("created_queue", 100, "high"))
.unwrap();
storage
.push(create_test_message("created_queue", 10, "low"))
.unwrap();
let first = storage
.pop("created_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(first.payload, "low");
assert_eq!(first.priority, Priority::Numeric(10));
}
#[test]
fn test_get_queue_config() {
let (storage, _temp_dir) = create_test_storage();
let default_config = storage.get_queue_config("unconfigured_queue");
assert_eq!(default_config.ordering, PriorityOrdering::MaxFirst);
let min_first_config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.configure_queue("min_queue", min_first_config.clone());
let retrieved_config = storage.get_queue_config("min_queue");
assert_eq!(retrieved_config.ordering, PriorityOrdering::MinFirst);
}
#[test]
fn test_priority_ordering_edge_cases() {
let (storage, _temp_dir) = create_test_storage();
let min_first_config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.configure_queue("edge_queue", min_first_config);
storage
.push(create_test_message("edge_queue", 0, "zero"))
.unwrap();
storage
.push(create_test_message("edge_queue", u64::MAX, "max"))
.unwrap();
storage
.push(create_test_message("edge_queue", 1, "one"))
.unwrap();
let first = storage.pop("edge_queue", "consumer1", 30).unwrap().unwrap();
assert_eq!(first.payload, "zero");
assert_eq!(first.priority, Priority::Numeric(0));
let second = storage.pop("edge_queue", "consumer2", 30).unwrap().unwrap();
assert_eq!(second.payload, "one");
assert_eq!(second.priority, Priority::Numeric(1));
let third = storage.pop("edge_queue", "consumer3", 30).unwrap().unwrap();
assert_eq!(third.payload, "max");
assert_eq!(third.priority, Priority::Numeric(u64::MAX));
}
#[test]
fn test_concurrent_mixed_priority_queues() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
let max_config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
..Default::default()
};
let min_config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.configure_queue("max_queue", max_config);
storage.configure_queue("min_queue", min_config);
for i in 0..10 {
let priority = i * 10;
storage
.push(create_test_message(
"max_queue",
priority,
&format!("max_{}", priority),
))
.unwrap();
storage
.push(create_test_message(
"min_queue",
priority,
&format!("min_{}", priority),
))
.unwrap();
}
let max_storage = storage.clone();
let min_storage = storage.clone();
let max_handle = std::thread::spawn(move || {
let mut consumed = vec![];
while let Some(msg) = max_storage.pop("max_queue", "max_consumer", 30).unwrap() {
consumed.push(msg.priority);
}
consumed
});
let min_handle = std::thread::spawn(move || {
let mut consumed = vec![];
while let Some(msg) = min_storage.pop("min_queue", "min_consumer", 30).unwrap() {
consumed.push(msg.priority);
}
consumed
});
let max_priorities = max_handle.join().unwrap();
let min_priorities = min_handle.join().unwrap();
assert_eq!(
max_priorities,
vec![90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
.into_iter()
.map(Priority::Numeric)
.collect::<Vec<_>>()
);
assert_eq!(
min_priorities,
vec![0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
.into_iter()
.map(Priority::Numeric)
.collect::<Vec<_>>()
);
}
#[test]
fn test_timeout_monitor_unlock_expired_messages() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
let message = create_test_message("timeout_queue", 100, "timeout_test");
let message_id = message.id.clone();
storage.push(message).unwrap();
let locked_msg = storage
.pop("timeout_queue", "consumer1", 1)
.unwrap()
.unwrap();
assert!(locked_msg.locked_until.is_some());
assert_eq!(locked_msg.locked_by, Some("consumer1".to_string()));
std::thread::sleep(std::time::Duration::from_secs(2));
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(unlocked_count, 1);
let unlocked_msg = storage
.pop("timeout_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(unlocked_msg.id, message_id);
assert_eq!(unlocked_msg.retry_count, 2); assert_eq!(unlocked_msg.locked_by, Some("consumer2".to_string()));
}
#[test]
fn test_unlock_expired_recovers_locks_after_restart() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
let message_id = {
let storage = Storage::new(data_path).unwrap();
let msg = create_test_message("restart_locks", 10, "stranded");
let id = msg.id.clone();
storage.push(msg).unwrap();
let locked = storage
.pop("restart_locks", "consumer1", 1)
.unwrap()
.unwrap();
assert!(locked.locked_until.is_some());
id
};
std::thread::sleep(std::time::Duration::from_secs(2));
let storage = Storage::new(data_path).unwrap();
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(
unlocked_count, 1,
"first sweep after restart must perform a full scan and recover stranded lock"
);
let recovered = storage
.pop("restart_locks", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(recovered.id, message_id);
assert_eq!(recovered.locked_by, Some("consumer2".to_string()));
}
#[test]
fn test_diagnose_queue_reports_clean_state_after_normal_ops() {
let (storage, _temp_dir) = create_test_storage();
for i in 0..3 {
storage
.push(create_test_message("diag_q", 100, &format!("p-{i}")))
.unwrap();
}
let m1 = storage.pop("diag_q", "c1", 3600).unwrap().unwrap();
storage.ack("diag_q", &m1.id, "c1").unwrap();
storage.pop("diag_q", "c1", 3600).unwrap();
let diag = storage.diagnose_queue("diag_q").unwrap();
assert!(
diag.discrepancies.is_empty(),
"no drift expected: {:?}",
diag.discrepancies
);
assert_eq!(diag.counter_available, 1);
assert_eq!(diag.counter_locked, 1);
assert_eq!(diag.scanned_total, 2); assert_eq!(diag.scanned_locked_active, 1);
assert_eq!(diag.scanned_available, 1);
}
#[test]
fn test_diagnose_queue_detects_injected_drift() {
let (storage, _temp_dir) = create_test_storage();
for i in 0..2 {
storage
.push(create_test_message("drifty", 100, &format!("p-{i}")))
.unwrap();
}
storage.__test_set_queue_counters("drifty", 5, 3);
let diag = storage.diagnose_queue("drifty").unwrap();
assert_eq!(diag.counter_available, 5);
assert_eq!(diag.counter_locked, 3);
assert_eq!(diag.scanned_total, 2);
assert_eq!(diag.scanned_available, 2);
assert_eq!(diag.scanned_locked_active, 0);
assert_eq!(diag.discrepancies.len(), 2);
assert!(diag
.discrepancies
.iter()
.any(|d| d.contains("counter.available")));
assert!(diag
.discrepancies
.iter()
.any(|d| d.contains("counter.locked")));
}
#[test]
fn test_reseed_queue_counters_repairs_drift() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("repair_q", 100, "p"))
.unwrap();
let m = storage.pop("repair_q", "c1", 3600).unwrap().unwrap();
storage.__test_set_queue_counters("repair_q", 99, 99);
let diag_before = storage.diagnose_queue("repair_q").unwrap();
assert!(
!diag_before.discrepancies.is_empty(),
"drift should be detected"
);
let (a, l) = storage.reseed_queue_counters("repair_q").unwrap();
assert_eq!(a, 0);
assert_eq!(l, 1);
let diag_after = storage.diagnose_queue("repair_q").unwrap();
assert!(
diag_after.discrepancies.is_empty(),
"drift should be repaired"
);
storage.ack("repair_q", &m.id, "c1").unwrap();
let diag_final = storage.diagnose_queue("repair_q").unwrap();
assert_eq!(diag_final.counter_available, 0);
assert_eq!(diag_final.counter_locked, 0);
}
#[test]
fn test_repop_of_expired_lock_does_not_double_count() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("drift_q", 100, "p"))
.unwrap();
let s = storage.get_queue_stats("drift_q").unwrap();
assert_eq!((s.available, s.locked), (1, 0), "after push");
let m1 = storage.pop("drift_q", "c1", 1).unwrap().unwrap();
let s = storage.get_queue_stats("drift_q").unwrap();
assert_eq!((s.available, s.locked), (0, 1), "after first pop");
std::thread::sleep(std::time::Duration::from_secs(2));
let m2 = storage.pop("drift_q", "c2", 30).unwrap().unwrap();
assert_eq!(m1.id, m2.id, "should be the same message");
let s = storage.get_queue_stats("drift_q").unwrap();
assert_eq!(
(s.available, s.locked),
(0, 1),
"re-pop of expired lock must not double-count locked"
);
assert!(storage.ack("drift_q", &m2.id, "c2").unwrap());
let s = storage.get_queue_stats("drift_q").unwrap();
assert_eq!((s.available, s.locked), (0, 0), "after ack");
}
#[test]
fn test_unlock_expired_recovers_cap_overflow_lock() {
let prev = std::env::var("QRUSTY_MAX_LOCKED_INDEX").ok();
std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", "1");
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path().to_str().unwrap()).unwrap();
match prev {
Some(v) => std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", v),
None => std::env::remove_var("QRUSTY_MAX_LOCKED_INDEX"),
}
let storage = Arc::new(storage);
storage
.push(create_test_message("cap_queue", 100, "first"))
.unwrap();
storage
.push(create_test_message("cap_queue", 90, "second"))
.unwrap();
let locked1 = storage.pop("cap_queue", "consumer1", 1).unwrap().unwrap();
let locked2 = storage.pop("cap_queue", "consumer1", 1).unwrap().unwrap();
assert!(locked1.locked_until.is_some());
assert!(locked2.locked_until.is_some());
storage.ack("cap_queue", &locked1.id, "consumer1").unwrap();
std::thread::sleep(std::time::Duration::from_secs(2));
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(
unlocked_count, 1,
"sticky flag must force a full scan even after the index drops below cap"
);
let recovered = storage.pop("cap_queue", "consumer2", 30).unwrap().unwrap();
assert_eq!(recovered.id, locked2.id);
}
#[test]
fn test_allow_duplicates_default_true_allows_duplicate_payloads() {
let (storage, _temp_dir) = create_test_storage();
let first = create_test_message("dup_default_queue", 100, "same");
let second = create_test_message("dup_default_queue", 50, "same");
storage.push(first).unwrap();
let result = storage.push(second);
assert!(result.is_ok());
}
#[test]
fn test_allow_duplicates_false_rejects_unlocked_duplicate_on_publish() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
};
storage.configure_queue("no_dups_queue", config);
let first = create_test_message("no_dups_queue", 100, "same");
let second = create_test_message("no_dups_queue", 50, "same");
storage.push(first).unwrap();
let result = storage.push(second);
assert!(result.is_err());
}
#[test]
fn test_allow_duplicates_false_transition_nack_unlocks_not_drops() {
let (storage, _temp_dir) = create_test_storage();
storage.configure_queue(
"dups_then_disable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: true,
..Default::default()
},
);
let now = Utc::now();
let mut first = create_test_message("dups_then_disable_queue", 100, "same");
first.created_at = now - chrono::Duration::seconds(2);
let first_id = first.id.clone();
let mut second = create_test_message("dups_then_disable_queue", 50, "same");
second.created_at = now - chrono::Duration::seconds(1);
storage.push(first).unwrap();
storage.push(second).unwrap();
let locked = storage
.pop("dups_then_disable_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(locked.id, first_id);
storage.configure_queue(
"dups_then_disable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let success = storage
.nack("dups_then_disable_queue", &first_id, "consumer1")
.unwrap();
assert!(success);
let a = storage
.pop("dups_then_disable_queue", "consumer2", 30)
.unwrap();
assert!(a.is_some());
let b = storage
.pop("dups_then_disable_queue", "consumer3", 30)
.unwrap();
assert!(b.is_some());
}
#[test]
fn test_configure_queue_disabling_duplicates_dedupes_existing_unlocked_messages() {
let (storage, _temp_dir) = create_test_storage();
storage.configure_queue(
"dedupe_on_disable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: true,
..Default::default()
},
);
let high = create_test_message("dedupe_on_disable_queue", 100, "same");
let low = create_test_message("dedupe_on_disable_queue", 50, "same");
storage.push(high).unwrap();
storage.push(low).unwrap();
storage.configure_queue(
"dedupe_on_disable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let msg = storage
.pop("dedupe_on_disable_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(msg.payload, "same");
assert_eq!(msg.priority, Priority::Numeric(100));
let acked = storage
.ack("dedupe_on_disable_queue", &msg.id, "consumer1")
.unwrap();
assert!(acked);
let empty = storage
.pop("dedupe_on_disable_queue", "consumer2", 30)
.unwrap();
assert!(empty.is_none());
}
#[test]
fn test_configure_queue_dedup_rebuild_updates_counters() {
let (storage, _temp_dir) = create_test_storage();
storage.configure_queue(
"dedup_counter_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: true,
..Default::default()
},
);
for i in 0..3 {
storage
.push(create_test_message(
"dedup_counter_queue",
100,
&format!("p-{i}"),
))
.unwrap();
}
for _ in 0..2 {
storage
.push(create_test_message("dedup_counter_queue", 100, "p-0"))
.unwrap();
}
let pre = storage.get_queue_stats("dedup_counter_queue").unwrap();
assert_eq!(
pre.available, 5,
"precondition: counters reflect all 5 inserted messages"
);
storage.configure_queue(
"dedup_counter_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let post = storage.get_queue_stats("dedup_counter_queue").unwrap();
assert_eq!(
post.available, 3,
"counters must reflect post-dedupe count (2 dupes removed), not pre-dedupe 5"
);
assert_eq!(post.locked, 0);
let mut popped = 0;
while storage
.pop("dedup_counter_queue", "c1", 3600)
.unwrap()
.is_some()
{
popped += 1;
}
assert_eq!(popped, 3, "pop count must match post-dedupe counter");
}
#[test]
fn test_configure_queue_enabling_duplicates_does_not_modify_existing_messages() {
let (storage, _temp_dir) = create_test_storage();
storage.configure_queue(
"no_change_on_enable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let first = create_test_message("no_change_on_enable_queue", 100, "a");
let second = create_test_message("no_change_on_enable_queue", 50, "b");
storage.push(first).unwrap();
storage.push(second).unwrap();
storage.configure_queue(
"no_change_on_enable_queue",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: true,
..Default::default()
},
);
let msg1 = storage
.pop("no_change_on_enable_queue", "consumer1", 30)
.unwrap()
.unwrap();
let acked1 = storage
.ack("no_change_on_enable_queue", &msg1.id, "consumer1")
.unwrap();
assert!(acked1);
let msg2 = storage
.pop("no_change_on_enable_queue", "consumer2", 30)
.unwrap()
.unwrap();
let acked2 = storage
.ack("no_change_on_enable_queue", &msg2.id, "consumer2")
.unwrap();
assert!(acked2);
let empty = storage
.pop("no_change_on_enable_queue", "consumer3", 30)
.unwrap();
assert!(empty.is_none());
}
#[test]
fn test_timeout_monitor_no_expired_messages() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
let message = create_test_message("timeout_queue", 100, "long_timeout");
storage.push(message).unwrap();
storage
.pop("timeout_queue", "consumer1", 60)
.unwrap()
.unwrap();
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(unlocked_count, 0);
let empty = storage.pop("timeout_queue", "consumer2", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_timeout_monitor_multiple_expired_messages() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
for i in 0..5 {
let message = create_test_message("batch_queue", i * 10, &format!("msg_{}", i));
storage.push(message).unwrap();
storage
.pop("batch_queue", &format!("consumer_{}", i), 1)
.unwrap()
.unwrap();
}
std::thread::sleep(std::time::Duration::from_secs(2));
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(unlocked_count, 5);
let mut recovered_messages = Vec::new();
for _ in 0..5 {
if let Some(msg) = storage.pop("batch_queue", "recovery_consumer", 30).unwrap() {
recovered_messages.push(msg);
}
}
assert_eq!(recovered_messages.len(), 5);
for msg in recovered_messages {
assert_eq!(msg.retry_count, 2);
assert_eq!(msg.locked_by, Some("recovery_consumer".to_string()));
}
}
#[test]
fn test_timeout_monitor_mixed_expired_and_valid() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
let short_msg = create_test_message("mixed_queue", 200, "short_timeout");
let long_msg = create_test_message("mixed_queue", 100, "long_timeout");
storage.push(short_msg.clone()).unwrap();
storage.push(long_msg.clone()).unwrap();
let first_locked = storage
.pop("mixed_queue", "short_consumer", 1) .unwrap()
.unwrap();
assert_eq!(first_locked.payload, "short_timeout");
let second_locked = storage
.pop("mixed_queue", "long_consumer", 60) .unwrap()
.unwrap();
assert_eq!(second_locked.payload, "long_timeout");
std::thread::sleep(std::time::Duration::from_secs(2));
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert_eq!(unlocked_count, 1);
let available_msg = storage
.pop("mixed_queue", "recovery_consumer", 30)
.unwrap()
.unwrap();
assert_eq!(available_msg.payload, "short_timeout");
assert_eq!(available_msg.retry_count, 2);
let empty = storage.pop("mixed_queue", "another_consumer", 30).unwrap();
assert!(empty.is_none());
}
#[test]
fn test_timeout_monitor_acked_message_cleanup() {
let (storage, _temp_dir) = create_test_storage();
let storage = Arc::new(storage);
let message = create_test_message("ack_queue", 100, "ack_test");
let message_id = message.id.clone();
storage.push(message).unwrap();
storage.pop("ack_queue", "consumer1", 1).unwrap().unwrap();
let ack_result = storage.ack("ack_queue", &message_id, "consumer1").unwrap();
assert!(ack_result);
std::thread::sleep(std::time::Duration::from_secs(2));
let unlocked_count = storage.unlock_expired_messages().unwrap();
assert!(unlocked_count == 0);
}
#[test]
fn test_queue_stats_empty_system() {
let (storage, _temp_dir) = create_test_storage();
let all_stats = storage.get_all_queue_stats().unwrap();
assert_eq!(all_stats.len(), 0);
let queues = storage.list_queues().unwrap();
assert_eq!(queues.len(), 0);
}
#[test]
fn test_queue_stats_single_queue() {
let (storage, _temp_dir) = create_test_storage();
let msg1 = create_test_message("test_queue", 100, "msg1");
let msg2 = create_test_message("test_queue", 50, "msg2");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let stats = storage.get_queue_stats("test_queue").unwrap();
assert_eq!(stats.name, "test_queue");
assert_eq!(stats.available, 2);
assert_eq!(stats.locked, 0);
assert_eq!(stats.total, 2);
let all_stats = storage.get_all_queue_stats().unwrap();
assert_eq!(all_stats.len(), 1);
assert_eq!(all_stats[0].name, "test_queue");
assert_eq!(all_stats[0].total, 2);
let queues = storage.list_queues().unwrap();
assert_eq!(queues, vec!["test_queue"]);
}
#[test]
fn test_queue_stats_multiple_queues() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("orders", 100, "order1"))
.unwrap();
storage
.push(create_test_message("orders", 50, "order2"))
.unwrap();
storage
.push(create_test_message("notifications", 200, "notif1"))
.unwrap();
storage
.push(create_test_message("analytics", 10, "analytics1"))
.unwrap();
let all_stats = storage.get_all_queue_stats().unwrap();
assert_eq!(all_stats.len(), 3);
assert_eq!(all_stats[0].name, "analytics");
assert_eq!(all_stats[1].name, "notifications");
assert_eq!(all_stats[2].name, "orders");
assert_eq!(all_stats[0].total, 1); assert_eq!(all_stats[1].total, 1); assert_eq!(all_stats[2].total, 2);
let orders_stats = storage.get_queue_stats("orders").unwrap();
assert_eq!(orders_stats.available, 2);
assert_eq!(orders_stats.locked, 0);
let queues = storage.list_queues().unwrap();
assert_eq!(queues, vec!["analytics", "notifications", "orders"]);
}
#[test]
fn test_queue_stats_with_locked_messages() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("work_queue", 100, "task1"))
.unwrap();
storage
.push(create_test_message("work_queue", 50, "task2"))
.unwrap();
storage
.push(create_test_message("work_queue", 25, "task3"))
.unwrap();
storage.pop("work_queue", "worker1", 30).unwrap().unwrap();
let stats = storage.get_queue_stats("work_queue").unwrap();
assert_eq!(stats.available, 2);
assert_eq!(stats.locked, 1);
assert_eq!(stats.total, 3);
storage.pop("work_queue", "worker2", 30).unwrap().unwrap();
let stats = storage.get_queue_stats("work_queue").unwrap();
assert_eq!(stats.available, 1);
assert_eq!(stats.locked, 2);
assert_eq!(stats.total, 3);
let all_stats = storage.get_all_queue_stats().unwrap();
assert_eq!(all_stats.len(), 1);
assert_eq!(all_stats[0].available, 1);
assert_eq!(all_stats[0].locked, 2);
}
#[test]
fn test_queue_stats_after_ack_nack() {
let (storage, _temp_dir) = create_test_storage();
let msg1 = create_test_message("process_queue", 100, "task1");
let msg2 = create_test_message("process_queue", 50, "task2");
let msg1_id = msg1.id.clone();
let msg2_id = msg2.id.clone();
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let stats = storage.get_queue_stats("process_queue").unwrap();
assert_eq!(stats.total, 2);
assert_eq!(stats.available, 2);
storage
.pop("process_queue", "worker1", 30)
.unwrap()
.unwrap();
storage
.pop("process_queue", "worker2", 30)
.unwrap()
.unwrap();
let stats = storage.get_queue_stats("process_queue").unwrap();
assert_eq!(stats.total, 2);
assert_eq!(stats.locked, 2);
assert_eq!(stats.available, 0);
storage.ack("process_queue", &msg1_id, "worker1").unwrap();
let stats = storage.get_queue_stats("process_queue").unwrap();
assert_eq!(stats.total, 1);
assert_eq!(stats.locked, 1);
assert_eq!(stats.available, 0);
storage.nack("process_queue", &msg2_id, "worker2").unwrap();
let stats = storage.get_queue_stats("process_queue").unwrap();
assert_eq!(stats.total, 1);
assert_eq!(stats.locked, 0);
assert_eq!(stats.available, 1);
}
#[test]
fn test_queue_stats_nonexistent_queue() {
let (storage, _temp_dir) = create_test_storage();
let stats = storage.get_queue_stats("nonexistent").unwrap();
assert_eq!(stats.name, "nonexistent");
assert_eq!(stats.available, 0);
assert_eq!(stats.locked, 0);
assert_eq!(stats.total, 0);
}
#[test]
fn test_delete_queue() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("test_queue", QueueConfig::default());
let msg1 = create_test_message("test_queue", 100, "payload1");
let msg2 = create_test_message("test_queue", 200, "payload2");
let msg3 = create_test_message("test_queue", 50, "payload3");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
storage.push(msg3).unwrap();
let stats = storage.get_queue_stats("test_queue").unwrap();
assert_eq!(stats.total, 3);
let deleted_count = storage.delete_queue("test_queue").unwrap();
assert_eq!(deleted_count, 3);
let stats_after = storage.get_queue_stats("test_queue").unwrap();
assert_eq!(stats_after.total, 0);
let result = storage.pop("test_queue", "consumer", 1).unwrap();
assert!(result.is_none());
}
#[test]
fn test_delete_queue_with_locked_messages() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("locked_queue", QueueConfig::default());
let msg1 = create_test_message("locked_queue", 100, "payload1");
let msg2 = create_test_message("locked_queue", 200, "payload2");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let _consumed = storage
.pop("locked_queue", "consumer1", 30)
.unwrap()
.unwrap();
let stats = storage.get_queue_stats("locked_queue").unwrap();
assert_eq!(stats.total, 2);
assert_eq!(stats.locked, 1);
assert_eq!(stats.available, 1);
let deleted_count = storage.delete_queue("locked_queue").unwrap();
assert_eq!(deleted_count, 2);
let stats_after = storage.get_queue_stats("locked_queue").unwrap();
assert_eq!(stats_after.total, 0);
}
#[test]
fn test_purge_queue() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("purge_test", QueueConfig::default());
let msg1 = create_test_message("purge_test", 100, "payload1");
let msg2 = create_test_message("purge_test", 200, "payload2");
let msg3 = create_test_message("purge_test", 50, "payload3");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
storage.push(msg3).unwrap();
let stats = storage.get_queue_stats("purge_test").unwrap();
assert_eq!(stats.total, 3);
let purged_count = storage.purge_queue("purge_test").unwrap();
assert_eq!(purged_count, 3);
let stats_after = storage.get_queue_stats("purge_test").unwrap();
assert_eq!(stats_after.total, 0);
let new_msg = create_test_message("purge_test", 300, "new_payload");
storage.push(new_msg).unwrap();
let final_stats = storage.get_queue_stats("purge_test").unwrap();
assert_eq!(final_stats.total, 1);
}
#[test]
fn test_purge_queue_preserves_config() {
let (storage, _temp_dir) = create_test_storage();
let custom_config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
..Default::default()
};
storage.create_queue("config_test", custom_config.clone());
let msg1 = create_test_message("config_test", 100, "payload1");
let msg2 = create_test_message("config_test", 50, "payload2");
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let consumed1 = storage.pop("config_test", "consumer", 30).unwrap().unwrap();
assert_eq!(consumed1.priority, Priority::Numeric(50));
storage
.ack("config_test", &consumed1.id, "consumer")
.unwrap();
let purged_count = storage.purge_queue("config_test").unwrap();
assert_eq!(purged_count, 1);
let new_msg1 = create_test_message("config_test", 200, "new1");
let new_msg2 = create_test_message("config_test", 150, "new2");
storage.push(new_msg1).unwrap();
storage.push(new_msg2).unwrap();
let consumed_new = storage
.pop("config_test", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(consumed_new.priority, Priority::Numeric(150)); }
#[test]
fn test_delete_nonexistent_queue() {
let (storage, _temp_dir) = create_test_storage();
let deleted_count = storage.delete_queue("nonexistent").unwrap();
assert_eq!(deleted_count, 0);
}
#[test]
fn test_purge_nonexistent_queue() {
let (storage, _temp_dir) = create_test_storage();
let purged_count = storage.purge_queue("nonexistent").unwrap();
assert_eq!(purged_count, 0);
}
#[test]
fn test_queue_isolation_delete_purge() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("queue1", QueueConfig::default());
storage.create_queue("queue2", QueueConfig::default());
let msg1_q1 = create_test_message("queue1", 100, "q1_payload1");
let msg2_q1 = create_test_message("queue1", 200, "q1_payload2");
let msg1_q2 = create_test_message("queue2", 150, "q2_payload1");
let msg2_q2 = create_test_message("queue2", 250, "q2_payload2");
storage.push(msg1_q1).unwrap();
storage.push(msg2_q1).unwrap();
storage.push(msg1_q2).unwrap();
storage.push(msg2_q2).unwrap();
let stats1 = storage.get_queue_stats("queue1").unwrap();
let stats2 = storage.get_queue_stats("queue2").unwrap();
assert_eq!(stats1.total, 2);
assert_eq!(stats2.total, 2);
let deleted_count = storage.delete_queue("queue1").unwrap();
assert_eq!(deleted_count, 2);
let stats1_after = storage.get_queue_stats("queue1").unwrap();
let stats2_after = storage.get_queue_stats("queue2").unwrap();
assert_eq!(stats1_after.total, 0);
assert_eq!(stats2_after.total, 2);
let purged_count = storage.purge_queue("queue2").unwrap();
assert_eq!(purged_count, 2);
let stats2_final = storage.get_queue_stats("queue2").unwrap();
assert_eq!(stats2_final.total, 0);
}
#[test]
fn test_has_duplicate_payload_excludes_message_id() {
let (storage, _temp_dir) = create_test_storage();
let message = create_test_message("dup_exclude_queue", 1, "same");
let message_id = message.id.clone();
storage.push(message).unwrap();
let has_dupe = storage
.has_duplicate_payload("dup_exclude_queue", "same", Some(&message_id))
.unwrap();
assert!(!has_dupe);
}
#[test]
fn test_allow_duplicates_false_blocks_push_when_message_is_locked() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue(
"locked_dupe_queue",
QueueConfig {
allow_duplicates: false,
..Default::default()
},
);
let message_1 = create_test_message("locked_dupe_queue", 100, "dup_payload");
storage.push(message_1).unwrap();
storage
.pop("locked_dupe_queue", "consumer_lock", 30)
.unwrap()
.unwrap();
let message_2 = create_test_message("locked_dupe_queue", 90, "dup_payload");
let push_res = storage.push(message_2);
assert!(
push_res.is_err(),
"Push must be rejected while payload is locked"
);
}
#[test]
fn test_stats_and_queue_listing_skip_invalid_and_corrupt_entries() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let messages_path = format!("{}/messages", temp_dir.path().to_str().unwrap());
{
let mut opts = Options::default();
opts.create_if_missing(true);
let db = DB::open(&opts, &messages_path).expect("Failed to open raw test DB");
db.put(b"invalid_key", b"not_json").unwrap();
db.put(b"_dlq/somequeue/someid", b"not_json").unwrap();
db.put(b"_queue_config/ignored_queue", b"not_json").unwrap();
db.put(
b"_queue_config/ok_queue",
serde_json::to_vec(&QueueConfig::default()).unwrap(),
)
.unwrap();
db.put(b"corrupt_queue/0/0/corrupt_id", b"not_json")
.unwrap();
let ok_message = create_test_message("ok_queue", 1, "ok");
db.put(
b"ok_queue/0/0/ok_id",
serde_json::to_vec(&ok_message).unwrap(),
)
.unwrap();
}
let storage = Storage::new(temp_dir.path().to_str().unwrap()).unwrap();
let all_stats = storage.get_all_queue_stats().unwrap();
assert!(all_stats.iter().any(|s| s.name == "ok_queue"));
let queues = storage.list_queues().unwrap();
assert!(queues.contains(&"ok_queue".to_string()));
assert!(queues.contains(&"corrupt_queue".to_string()));
assert!(!queues.iter().any(|q| q.starts_with("_dlq")));
let corrupt_stats = storage.get_queue_stats("corrupt_queue").unwrap();
assert_eq!(corrupt_stats.total, 0);
}
#[test]
fn test_allow_duplicates_false_allows_same_payload_after_ack() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
};
storage.configure_queue("no_dups_ack_queue", config);
let first = create_test_message("no_dups_ack_queue", 100, "same_payload");
let first_id = first.id.clone();
storage.push(first).unwrap();
let duplicate_while_available = create_test_message("no_dups_ack_queue", 100, "same_payload");
let result_while_available = storage.push(duplicate_while_available);
assert!(
result_while_available.is_err(),
"Should reject duplicate while original is available (unlocked)"
);
let locked = storage
.pop("no_dups_ack_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(locked.id, first_id);
let ack_result = storage
.ack("no_dups_ack_queue", &first_id, "consumer1")
.unwrap();
assert!(ack_result, "ACK should succeed");
let second = create_test_message("no_dups_ack_queue", 100, "same_payload");
let second_id = second.id.clone();
let result = storage.push(second);
assert!(
result.is_ok(),
"Should allow message with same payload after ACK: {:?}",
result
);
let popped = storage
.pop("no_dups_ack_queue", "consumer2", 30)
.unwrap()
.unwrap();
assert_eq!(popped.id, second_id);
assert_eq!(popped.payload, "same_payload");
}
#[test]
fn test_allow_duplicates_false_allows_same_payload_after_nack_to_dlq() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
};
storage.configure_queue("no_dups_nack_queue", config);
let mut first = create_test_message("no_dups_nack_queue", 100, "same_payload");
first.max_retries = 0;
first.retry_count = 0;
let first_id = first.id.clone();
storage.push(first).unwrap();
let locked = storage
.pop("no_dups_nack_queue", "consumer1", 30)
.unwrap()
.unwrap();
assert_eq!(locked.id, first_id);
assert_eq!(locked.retry_count, 1);
let nack_result = storage
.nack("no_dups_nack_queue", &first_id, "consumer1")
.unwrap();
assert!(nack_result, "NACK should succeed");
let empty = storage.pop("no_dups_nack_queue", "consumer2", 30).unwrap();
assert!(empty.is_none(), "Queue should be empty after DLQ move");
let second = create_test_message("no_dups_nack_queue", 100, "same_payload");
let second_id = second.id.clone();
let result = storage.push(second);
assert!(
result.is_ok(),
"Should allow message with same payload after NACK to DLQ: {:?}",
result
);
let popped = storage
.pop("no_dups_nack_queue", "consumer3", 30)
.unwrap()
.unwrap();
assert_eq!(popped.id, second_id);
assert_eq!(popped.payload, "same_payload");
}
#[test]
fn test_batch_ack_all_found() {
let (storage, _temp_dir) = create_test_storage();
let mut ids: Vec<String> = Vec::new();
for i in 0..3 {
let m = create_test_message("batch_q", 100, &format!("payload-{}", i));
let id = m.id.clone();
storage.push(m).unwrap();
ids.push(id);
}
for _ in 0..3 {
storage.pop("batch_q", "consumer1", 3600).unwrap();
}
let result = storage.batch_ack("batch_q", "consumer1", &ids).unwrap();
assert_eq!(result.acked.len(), 3);
assert_eq!(result.not_found.len(), 0);
let none = storage.pop("batch_q", "consumer1", 30).unwrap();
assert!(none.is_none());
}
#[test]
fn test_batch_ack_partial_not_found() {
let (storage, _temp_dir) = create_test_storage();
let msg = create_test_message("batch_q", 100, "real");
let real_id = msg.id.clone();
storage.push(msg).unwrap();
storage.pop("batch_q", "consumer1", 3600).unwrap();
let ids = vec![real_id.clone(), "nonexistent-id".to_string()];
let result = storage.batch_ack("batch_q", "consumer1", &ids).unwrap();
assert_eq!(result.acked, vec![real_id]);
assert_eq!(result.not_found, vec!["nonexistent-id".to_string()]);
}
#[test]
fn test_batch_ack_wrong_consumer_not_acked() {
let (storage, _temp_dir) = create_test_storage();
let msg = create_test_message("batch_q", 100, "data");
let id = msg.id.clone();
storage.push(msg).unwrap();
storage.pop("batch_q", "consumer1", 3600).unwrap();
let result = storage
.batch_ack("batch_q", "wrong-consumer", std::slice::from_ref(&id))
.unwrap();
assert_eq!(result.acked.len(), 0);
assert_eq!(result.not_found, vec![id]);
}
#[test]
fn test_batch_ack_empty_list() {
let (storage, _temp_dir) = create_test_storage();
let result = storage.batch_ack("batch_q", "consumer1", &[]).unwrap();
assert_eq!(result.acked.len(), 0);
assert_eq!(result.not_found.len(), 0);
}
#[test]
fn test_batch_ack_removes_from_locked_index() {
let (storage, _temp_dir) = create_test_storage();
let msg = create_test_message("batch_q", 100, "data");
let id = msg.id.clone();
storage.push(msg).unwrap();
let locked = storage.pop("batch_q", "consumer1", 3600).unwrap().unwrap();
assert_eq!(locked.id, id);
storage
.batch_ack("batch_q", "consumer1", std::slice::from_ref(&id))
.unwrap();
let unlocked = storage.unlock_expired_messages().unwrap();
assert_eq!(unlocked, 0, "locked_index should be clean after batch_ack");
}
#[test]
fn test_batch_nack_all_unlocked_for_retry() {
let (storage, _temp_dir) = create_test_storage();
let mut ids: Vec<String> = Vec::new();
for i in 0..3 {
let m = create_test_message("batch_q", 100, &format!("p-{}", i));
let id = m.id.clone();
storage.push(m).unwrap();
ids.push(id);
}
for _ in 0..3 {
storage.pop("batch_q", "consumer1", 3600).unwrap();
}
let result = storage.batch_nack("batch_q", "consumer1", &ids).unwrap();
assert_eq!(result.unlocked.len(), 3);
assert_eq!(result.dead_lettered.len(), 0);
assert_eq!(result.dropped.len(), 0);
assert_eq!(result.not_found.len(), 0);
for _ in 0..3 {
let m = storage.pop("batch_q", "consumer2", 30).unwrap();
assert!(m.is_some());
}
}
#[test]
fn test_batch_nack_exceeded_retries_moves_to_dlq() {
let (storage, _temp_dir) = create_test_storage();
let mut msg = create_test_message("batch_q", 100, "exhausted");
msg.max_retries = 1;
let id = msg.id.clone();
storage.push(msg).unwrap();
storage.pop("batch_q", "consumer1", 3600).unwrap();
let result = storage.batch_nack("batch_q", "consumer1", &[id]).unwrap();
assert_eq!(result.dead_lettered.len(), 1);
assert_eq!(result.unlocked.len(), 0);
let none = storage.pop("batch_q", "consumer2", 30).unwrap();
assert!(none.is_none());
}
#[test]
fn test_batch_nack_no_duplicates_push_rejected_while_locked() {
let (storage, _temp_dir) = create_test_storage();
storage.configure_queue(
"no_dup_q",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let mut msg_a = create_test_message("no_dup_q", 100, "same-payload");
msg_a.max_retries = 10;
let id_a = msg_a.id.clone();
storage.push(msg_a).unwrap();
storage.pop("no_dup_q", "consumer1", 3600).unwrap();
let mut msg_b = create_test_message("no_dup_q", 50, "same-payload");
msg_b.max_retries = 10;
assert!(
storage.push(msg_b).is_err(),
"Push of duplicate payload must fail while first message is locked"
);
let result = storage
.batch_nack("no_dup_q", "consumer1", &[id_a])
.unwrap();
assert_eq!(result.unlocked.len(), 1, "should unlock for retry");
assert_eq!(
result.dropped.len(),
0,
"drop path is unreachable under DLV-0010"
);
let a = storage.pop("no_dup_q", "consumer2", 30).unwrap();
assert!(a.is_some());
}
#[test]
fn test_batch_nack_wrong_consumer_not_nacked() {
let (storage, _temp_dir) = create_test_storage();
let msg = create_test_message("batch_q", 100, "data");
let id = msg.id.clone();
storage.push(msg).unwrap();
storage.pop("batch_q", "consumer1", 3600).unwrap();
let result = storage
.batch_nack("batch_q", "wrong-consumer", std::slice::from_ref(&id))
.unwrap();
assert_eq!(result.not_found, vec![id]);
assert_eq!(result.unlocked.len(), 0);
}
#[test]
fn test_batch_nack_empty_list() {
let (storage, _temp_dir) = create_test_storage();
let result = storage.batch_nack("batch_q", "consumer1", &[]).unwrap();
assert_eq!(result.unlocked.len(), 0);
assert_eq!(result.dead_lettered.len(), 0);
assert_eq!(result.dropped.len(), 0);
assert_eq!(result.not_found.len(), 0);
}
fn clear_sticky_flag(storage: &Storage) {
let _ = storage.unlock_expired_messages().unwrap();
}
#[test]
fn test_batch_ack_fast_path_after_sticky_cleared() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let mut ids = Vec::new();
for i in 0..3 {
let m = create_test_message("fast_q", 100, &format!("p-{i}"));
ids.push(m.id.clone());
storage.push(m).unwrap();
}
for _ in 0..3 {
storage.pop("fast_q", "c1", 3600).unwrap().unwrap();
}
let result = storage.batch_ack("fast_q", "c1", &ids).unwrap();
assert_eq!(result.acked.len(), 3);
assert!(result.not_found.is_empty());
}
#[test]
fn test_batch_ack_fast_path_returns_not_found_without_scan() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let bogus_ids = vec!["nonexistent-1".to_string(), "nonexistent-2".to_string()];
let result = storage.batch_ack("any_queue", "c1", &bogus_ids).unwrap();
assert!(result.acked.is_empty());
assert_eq!(result.not_found.len(), 2);
}
#[test]
fn test_batch_ack_fallback_when_sticky_flag_set() {
let prev = std::env::var("QRUSTY_MAX_LOCKED_INDEX").ok();
std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", "1");
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path().to_str().unwrap()).unwrap();
match prev {
Some(v) => std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", v),
None => std::env::remove_var("QRUSTY_MAX_LOCKED_INDEX"),
}
storage
.push(create_test_message("overflow_q", 100, "first"))
.unwrap();
storage
.push(create_test_message("overflow_q", 90, "second"))
.unwrap();
let m1 = storage.pop("overflow_q", "c1", 3600).unwrap().unwrap();
let m2 = storage.pop("overflow_q", "c1", 3600).unwrap().unwrap();
let ids = vec![m1.id.clone(), m2.id.clone()];
let result = storage.batch_ack("overflow_q", "c1", &ids).unwrap();
assert_eq!(
result.acked.len(),
2,
"batch_ack fallback must find cap-overflow locks"
);
assert!(result.not_found.is_empty());
}
#[test]
fn test_batch_nack_fast_path_after_sticky_cleared() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let mut ids = Vec::new();
for i in 0..3 {
let m = create_test_message("fast_q", 100, &format!("p-{i}"));
ids.push(m.id.clone());
storage.push(m).unwrap();
}
for _ in 0..3 {
storage.pop("fast_q", "c1", 3600).unwrap().unwrap();
}
let result = storage.batch_nack("fast_q", "c1", &ids).unwrap();
assert_eq!(result.unlocked.len(), 3);
assert!(result.dead_lettered.is_empty());
assert!(result.not_found.is_empty());
}
#[test]
fn test_batch_nack_fallback_when_sticky_flag_set() {
let prev = std::env::var("QRUSTY_MAX_LOCKED_INDEX").ok();
std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", "1");
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path().to_str().unwrap()).unwrap();
match prev {
Some(v) => std::env::set_var("QRUSTY_MAX_LOCKED_INDEX", v),
None => std::env::remove_var("QRUSTY_MAX_LOCKED_INDEX"),
}
storage
.push(create_test_message("overflow_q", 100, "first"))
.unwrap();
storage
.push(create_test_message("overflow_q", 90, "second"))
.unwrap();
let m1 = storage.pop("overflow_q", "c1", 3600).unwrap().unwrap();
let m2 = storage.pop("overflow_q", "c1", 3600).unwrap().unwrap();
let ids = vec![m1.id.clone(), m2.id.clone()];
let result = storage.batch_nack("overflow_q", "c1", &ids).unwrap();
assert_eq!(
result.unlocked.len(),
2,
"batch_nack fallback must find cap-overflow locks"
);
}
#[test]
fn test_ack_fast_path_cleans_stale_secondary_index_entry() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let ghost_id = "ghost-id";
let ghost_key = "some_queue/ghost-key";
storage.__test_insert_locked_id_index_entry("some_queue", ghost_id, ghost_key);
assert!(!storage.ack("some_queue", ghost_id, "c1").unwrap());
assert!(
!storage.__test_locked_id_index_has("some_queue", ghost_id),
"ack fast path must clean stale secondary-index entries"
);
}
#[test]
fn test_nack_fast_path_cleans_stale_secondary_index_entry() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let ghost_id = "ghost-id";
let ghost_key = "some_queue/ghost-key";
storage.__test_insert_locked_id_index_entry("some_queue", ghost_id, ghost_key);
assert!(!storage.nack("some_queue", ghost_id, "c1").unwrap());
assert!(
!storage.__test_locked_id_index_has("some_queue", ghost_id),
"nack fast path must clean stale secondary-index entries"
);
}
#[test]
fn test_renew_fast_path_cleans_stale_secondary_index_entry() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let ghost_id = "ghost-id";
let ghost_key = "some_queue/ghost-key";
storage.__test_insert_locked_id_index_entry("some_queue", ghost_id, ghost_key);
assert!(!storage.renew("some_queue", ghost_id, "c1", 30).unwrap());
assert!(
!storage.__test_locked_id_index_has("some_queue", ghost_id),
"renew fast path must clean stale secondary-index entries"
);
}
#[test]
fn test_batch_ack_fast_path_cleans_stale_secondary_index_entries() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let ghost_id = "ghost-id";
let ghost_key = "some_queue/ghost-key";
storage.__test_insert_locked_id_index_entry("some_queue", ghost_id, ghost_key);
assert!(
storage.__test_locked_id_index_has("some_queue", ghost_id),
"precondition: stale entry is present"
);
let result = storage
.batch_ack("some_queue", "c1", &[ghost_id.to_string()])
.unwrap();
assert!(result.acked.is_empty());
assert_eq!(result.not_found, vec![ghost_id.to_string()]);
assert!(
!storage.__test_locked_id_index_has("some_queue", ghost_id),
"batch_ack fast path must clean stale secondary-index entries"
);
}
#[test]
fn test_batch_nack_fast_path_cleans_stale_secondary_index_entries() {
let (storage, _temp_dir) = create_test_storage();
clear_sticky_flag(&storage);
let ghost_id = "ghost-id";
let ghost_key = "some_queue/ghost-key";
storage.__test_insert_locked_id_index_entry("some_queue", ghost_id, ghost_key);
let result = storage
.batch_nack("some_queue", "c1", &[ghost_id.to_string()])
.unwrap();
assert!(result.unlocked.is_empty());
assert!(result.dead_lettered.is_empty());
assert_eq!(result.not_found, vec![ghost_id.to_string()]);
assert!(
!storage.__test_locked_id_index_has("some_queue", ghost_id),
"batch_nack fast path must clean stale secondary-index entries"
);
}
#[test]
fn test_rename_queue_does_not_touch_unrelated_locked_id_index_entries() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("other", 100, "keep"))
.unwrap();
let other = storage.pop("other", "c1", 3600).unwrap().unwrap();
assert!(
storage.__test_locked_id_index_has("other", &other.id),
"precondition: unrelated queue's lock is tracked"
);
storage
.push(create_test_message("from_q", 100, "move"))
.unwrap();
let moved = storage.pop("from_q", "c1", 3600).unwrap().unwrap();
storage.rename_queue("from_q", "to_q").unwrap();
assert!(
storage.__test_locked_id_index_has("other", &other.id),
"rename must not touch unrelated queues' secondary-index entries"
);
assert!(
!storage.__test_locked_id_index_has("from_q", &moved.id),
"old queue name must be fully cleared from the secondary index"
);
assert!(
storage.__test_locked_id_index_has("to_q", &moved.id),
"new queue name must contain the rekeyed entry"
);
}
#[test]
fn test_rename_queue_leaves_unrelated_queue_locks_alone() {
let (storage, _temp_dir) = create_test_storage();
storage
.push(create_test_message("unrelated", 100, "keep"))
.unwrap();
let unrelated = storage.pop("unrelated", "c1", 3600).unwrap().unwrap();
storage
.push(create_test_message("from_q", 100, "move"))
.unwrap();
let moved = storage.pop("from_q", "c1", 3600).unwrap().unwrap();
storage.rename_queue("from_q", "to_q").unwrap();
assert!(storage.ack("unrelated", &unrelated.id, "c1").unwrap());
assert!(storage.ack("to_q", &moved.id, "c1").unwrap());
}
fn no_dup_config() -> QueueConfig {
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
}
}
fn allow_dup_config() -> QueueConfig {
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: true,
..Default::default()
}
}
#[test]
fn test_pset_blocks_push_while_locked() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "payload-x");
storage.push(m1).unwrap();
let _locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
let m2 = create_test_message("nd", 100, "payload-x");
let result = storage.push(m2);
assert!(
result.is_err(),
"Push of duplicate payload must be rejected while original is locked"
);
}
#[test]
fn test_pset_allows_push_after_ack() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "payload-reuse");
storage.push(m1).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
storage.ack("nd", &locked.id, "c1").unwrap();
let m2 = create_test_message("nd", 100, "payload-reuse");
assert!(
storage.push(m2).is_ok(),
"Push of same payload must succeed after ack"
);
}
#[test]
fn test_pset_nack_retry_keeps_payload_blocked() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "payload-y");
storage.push(m1).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
storage.nack("nd", &locked.id, "c1").unwrap();
let m2 = create_test_message("nd", 100, "payload-y");
assert!(
storage.push(m2).is_err(),
"Push of duplicate payload must be rejected after nack-retry"
);
}
#[test]
fn test_pset_nack_dlq_allows_repush() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let mut m1 = create_test_message("nd", 100, "payload-z");
m1.max_retries = 0; storage.push(m1).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
storage.nack("nd", &locked.id, "c1").unwrap();
let m2 = create_test_message("nd", 100, "payload-z");
assert!(
storage.push(m2).is_ok(),
"Push of same payload must succeed after message is dead-lettered"
);
}
#[test]
fn test_pset_startup_reconstruction_blocks_push() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_str().unwrap();
{
let storage = Storage::new(path).unwrap();
storage.configure_queue("nd", no_dup_config());
let m = create_test_message("nd", 100, "persisted-payload");
storage.push(m).unwrap();
}
{
let storage = Storage::new(path).unwrap();
let m2 = create_test_message("nd", 100, "persisted-payload");
assert!(
storage.push(m2).is_err(),
"Startup reconstruction must block duplicate for unlocked message"
);
}
}
#[test]
fn test_pset_startup_reconstruction_includes_locked() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_str().unwrap();
{
let storage = Storage::new(path).unwrap();
storage.configure_queue("nd", no_dup_config());
let m = create_test_message("nd", 100, "locked-payload");
storage.push(m).unwrap();
let _locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
}
{
let storage = Storage::new(path).unwrap();
let m2 = create_test_message("nd", 100, "locked-payload");
assert!(
storage.push(m2).is_err(),
"Startup reconstruction must block duplicate for locked message"
);
}
}
#[test]
fn test_pset_purge_clears_set() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "purge-payload");
storage.push(m1).unwrap();
let m_check = create_test_message("nd", 100, "purge-payload");
assert!(storage.push(m_check).is_err());
storage.purge_queue("nd").unwrap();
let m2 = create_test_message("nd", 100, "purge-payload");
assert!(
storage.push(m2).is_ok(),
"Same payload must be allowed after purge"
);
}
#[test]
fn test_pset_delete_clears_set() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "delete-payload");
storage.push(m1).unwrap();
storage.delete_queue("nd").unwrap();
storage.configure_queue("nd", no_dup_config());
let m2 = create_test_message("nd", 100, "delete-payload");
assert!(
storage.push(m2).is_ok(),
"Same payload must be allowed after queue delete + recreate"
);
}
#[test]
fn test_pset_disable_constraint_drops_set() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let m1 = create_test_message("nd", 100, "shared-payload");
storage.push(m1).unwrap();
storage.configure_queue("nd", allow_dup_config());
let m2 = create_test_message("nd", 100, "shared-payload");
assert!(
storage.push(m2).is_ok(),
"Same payload must be allowed after switching to allow_duplicates=true"
);
}
#[test]
fn test_pset_enable_constraint_builds_set() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", allow_dup_config());
let m1 = create_test_message("nd", 100, "existing-payload");
storage.push(m1).unwrap();
storage.configure_queue("nd", no_dup_config());
let m2 = create_test_message("nd", 100, "existing-payload");
assert!(
storage.push(m2).is_err(),
"Existing payload must be in set after enabling allow_duplicates=false"
);
}
#[test]
fn test_pset_batch_ack_removes_payloads() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let payloads = ["ba1", "ba2", "ba3"];
let mut ids = Vec::new();
for p in &payloads {
let m = create_test_message("nd", 100, p);
storage.push(m).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
ids.push(locked.id);
}
storage.batch_ack("nd", "c1", &ids).unwrap();
for p in &payloads {
let m = create_test_message("nd", 100, p);
assert!(
storage.push(m).is_ok(),
"Payload {} must be re-pushable after batch_ack",
p
);
}
}
#[test]
fn test_pset_batch_nack_dlq_removes_payloads() {
let (storage, _dir) = create_test_storage();
storage.configure_queue("nd", no_dup_config());
let payloads = ["bn1", "bn2"];
let mut ids = Vec::new();
for p in &payloads {
let mut m = create_test_message("nd", 100, p);
m.max_retries = 0;
storage.push(m).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
ids.push(locked.id);
}
let result = storage.batch_nack("nd", "c1", &ids).unwrap();
assert_eq!(result.dead_lettered.len(), 2);
for p in &payloads {
let m = create_test_message("nd", 100, p);
assert!(
storage.push(m).is_ok(),
"Payload {} must be re-pushable after batch_nack DLQ",
p
);
}
}
#[test]
fn test_pset_unlock_expired_retry_keeps_payload_blocked() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_str().unwrap();
let past = Utc::now() - chrono::Duration::seconds(60);
let locked_id = {
let storage = Storage::new(path).unwrap();
storage.configure_queue("nd", no_dup_config());
let m = create_test_message("nd", 100, "exp-payload");
storage.push(m).unwrap();
let locked = storage.pop("nd", "c1", 3600).unwrap().unwrap();
locked.id
};
{
let mut opts = Options::default();
opts.create_if_missing(false);
let db = DB::open(&opts, format!("{}/messages", path)).unwrap();
let prefix = "nd/";
let iter = db.prefix_iterator(prefix.as_bytes());
for item in iter {
let (key, value) = item.unwrap();
if !String::from_utf8_lossy(&key).starts_with(prefix) {
break;
}
let mut m: Message = serde_json::from_slice(&value).unwrap();
if m.id == locked_id {
m.locked_until = Some(past);
db.put(&key, serde_json::to_vec(&m).unwrap()).unwrap();
break;
}
}
}
{
let storage = Storage::new(path).unwrap();
let popped = storage.pop("nd", "c2", 30).unwrap();
assert!(
popped.is_some(),
"Expired-lock message must be available via pop"
);
let m2 = create_test_message("nd", 100, "exp-payload");
assert!(
storage.push(m2).is_err(),
"Payload must stay blocked while the re-locked message is in the queue"
);
}
}
#[test]
fn test_rename_queue_same_name_returns_ok() {
let (storage, _tmp) = create_test_storage();
let msg = create_test_message("rq_same", 1, r#"{"x":1}"#);
storage.push(msg).unwrap();
let result = storage.rename_queue("rq_same", "rq_same");
assert!(
result.is_ok(),
"rename to same name must succeed: {result:?}"
);
}
#[test]
fn test_rename_queue_not_found_returns_error() {
let (storage, _tmp) = create_test_storage();
let result = storage.rename_queue("does_not_exist", "also_new");
assert!(
matches!(result, Err(RenameQueueError::NotFound)),
"expected NotFound, got {result:?}"
);
}
#[test]
fn test_rename_queue_already_exists_returns_error() {
let (storage, _tmp) = create_test_storage();
let msg1 = create_test_message("rq_from", 1, r#"{"a":1}"#);
let msg2 = create_test_message("rq_to", 1, r#"{"b":2}"#);
storage.push(msg1).unwrap();
storage.push(msg2).unwrap();
let result = storage.rename_queue("rq_from", "rq_to");
assert!(
matches!(result, Err(RenameQueueError::AlreadyExists)),
"expected AlreadyExists, got {result:?}"
);
}
#[test]
fn test_rename_queue_empty_name_returns_storage_error() {
let (storage, _tmp) = create_test_storage();
let result = storage.rename_queue("", "some_name");
assert!(
matches!(result, Err(RenameQueueError::Storage(_))),
"expected Storage error for empty from, got {result:?}"
);
let result2 = storage.rename_queue("some_name", "");
assert!(
matches!(result2, Err(RenameQueueError::Storage(_))),
"expected Storage error for empty to, got {result2:?}"
);
let result3 = storage.rename_queue(" ", "name");
assert!(
matches!(result3, Err(RenameQueueError::Storage(_))),
"expected Storage error for whitespace name, got {result3:?}"
);
}
#[test]
fn test_rename_queue_error_display() {
let e = RenameQueueError::NotFound;
assert_eq!(e.to_string(), "queue not found");
assert!(std::error::Error::source(&e).is_none());
let e = RenameQueueError::AlreadyExists;
assert_eq!(e.to_string(), "target queue already exists");
assert!(std::error::Error::source(&e).is_none());
let inner = anyhow::anyhow!("disk full");
let e = RenameQueueError::Storage(inner);
assert!(e.to_string().contains("storage error"));
assert!(std::error::Error::source(&e).is_some());
let anyhow_err = anyhow::anyhow!("oops");
let converted: RenameQueueError = anyhow_err.into();
assert!(matches!(converted, RenameQueueError::Storage(_)));
}
use qrusty::message::PriorityKind;
fn create_text_priority_message(queue: &str, priority: &str, payload: &str) -> Message {
Message {
id: uuid::Uuid::new_v4().to_string(),
queue: queue.to_string(),
priority: Priority::Text(priority.to_string()),
payload: payload.to_string(),
created_at: Utc::now(),
locked_until: None,
locked_by: None,
retry_count: 0,
max_retries: 3,
payload_ref: None,
payload_hash: None,
}
}
#[test]
fn test_text_priority_min_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("text_min", config);
storage
.push(create_text_priority_message("text_min", "charlie", "c"))
.unwrap();
storage
.push(create_text_priority_message("text_min", "alpha", "a"))
.unwrap();
storage
.push(create_text_priority_message("text_min", "bravo", "b"))
.unwrap();
let first = storage.pop("text_min", "c1", 30).unwrap().unwrap();
assert_eq!(first.payload, "a");
assert_eq!(first.priority, Priority::Text("alpha".to_string()));
let second = storage.pop("text_min", "c2", 30).unwrap().unwrap();
assert_eq!(second.payload, "b");
assert_eq!(second.priority, Priority::Text("bravo".to_string()));
let third = storage.pop("text_min", "c3", 30).unwrap().unwrap();
assert_eq!(third.payload, "c");
assert_eq!(third.priority, Priority::Text("charlie".to_string()));
}
#[test]
fn test_text_priority_max_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("text_max", config);
storage
.push(create_text_priority_message("text_max", "alpha", "a"))
.unwrap();
storage
.push(create_text_priority_message("text_max", "charlie", "c"))
.unwrap();
storage
.push(create_text_priority_message("text_max", "bravo", "b"))
.unwrap();
let first = storage.pop("text_max", "c1", 30).unwrap().unwrap();
assert_eq!(first.payload, "c");
assert_eq!(first.priority, Priority::Text("charlie".to_string()));
let second = storage.pop("text_max", "c2", 30).unwrap().unwrap();
assert_eq!(second.payload, "b");
assert_eq!(second.priority, Priority::Text("bravo".to_string()));
let third = storage.pop("text_max", "c3", 30).unwrap().unwrap();
assert_eq!(third.payload, "a");
assert_eq!(third.priority, Priority::Text("alpha".to_string()));
}
#[test]
fn test_text_priority_fifo() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::Fifo,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("text_fifo", config);
let now = Utc::now();
let mut m1 = create_text_priority_message("text_fifo", "zzz", "first");
m1.created_at = now - chrono::Duration::seconds(2);
let mut m2 = create_text_priority_message("text_fifo", "aaa", "second");
m2.created_at = now - chrono::Duration::seconds(1);
storage.push(m1).unwrap();
storage.push(m2).unwrap();
let first = storage.pop("text_fifo", "c1", 30).unwrap().unwrap();
assert_eq!(first.payload, "first");
let second = storage.pop("text_fifo", "c2", 30).unwrap().unwrap();
assert_eq!(second.payload, "second");
}
#[test]
fn test_text_priority_kind_mismatch_rejected() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("text_only", config);
let msg = create_test_message("text_only", 42, "nope");
let err = storage.push(msg).unwrap_err();
assert!(err.to_string().contains("Priority kind mismatch"));
storage.configure_queue("num_only", QueueConfig::default());
let msg = create_text_priority_message("num_only", "hello", "nope");
let err = storage.push(msg).unwrap_err();
assert!(err.to_string().contains("Priority kind mismatch"));
}
#[test]
fn test_text_priority_validation() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("val_q", config);
let msg = create_text_priority_message("val_q", "", "x");
let err = storage.push(msg).unwrap_err();
assert!(err.to_string().contains("empty"));
let msg = create_text_priority_message("val_q", "a/b", "slash_ok");
storage.push(msg).unwrap();
let long = "x".repeat(200);
let msg = create_text_priority_message("val_q", &long, "ok");
storage.push(msg).unwrap();
}
#[test]
fn test_text_priority_max_first_prefix_ordering() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("pfx", config);
for key in &["a", "ab", "abc", "b", "ba"] {
storage
.push(create_text_priority_message("pfx", key, key))
.unwrap();
}
let order: Vec<String> = {
let mut v = Vec::new();
for i in 0..5 {
let m = storage.pop("pfx", &format!("c{}", i), 30).unwrap().unwrap();
v.push(m.payload);
}
v
};
assert_eq!(order, vec!["ba", "b", "abc", "ab", "a"]);
}
#[test]
fn test_delete_queue_removes_dlq_entries() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("dlq_test", QueueConfig::default());
let mut msg = create_test_message("dlq_test", 100, "will_dlq");
msg.max_retries = 0;
let msg_id = msg.id.clone();
storage.push(msg).unwrap();
let consumed = storage.pop("dlq_test", "c1", 30).unwrap().unwrap();
assert_eq!(consumed.id, msg_id);
storage.nack("dlq_test", &msg_id, "c1").unwrap();
let stats = storage.get_queue_stats("dlq_test").unwrap();
assert_eq!(stats.total, 0);
storage.delete_queue("dlq_test").unwrap();
assert!(!storage.queue_exists("dlq_test").unwrap());
storage.create_queue("dlq_test", QueueConfig::default());
assert!(storage.queue_exists("dlq_test").unwrap());
}
#[test]
fn test_delete_queue_with_dlq_allows_recreate_via_api() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue("recreate_test", QueueConfig::default());
let mut msg = create_test_message("recreate_test", 50, "dlq_payload");
msg.max_retries = 0;
let msg_id = msg.id.clone();
storage.push(msg).unwrap();
storage.pop("recreate_test", "c1", 30).unwrap().unwrap();
storage.nack("recreate_test", &msg_id, "c1").unwrap();
storage.delete_queue("recreate_test").unwrap();
assert!(!storage.queue_exists("recreate_test").unwrap());
storage.create_queue("recreate_test", QueueConfig::default());
let msg2 = create_test_message("recreate_test", 100, "fresh_msg");
storage.push(msg2).unwrap();
let stats = storage.get_queue_stats("recreate_test").unwrap();
assert_eq!(stats.total, 1);
}
#[test]
fn test_text_priority_with_slash_min_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("slash_q", config);
let msg1 = create_text_priority_message("slash_q", "/usr/local/bin", "bin_payload");
storage.push(msg1).unwrap();
let msg2 = create_text_priority_message("slash_q", "/usr/local/share", "share_payload");
storage.push(msg2).unwrap();
let msg3 = create_text_priority_message("slash_q", "/etc/config", "etc_payload");
storage.push(msg3).unwrap();
let m = storage.pop("slash_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.payload, "etc_payload");
assert_eq!(m.priority, Priority::Text("/etc/config".to_string()));
storage.ack("slash_q", &m.id, "c1").unwrap();
let m = storage.pop("slash_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.payload, "bin_payload");
assert_eq!(m.priority, Priority::Text("/usr/local/bin".to_string()));
storage.ack("slash_q", &m.id, "c1").unwrap();
let m = storage.pop("slash_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.payload, "share_payload");
assert_eq!(m.priority, Priority::Text("/usr/local/share".to_string()));
storage.ack("slash_q", &m.id, "c1").unwrap();
}
#[test]
fn test_text_priority_with_slash_max_first() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("slash_max_q", config);
let msg1 = create_text_priority_message("slash_max_q", "/a/first", "first_payload");
storage.push(msg1).unwrap();
let msg2 = create_text_priority_message("slash_max_q", "/z/last", "last_payload");
storage.push(msg2).unwrap();
let m = storage.pop("slash_max_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.payload, "last_payload");
assert_eq!(m.priority, Priority::Text("/z/last".to_string()));
storage.ack("slash_max_q", &m.id, "c1").unwrap();
let m = storage.pop("slash_max_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.payload, "first_payload");
assert_eq!(m.priority, Priority::Text("/a/first".to_string()));
}
#[test]
fn test_text_priority_slash_does_not_corrupt_queue_stats() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("stats_slash_q", config);
let msg1 = create_text_priority_message("stats_slash_q", "/a/b/c/d", "payload1");
storage.push(msg1).unwrap();
let msg2 = create_text_priority_message("stats_slash_q", "simple", "payload2");
storage.push(msg2).unwrap();
let stats = storage.get_queue_stats("stats_slash_q").unwrap();
assert_eq!(stats.total, 2);
assert_eq!(stats.available, 2);
let queues = storage.list_queues().unwrap();
assert!(queues.contains(&"stats_slash_q".to_string()));
let all_stats = storage.get_all_queue_stats().unwrap();
let qs = all_stats
.iter()
.find(|s| s.name == "stats_slash_q")
.unwrap();
assert_eq!(qs.total, 2);
}
#[test]
fn test_text_priority_with_null_byte() {
let (storage, _temp_dir) = create_test_storage();
let config = QueueConfig {
ordering: PriorityOrdering::MinFirst,
priority_kind: PriorityKind::Text,
..Default::default()
};
storage.configure_queue("null_q", config);
let msg = create_text_priority_message("null_q", "before\0after", "null_payload");
storage.push(msg).unwrap();
let m = storage.pop("null_q", "c1", 30).unwrap().unwrap();
assert_eq!(m.priority, Priority::Text("before\0after".to_string()));
assert_eq!(m.payload, "null_payload");
}
#[test]
fn test_payload_store_push_pop_ack_roundtrip() {
let (storage, _temp_dir) = create_test_storage();
let payload_text = "hello from the payload store";
let msg = create_test_message("ps_queue", 50, payload_text);
let msg_id = msg.id.clone();
storage.push(msg).unwrap();
let popped = storage.pop("ps_queue", "consumer-1", 30).unwrap().unwrap();
assert_eq!(popped.id, msg_id);
assert_eq!(popped.payload, payload_text);
let acked = storage.ack("ps_queue", &msg_id, "consumer-1").unwrap();
assert!(acked);
}
#[test]
fn test_payload_store_duplicate_detection_with_externalized_payloads() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue(
"ps_nodup",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let msg1 = create_test_message("ps_nodup", 10, "unique-payload");
storage.push(msg1).unwrap();
let msg2 = create_test_message("ps_nodup", 20, "unique-payload");
let result = storage.push(msg2);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Duplicate"));
}
#[test]
fn test_payload_store_ack_allows_repush_of_same_payload() {
let (storage, _temp_dir) = create_test_storage();
storage.create_queue(
"ps_repush",
QueueConfig {
ordering: PriorityOrdering::MaxFirst,
allow_duplicates: false,
..Default::default()
},
);
let msg = create_test_message("ps_repush", 10, "repush-me");
let id = storage.push(msg).unwrap();
storage.pop("ps_repush", "c1", 30).unwrap();
storage.ack("ps_repush", &id, "c1").unwrap();
let msg2 = create_test_message("ps_repush", 10, "repush-me");
assert!(storage.push(msg2).is_ok());
}
#[test]
fn test_payload_store_survives_restart() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
let msg_id;
{
let storage = Storage::new(data_path).unwrap();
let msg = create_test_message("ps_restart", 10, "persistent-payload");
msg_id = msg.id.clone();
storage.push(msg).unwrap();
}
{
let storage = Storage::new(data_path).unwrap();
let popped = storage.pop("ps_restart", "c1", 30).unwrap().unwrap();
assert_eq!(popped.id, msg_id);
assert_eq!(popped.payload, "persistent-payload");
}
}
#[test]
fn test_payload_store_nack_retry_preserves_payload() {
let (storage, _temp_dir) = create_test_storage();
let msg = create_test_message("ps_nack", 10, "nack-me");
storage.push(msg).unwrap();
let popped = storage.pop("ps_nack", "c1", 30).unwrap().unwrap();
assert_eq!(popped.payload, "nack-me");
storage.nack("ps_nack", &popped.id, "c1").unwrap();
let popped2 = storage.pop("ps_nack", "c2", 30).unwrap().unwrap();
assert_eq!(popped2.id, popped.id);
assert_eq!(popped2.payload, "nack-me");
}
#[test]
fn test_payload_store_push_pop_many_after_restart() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
{
let storage = Storage::new(data_path).unwrap();
for i in 0..10 {
let msg = create_test_message("ps_restart_many", i, &format!("old-payload-{}", i));
storage.push(msg).unwrap();
}
}
{
let storage = Storage::new(data_path).unwrap();
for i in 10..50 {
let msg = create_test_message("ps_restart_many", i, &format!("new-payload-{}", i));
storage.push(msg).unwrap();
}
let mut popped = Vec::new();
while let Some(msg) = storage.pop("ps_restart_many", "c1", 30).unwrap() {
assert!(
!msg.payload.is_empty(),
"payload for message {} must not be empty (stale mmap bug)",
msg.id
);
popped.push(msg);
}
assert_eq!(popped.len(), 50, "should pop all 50 messages");
}
}
#[test]
fn test_old_format_messages_survive_full_lifecycle() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
let old_msg_json = r#"{
"id": "old-msg-001",
"queue": "old_queue",
"priority": 50,
"payload": "{\"file\":\"/path/to/book.epub\"}",
"created_at": "2026-01-01T00:00:00Z",
"locked_until": null,
"locked_by": null,
"retry_count": 0,
"max_retries": 3
}"#;
{
let mut opts = Options::default();
opts.create_if_missing(true);
let db = DB::open(&opts, format!("{}/messages", data_path)).unwrap();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
..Default::default()
};
let config_key = "_queue_config/old_queue";
db.put(config_key.as_bytes(), serde_json::to_vec(&config).unwrap())
.unwrap();
let key = "old_queue/18446744073709551565/0000000000000000/old-msg-001";
db.put(key.as_bytes(), old_msg_json.as_bytes()).unwrap();
}
{
let storage = Storage::new(data_path).unwrap();
let msg = storage.pop("old_queue", "c1", 30).unwrap().unwrap();
assert_eq!(msg.id, "old-msg-001");
assert_eq!(msg.payload, r#"{"file":"/path/to/book.epub"}"#);
storage.nack("old_queue", "old-msg-001", "c1").unwrap();
let msg2 = storage.pop("old_queue", "c2", 30).unwrap().unwrap();
assert_eq!(msg2.payload, r#"{"file":"/path/to/book.epub"}"#);
storage.ack("old_queue", "old-msg-001", "c2").unwrap();
}
}
#[test]
fn test_mixed_old_and_new_format_messages() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
{
let mut opts = Options::default();
opts.create_if_missing(true);
let db = DB::open(&opts, format!("{}/messages", data_path)).unwrap();
let config = QueueConfig {
ordering: PriorityOrdering::MaxFirst,
..Default::default()
};
db.put(
"_queue_config/mixed_q".as_bytes(),
serde_json::to_vec(&config).unwrap(),
)
.unwrap();
let old_json = r#"{
"id": "old-001",
"queue": "mixed_q",
"priority": 100,
"payload": "old-inline-payload",
"created_at": "2026-01-01T00:00:00Z",
"locked_until": null,
"locked_by": null,
"retry_count": 0,
"max_retries": 3
}"#;
let key = "mixed_q/18446744073709551515/0000000000000000/old-001";
db.put(key.as_bytes(), old_json.as_bytes()).unwrap();
}
{
let storage = Storage::new(data_path).unwrap();
let new_msg = create_test_message("mixed_q", 200, "new-externalized-payload");
storage.push(new_msg).unwrap();
let msg1 = storage.pop("mixed_q", "c1", 30).unwrap().unwrap();
assert_eq!(msg1.payload, "new-externalized-payload");
let msg2 = storage.pop("mixed_q", "c1", 30).unwrap().unwrap();
assert_eq!(msg2.id, "old-001");
assert_eq!(msg2.payload, "old-inline-payload");
}
}
#[test]
fn test_broken_payload_ref_cleanup_and_fresh_push() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
let big = "x".repeat(5000);
{
let storage = Storage::new(data_path).unwrap();
storage
.push(create_test_message("broken_q", 100, &big))
.unwrap();
storage
.push(create_test_message("broken_q", 50, &big))
.unwrap();
}
let payloads_dir = temp_dir.path().join("payloads");
std::fs::remove_dir_all(&payloads_dir).unwrap();
let storage = Storage::new(data_path).unwrap();
let popped = storage.pop("broken_q", "c1", 30).unwrap();
assert!(
popped.is_none(),
"broken messages should have been purged by integrity scan"
);
storage
.push(create_test_message("broken_q", 10, "fresh-message"))
.unwrap();
let popped = storage.pop("broken_q", "c1", 30).unwrap().unwrap();
assert_eq!(popped.payload, "fresh-message");
}
#[test]
fn test_pop_returns_none_when_all_payloads_broken() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
{
let storage = Storage::new(data_path).unwrap();
let big = "x".repeat(5000); let msg = create_test_message("all_broken_q", 100, &big);
storage.push(msg).unwrap();
}
let payloads_dir = temp_dir.path().join("payloads");
for entry in std::fs::read_dir(&payloads_dir).unwrap() {
let entry = entry.unwrap();
if entry.file_name().to_string_lossy().starts_with("payload_") {
std::fs::remove_file(entry.path()).unwrap();
}
}
let storage = Storage::new(data_path).unwrap();
let popped = storage.pop("all_broken_q", "c1", 30).unwrap();
assert!(
popped.is_none(),
"pop must return None when all payloads are broken"
);
}
#[test]
fn test_integrity_scan_deletes_broken_messages() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
{
let big = "x".repeat(5000);
let storage = Storage::new(data_path).unwrap();
storage
.push(create_test_message("scan_q", 100, &big))
.unwrap();
storage
.push(create_test_message("scan_q", 50, &big))
.unwrap();
}
let payloads_dir = temp_dir.path().join("payloads");
for entry in std::fs::read_dir(&payloads_dir).unwrap() {
let entry = entry.unwrap();
if entry.file_name().to_string_lossy().starts_with("payload_") {
std::fs::remove_file(entry.path()).unwrap();
}
}
let storage = Storage::new(data_path).unwrap();
let stats = storage.get_queue_stats("scan_q").unwrap();
assert_eq!(
stats.available, 0,
"broken messages should have been purged by integrity scan"
);
}
#[test]
fn test_integrity_scan_decrements_locked_for_locked_broken_message() {
let temp_dir = TempDir::new().unwrap();
let data_path = temp_dir.path().to_str().unwrap();
{
let big = "x".repeat(5000);
let storage = Storage::new(data_path).unwrap();
storage
.push(create_test_message("phantom_q", 100, &big))
.unwrap();
let msg = storage.pop("phantom_q", "consumer1", 600).unwrap().unwrap();
assert!(msg.locked_until.is_some());
let stats = storage.get_queue_stats("phantom_q").unwrap();
assert_eq!(stats.locked, 1);
assert_eq!(stats.available, 0);
}
let payloads_dir = temp_dir.path().join("payloads");
for entry in std::fs::read_dir(&payloads_dir).unwrap() {
let entry = entry.unwrap();
if entry.file_name().to_string_lossy().starts_with("payload_") {
std::fs::remove_file(entry.path()).unwrap();
}
}
let storage = Storage::new(data_path).unwrap();
let stats = storage.get_queue_stats("phantom_q").unwrap();
assert_eq!(
stats.locked, 0,
"locked counter must be decremented when a locked message with a broken payload is deleted"
);
assert_eq!(stats.available, 0);
}
#[test]
fn test_payload_store_stress_push_pop_1000() {
let (storage, _temp_dir) = create_test_storage();
let payloads: Vec<String> = (0..1000)
.map(|i| format!("stress-payload-{:04}-{}", i, "x".repeat(100)))
.collect();
for (i, payload) in payloads.iter().enumerate() {
let msg = create_test_message("stress_q", (1000 - i) as u64, payload);
storage.push(msg).unwrap();
}
for expected_payload in &payloads {
let msg = storage.pop("stress_q", "c1", 30).unwrap().unwrap();
assert!(
!msg.payload.is_empty(),
"payload must not be empty in stress test"
);
assert_eq!(&msg.payload, expected_payload);
storage.ack("stress_q", &msg.id, "c1").unwrap();
}
}
#[test]
fn test_compaction_does_not_break_hot_tier() {
let (storage, _temp_dir) = create_test_storage();
let payloads: Vec<String> = (0..10)
.map(|i| format!("payload-{}-{}", i, "x".repeat(5000)))
.collect();
for (i, payload) in payloads.iter().enumerate() {
let msg = create_test_message("compact_q", (100 - i) as u64, payload);
storage.push(msg).unwrap();
}
let popped = storage.pop("compact_q", "c1", 30).unwrap().unwrap();
assert_eq!(popped.payload, payloads[0]);
storage.ack("compact_q", &popped.id, "c1").unwrap();
let updated = storage.compact_payloads().unwrap();
assert!(updated > 0, "compaction should have updated references");
for (i, expected) in payloads.iter().enumerate().skip(1) {
let popped = storage.pop("compact_q", "c1", 30).unwrap();
assert!(popped.is_some(), "message {} should be available", i);
let popped = popped.unwrap();
assert_eq!(
popped.payload, *expected,
"message {} has wrong payload after compaction",
i
);
storage.ack("compact_q", &popped.id, "c1").unwrap();
}
}
#[test]
fn test_small_payloads_stay_inline() {
let (storage, _temp_dir) = create_test_storage();
let small = create_test_message("inline_q", 1, "tiny");
storage.push(small).unwrap();
let popped = storage.pop("inline_q", "c1", 30).unwrap().unwrap();
assert_eq!(popped.payload, "tiny");
assert!(
popped.payload_ref.is_none(),
"small payloads should stay inline, not externalized"
);
}
#[test]
fn test_large_payloads_are_externalized() {
let (storage, _temp_dir) = create_test_storage();
let large_payload = "x".repeat(5000);
let large = create_test_message("extern_q", 1, &large_payload);
storage.push(large).unwrap();
let popped = storage.pop("extern_q", "c1", 30).unwrap().unwrap();
assert_eq!(popped.payload, large_payload);
}
#[test]
fn test_compact_with_zero_live_refs_cleans_segments() {
let (storage, temp_dir) = create_test_storage();
let big = "x".repeat(5000);
for i in 0..5 {
let msg = create_test_message("zero_ref_q", (100 - i) as u64, &big);
storage.push(msg).unwrap();
}
let payloads_dir = temp_dir.path().join("payloads");
let segment_count_before = std::fs::read_dir(&payloads_dir)
.unwrap()
.filter(|e| {
e.as_ref()
.unwrap()
.file_name()
.to_string_lossy()
.starts_with("payload_")
})
.count();
assert!(segment_count_before > 0, "should have segment files");
for _ in 0..5 {
let popped = storage.pop("zero_ref_q", "c1", 30).unwrap().unwrap();
storage.ack("zero_ref_q", &popped.id, "c1").unwrap();
}
let updated = storage.compact_payloads().unwrap();
assert_eq!(updated, 0, "no references to update when all acked");
let store = storage.payload_store().unwrap();
assert_eq!(
store.disk_usage_bytes(),
0,
"disk usage should be 0 after cleaning empty segments"
);
let remaining_bytes: u64 = std::fs::read_dir(&payloads_dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("payload_"))
.map(|e| e.metadata().map(|m| m.len()).unwrap_or(0))
.sum();
assert_eq!(
remaining_bytes, 0,
"no payload data should remain on disk after cleanup"
);
}
#[test]
fn test_force_unlock_queue_unlocks_all_locked_messages() {
let (storage, _temp_dir) = create_test_storage();
for i in 0..4 {
storage
.push(create_test_message("fu_q", 10 + i, &format!("msg-{}", i)))
.unwrap();
}
let mut locked_ids = Vec::new();
for _ in 0..4 {
let msg = storage.pop("fu_q", "consumer1", 300).unwrap().unwrap();
locked_ids.push(msg.id.clone());
}
let stats = storage.get_queue_stats("fu_q").unwrap();
assert_eq!(stats.locked, 4);
assert_eq!(stats.available, 0);
let unlocked = storage.force_unlock_queue_sync("fu_q").unwrap();
assert_eq!(unlocked, 4);
let stats = storage.get_queue_stats("fu_q").unwrap();
assert_eq!(stats.locked, 0);
assert_eq!(stats.available, 4);
for _ in 0..4 {
assert!(storage.pop("fu_q", "consumer2", 300).unwrap().is_some());
}
}
#[test]
fn test_force_unlock_queue_empty_returns_zero() {
let (storage, _temp_dir) = create_test_storage();
let unlocked = storage.force_unlock_queue_sync("nonexistent").unwrap();
assert_eq!(unlocked, 0);
}
#[test]
fn test_force_unlock_queue_mixed_locked_and_available() {
let (storage, _temp_dir) = create_test_storage();
for i in 0..5 {
storage
.push(create_test_message("fu_mix", 10 + i, &format!("msg-{}", i)))
.unwrap();
}
for _ in 0..3 {
storage.pop("fu_mix", "consumer1", 300).unwrap();
}
let stats = storage.get_queue_stats("fu_mix").unwrap();
assert_eq!(stats.locked, 3);
assert_eq!(stats.available, 2);
let unlocked = storage.force_unlock_queue_sync("fu_mix").unwrap();
assert_eq!(unlocked, 3);
let stats = storage.get_queue_stats("fu_mix").unwrap();
assert_eq!(stats.locked, 0);
assert_eq!(stats.available, 5);
}
#[test]
fn test_force_unlock_queue_isolation() {
let (storage, _temp_dir) = create_test_storage();
for q in &["fu_iso_a", "fu_iso_b"] {
for i in 0..2 {
storage
.push(create_test_message(q, 10 + i, &format!("{}-msg-{}", q, i)))
.unwrap();
}
for _ in 0..2 {
storage.pop(q, "consumer1", 300).unwrap();
}
}
let unlocked = storage.force_unlock_queue_sync("fu_iso_a").unwrap();
assert_eq!(unlocked, 2);
let stats_b = storage.get_queue_stats("fu_iso_b").unwrap();
assert_eq!(stats_b.locked, 2);
assert_eq!(stats_b.available, 0);
}