Skip to main content

hotfix_store/
test_utils.rs

1//! Test utilities for message store implementations.
2//!
3//! This module provides a test harness for verifying that message store
4//! implementations conform to the expected behavior of the [`MessageStore`] trait.
5
6use crate::MessageStore;
7
8/// A factory trait for creating message store instances during testing.
9///
10/// Implement this trait for each message store implementation you want to test.
11/// The test functions in this module will use this factory to create fresh
12/// store instances for each test.
13#[async_trait::async_trait]
14pub trait TestStoreFactory: Send + Sync {
15    /// Creates a new message store instance.
16    ///
17    /// For persistent stores, this should return a store that can be
18    /// reopened with the same data (by calling `create_store` again).
19    async fn create_store(&self) -> Box<dyn MessageStore>;
20
21    /// Returns `true` if the store persists data across restarts.
22    ///
23    /// This is used to skip persistence-related tests for in-memory stores.
24    fn is_persistent(&self) -> bool {
25        true
26    }
27}
28
29/// Tests that a new store starts with sequence numbers at 1.
30pub async fn test_new_store_initialization(factory: &dyn TestStoreFactory) {
31    let store = factory.create_store().await;
32
33    assert_eq!(store.next_sender_seq_number(), 1);
34    assert_eq!(store.next_target_seq_number(), 1);
35}
36
37/// Tests adding and retrieving messages.
38pub async fn test_add_and_get_messages(factory: &dyn TestStoreFactory) {
39    let mut store = factory.create_store().await;
40
41    let message1 = b"test message 1";
42    let message2 = b"test message 2";
43    let message3 = b"test message 3";
44
45    store
46        .add(1, message1)
47        .await
48        .expect("Failed to add message 1");
49    store
50        .add(2, message2)
51        .await
52        .expect("Failed to add message 2");
53    store
54        .add(3, message3)
55        .await
56        .expect("Failed to add message 3");
57
58    let messages = store.get_slice(1, 3).await.expect("Failed to get messages");
59    assert_eq!(messages.len(), 3);
60    assert_eq!(messages[0], message1);
61    assert_eq!(messages[1], message2);
62    assert_eq!(messages[2], message3);
63}
64
65/// Tests getting a partial range of messages.
66pub async fn test_get_slice_partial_range(factory: &dyn TestStoreFactory) {
67    let mut store = factory.create_store().await;
68
69    let message1 = b"message 1";
70    let message2 = b"message 2";
71    let message3 = b"message 3";
72    let message4 = b"message 4";
73
74    store
75        .add(1, message1)
76        .await
77        .expect("Failed to add message 1");
78    store
79        .add(2, message2)
80        .await
81        .expect("Failed to add message 2");
82    store
83        .add(3, message3)
84        .await
85        .expect("Failed to add message 3");
86    store
87        .add(4, message4)
88        .await
89        .expect("Failed to add message 4");
90
91    let messages = store.get_slice(2, 3).await.expect("Failed to get messages");
92    assert_eq!(messages.len(), 2);
93    assert_eq!(messages[0], message2);
94    assert_eq!(messages[1], message3);
95}
96
97/// Tests getting a slice from an empty store.
98pub async fn test_get_slice_empty_range(factory: &dyn TestStoreFactory) {
99    let store = factory.create_store().await;
100
101    let messages = store.get_slice(1, 3).await.expect("Failed to get messages");
102    assert_eq!(messages.len(), 0);
103}
104
105/// Tests incrementing the sender sequence number.
106pub async fn test_increment_sender_seq_number(factory: &dyn TestStoreFactory) {
107    let mut store = factory.create_store().await;
108
109    assert_eq!(store.next_sender_seq_number(), 1);
110
111    store
112        .increment_sender_seq_number()
113        .await
114        .expect("Failed to increment sender seq number");
115    assert_eq!(store.next_sender_seq_number(), 2);
116
117    store
118        .increment_sender_seq_number()
119        .await
120        .expect("Failed to increment sender seq number");
121    assert_eq!(store.next_sender_seq_number(), 3);
122}
123
124/// Tests incrementing the target sequence number.
125pub async fn test_increment_target_seq_number(factory: &dyn TestStoreFactory) {
126    let mut store = factory.create_store().await;
127
128    assert_eq!(store.next_target_seq_number(), 1);
129
130    store
131        .increment_target_seq_number()
132        .await
133        .expect("Failed to increment target seq number");
134    assert_eq!(store.next_target_seq_number(), 2);
135
136    store
137        .increment_target_seq_number()
138        .await
139        .expect("Failed to increment target seq number");
140    assert_eq!(store.next_target_seq_number(), 3);
141}
142
143/// Tests setting the target sequence number directly.
144pub async fn test_set_target_seq_number(factory: &dyn TestStoreFactory) {
145    let mut store = factory.create_store().await;
146
147    assert_eq!(store.next_target_seq_number(), 1);
148
149    store
150        .set_target_seq_number(10)
151        .await
152        .expect("Failed to set target seq number");
153    assert_eq!(store.next_target_seq_number(), 11);
154
155    store
156        .set_target_seq_number(5)
157        .await
158        .expect("Failed to set target seq number");
159    assert_eq!(store.next_target_seq_number(), 6);
160}
161
162/// Tests resetting the store.
163pub async fn test_reset_store(factory: &dyn TestStoreFactory) {
164    let mut store = factory.create_store().await;
165
166    // Add some messages and increment sequence numbers
167    store
168        .add(1, b"message 1")
169        .await
170        .expect("Failed to add message");
171    store
172        .add(2, b"message 2")
173        .await
174        .expect("Failed to add message");
175    store
176        .increment_sender_seq_number()
177        .await
178        .expect("Failed to increment sender seq number");
179    store
180        .increment_target_seq_number()
181        .await
182        .expect("Failed to increment target seq number");
183
184    assert_eq!(store.next_sender_seq_number(), 2);
185    assert_eq!(store.next_target_seq_number(), 2);
186
187    let messages_before_reset = store.get_slice(1, 2).await.expect("Failed to get messages");
188    assert_eq!(messages_before_reset.len(), 2);
189
190    // Reset the store
191    store.reset().await.expect("Failed to reset store");
192
193    // Verify sequence numbers are reset
194    assert_eq!(store.next_sender_seq_number(), 1);
195    assert_eq!(store.next_target_seq_number(), 1);
196
197    // Verify messages are cleared
198    let messages_after_reset = store.get_slice(1, 2).await.expect("Failed to get messages");
199    assert_eq!(messages_after_reset.len(), 0);
200}
201
202/// Tests that data persists across store instances (for persistent stores only).
203pub async fn test_persistence_across_store_instances(factory: &dyn TestStoreFactory) {
204    if !factory.is_persistent() {
205        return;
206    }
207
208    // Create first store instance and add data
209    {
210        let mut store1 = factory.create_store().await;
211        store1
212            .add(1, b"persistent message")
213            .await
214            .expect("Failed to add message");
215        store1
216            .increment_sender_seq_number()
217            .await
218            .expect("Failed to increment sender seq number");
219        store1
220            .set_target_seq_number(5)
221            .await
222            .expect("Failed to set target seq number");
223    }
224
225    // Create second store instance and verify data persists
226    {
227        let store2 = factory.create_store().await;
228
229        assert_eq!(store2.next_sender_seq_number(), 2);
230        assert_eq!(store2.next_target_seq_number(), 6);
231
232        let messages = store2
233            .get_slice(1, 1)
234            .await
235            .expect("Failed to get messages");
236        assert_eq!(messages.len(), 1);
237        assert_eq!(messages[0], b"persistent message");
238    }
239}
240
241/// Tests getting a slice beyond available messages.
242pub async fn test_get_slice_beyond_available_messages(factory: &dyn TestStoreFactory) {
243    let mut store = factory.create_store().await;
244
245    store
246        .add(1, b"only message")
247        .await
248        .expect("Failed to add message");
249
250    let messages = store
251        .get_slice(1, 10)
252        .await
253        .expect("Failed to get messages");
254    assert_eq!(messages.len(), 1);
255    assert_eq!(messages[0], b"only message");
256}
257
258/// Tests overwriting an existing message.
259pub async fn test_overwrite_existing_message(factory: &dyn TestStoreFactory) {
260    let mut store = factory.create_store().await;
261
262    store
263        .add(1, b"original message")
264        .await
265        .expect("Failed to add original message");
266    store
267        .add(1, b"updated message")
268        .await
269        .expect("Failed to add updated message");
270
271    let messages = store.get_slice(1, 1).await.expect("Failed to get messages");
272    assert_eq!(messages.len(), 1);
273    assert_eq!(messages[0], b"updated message");
274}
275
276/// Tests that creation time is set on new stores.
277pub async fn test_creation_time_is_set(factory: &dyn TestStoreFactory) {
278    use chrono::Utc;
279
280    let before = Utc::now();
281    let store = factory.create_store().await;
282    let after = Utc::now();
283
284    assert!(before <= store.creation_time());
285    assert!(store.creation_time() <= after);
286}
287
288/// Tests that creation time is preserved across store restarts (for persistent stores only).
289pub async fn test_creation_time_is_preserved(factory: &dyn TestStoreFactory) {
290    if !factory.is_persistent() {
291        return;
292    }
293
294    let store = factory.create_store().await;
295    let creation_time1 = store.creation_time();
296    drop(store);
297
298    tokio::time::sleep(std::time::Duration::from_millis(10)).await;
299
300    let store = factory.create_store().await;
301    let creation_time2 = store.creation_time();
302
303    assert_eq!(creation_time1, creation_time2);
304}
305
306/// Tests that creation time is updated on reset.
307pub async fn test_creation_time_gets_reset_correctly(factory: &dyn TestStoreFactory) {
308    use chrono::Utc;
309
310    let mut store = factory.create_store().await;
311
312    tokio::time::sleep(std::time::Duration::from_millis(2)).await;
313    let after_sleep = Utc::now();
314    tokio::time::sleep(std::time::Duration::from_millis(2)).await;
315
316    store.reset().await.expect("failed to reset store");
317    let reset_creation_time = store.creation_time();
318    assert!(reset_creation_time > after_sleep);
319
320    if !factory.is_persistent() {
321        return;
322    }
323
324    drop(store);
325    let store = factory.create_store().await;
326    assert_eq!(reset_creation_time, store.creation_time());
327}
328
329/// Generates conformance tests for a message store implementation.
330///
331/// This macro creates a module containing all the standard conformance tests
332/// for a [`MessageStore`] implementation. Each test gets its own test function,
333/// allowing for parallel execution and clear test reporting.
334///
335/// # Arguments
336///
337/// * `$mod_name` - The name of the module to create (e.g., `in_memory`, `file`, `mongodb`)
338/// * `$factory` - An expression that creates a [`TestStoreFactory`] instance.
339///   This can be a sync expression (e.g., `MyFactory::new()`) or an async
340///   expression (e.g., `MyFactory::new().await`).
341///
342/// # Example
343///
344/// ```ignore
345/// use hotfix_store::conformance_tests;
346///
347/// struct MyStoreFactory;
348///
349/// #[async_trait::async_trait]
350/// impl TestStoreFactory for MyStoreFactory {
351///     async fn create_store(&self) -> Box<dyn MessageStore> {
352///         Box::new(MyStore::new())
353///     }
354/// }
355///
356/// conformance_tests!(my_store, MyStoreFactory);
357/// ```
358#[macro_export]
359macro_rules! conformance_tests {
360    ($mod_name:ident, $factory:expr) => {
361        mod $mod_name {
362            use super::*;
363
364            #[tokio::test]
365            async fn test_new_store_initialization() {
366                let factory = $factory;
367                $crate::test_utils::test_new_store_initialization(&factory).await;
368            }
369
370            #[tokio::test]
371            async fn test_add_and_get_messages() {
372                let factory = $factory;
373                $crate::test_utils::test_add_and_get_messages(&factory).await;
374            }
375
376            #[tokio::test]
377            async fn test_get_slice_partial_range() {
378                let factory = $factory;
379                $crate::test_utils::test_get_slice_partial_range(&factory).await;
380            }
381
382            #[tokio::test]
383            async fn test_get_slice_empty_range() {
384                let factory = $factory;
385                $crate::test_utils::test_get_slice_empty_range(&factory).await;
386            }
387
388            #[tokio::test]
389            async fn test_increment_sender_seq_number() {
390                let factory = $factory;
391                $crate::test_utils::test_increment_sender_seq_number(&factory).await;
392            }
393
394            #[tokio::test]
395            async fn test_increment_target_seq_number() {
396                let factory = $factory;
397                $crate::test_utils::test_increment_target_seq_number(&factory).await;
398            }
399
400            #[tokio::test]
401            async fn test_set_target_seq_number() {
402                let factory = $factory;
403                $crate::test_utils::test_set_target_seq_number(&factory).await;
404            }
405
406            #[tokio::test]
407            async fn test_reset_store() {
408                let factory = $factory;
409                $crate::test_utils::test_reset_store(&factory).await;
410            }
411
412            #[tokio::test]
413            async fn test_persistence_across_store_instances() {
414                let factory = $factory;
415                $crate::test_utils::test_persistence_across_store_instances(&factory).await;
416            }
417
418            #[tokio::test]
419            async fn test_get_slice_beyond_available_messages() {
420                let factory = $factory;
421                $crate::test_utils::test_get_slice_beyond_available_messages(&factory).await;
422            }
423
424            #[tokio::test]
425            async fn test_overwrite_existing_message() {
426                let factory = $factory;
427                $crate::test_utils::test_overwrite_existing_message(&factory).await;
428            }
429
430            #[tokio::test]
431            async fn test_creation_time_is_set() {
432                let factory = $factory;
433                $crate::test_utils::test_creation_time_is_set(&factory).await;
434            }
435
436            #[tokio::test]
437            async fn test_creation_time_is_preserved() {
438                let factory = $factory;
439                $crate::test_utils::test_creation_time_is_preserved(&factory).await;
440            }
441
442            #[tokio::test]
443            async fn test_creation_time_gets_reset_correctly() {
444                let factory = $factory;
445                $crate::test_utils::test_creation_time_gets_reset_correctly(&factory).await;
446            }
447        }
448    };
449}