backend/storage/
testing.rs

1//! Storage adapter testing utilities.
2//!
3//! Rewritten from:
4//! `automerge-repo/packages/automerge-repo/src/helpers/tests/storage-adapter-tests.ts`.
5//!
6//! Provides a test suite for any implementation of the `Storage` trait.
7//! Based on the TypeScript `runStorageAdapterTests` from automerge-repo.
8
9#![allow(dead_code)]
10
11use rand::Rng;
12use samod::storage::{Storage, StorageKey};
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::LazyLock;
16
17pub fn payload_a() -> Vec<u8> {
18    vec![0, 1, 127, 99, 154, 235]
19}
20
21pub fn payload_b() -> Vec<u8> {
22    vec![1, 76, 160, 53, 57, 10, 230]
23}
24
25pub fn payload_c() -> Vec<u8> {
26    vec![2, 111, 74, 131, 236, 96, 142, 193]
27}
28
29static LARGE_PAYLOAD: LazyLock<Vec<u8>> = LazyLock::new(|| {
30    let mut vec = vec![0u8; 100000];
31    rand::thread_rng().fill(&mut vec[..]);
32    vec
33});
34
35pub fn large_payload() -> Vec<u8> {
36    LARGE_PAYLOAD.clone()
37}
38
39/// Trait for storage test fixtures.
40pub trait StorageTestFixture: Sized + Send {
41    /// The storage type being tested.
42    type Storage: Storage + Send + Sync + 'static;
43
44    /// Setup the test fixture.
45    fn setup() -> impl std::future::Future<Output = Self> + Send;
46
47    /// Get reference to the storage adapter.
48    fn storage(&self) -> &Self::Storage;
49
50    /// Optional cleanup.
51    fn teardown(self) -> impl std::future::Future<Output = ()> + Send {
52        async {}
53    }
54}
55
56/// Helper to run a single test with setup and teardown.
57async fn run_test<F, TestFn>(test_fn: TestFn)
58where
59    F: StorageTestFixture,
60    TestFn: for<'a> FnOnce(&'a F::Storage) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> + Send,
61{
62    let fixture = F::setup().await;
63    test_fn(fixture.storage()).await;
64    fixture.teardown().await;
65}
66
67/// Run all storage adapter acceptance tests.
68pub async fn run_storage_adapter_tests<F: StorageTestFixture>() {
69    run_test::<F, _>(|a| Box::pin(test_load_should_return_none_if_no_data(a))).await;
70    run_test::<F, _>(|a| Box::pin(test_save_and_load_should_return_data_that_was_saved(a))).await;
71    run_test::<F, _>(|a| Box::pin(test_save_and_load_should_work_with_composite_keys(a))).await;
72    run_test::<F, _>(|a| Box::pin(test_save_and_load_should_work_with_large_payload(a))).await;
73    run_test::<F, _>(|a| Box::pin(test_load_range_should_return_empty_if_no_data(a))).await;
74    run_test::<F, _>(|a| Box::pin(test_save_and_load_range_should_return_all_matching_data(a)))
75        .await;
76    run_test::<F, _>(|a| Box::pin(test_save_and_load_range_should_only_load_matching_values(a)))
77        .await;
78    run_test::<F, _>(|a| Box::pin(test_save_and_remove_should_be_empty_after_removing(a))).await;
79    run_test::<F, _>(|a| Box::pin(test_save_and_save_should_overwrite(a))).await;
80}
81
82// describe("load")
83pub async fn test_load_should_return_none_if_no_data<S: Storage>(adapter: &S) {
84    let actual = adapter
85        .load(StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap())
86        .await;
87
88    assert_eq!(actual, None);
89}
90
91// describe("save and load")
92pub async fn test_save_and_load_should_return_data_that_was_saved<S: Storage>(adapter: &S) {
93    let key = StorageKey::from_parts(["storage-adapter-id"]).unwrap();
94    adapter.put(key.clone(), payload_a()).await;
95
96    let actual = adapter.load(key).await;
97
98    assert_eq!(actual, Some(payload_a()));
99}
100
101pub async fn test_save_and_load_should_work_with_composite_keys<S: Storage>(adapter: &S) {
102    let key = StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap();
103    adapter.put(key.clone(), payload_a()).await;
104
105    let actual = adapter.load(key).await;
106
107    assert_eq!(actual, Some(payload_a()));
108}
109
110pub async fn test_save_and_load_should_work_with_large_payload<S: Storage>(adapter: &S) {
111    let key = StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap();
112    adapter.put(key.clone(), large_payload()).await;
113
114    let actual = adapter.load(key).await;
115
116    assert_eq!(actual, Some(large_payload()));
117}
118
119// describe("loadRange")
120pub async fn test_load_range_should_return_empty_if_no_data<S: Storage>(adapter: &S) {
121    let result = adapter.load_range(StorageKey::from_parts(["AAAAA"]).unwrap()).await;
122
123    assert_eq!(result.len(), 0);
124}
125
126// describe("save and loadRange")
127pub async fn test_save_and_load_range_should_return_all_matching_data<S: Storage>(adapter: &S) {
128    let key_a = StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap();
129    let key_b = StorageKey::from_parts(["AAAAA", "snapshot", "yyyyy"]).unwrap();
130    let key_c = StorageKey::from_parts(["AAAAA", "sync-state", "zzzzz"]).unwrap();
131
132    adapter.put(key_a.clone(), payload_a()).await;
133    adapter.put(key_b.clone(), payload_b()).await;
134    adapter.put(key_c.clone(), payload_c()).await;
135
136    let result = adapter.load_range(StorageKey::from_parts(["AAAAA"]).unwrap()).await;
137
138    assert_eq!(result.len(), 3);
139    assert_eq!(result.get(&key_a), Some(&payload_a()));
140    assert_eq!(result.get(&key_b), Some(&payload_b()));
141    assert_eq!(result.get(&key_c), Some(&payload_c()));
142
143    let sync_result = adapter
144        .load_range(StorageKey::from_parts(["AAAAA", "sync-state"]).unwrap())
145        .await;
146
147    assert_eq!(sync_result.len(), 2);
148    assert_eq!(sync_result.get(&key_a), Some(&payload_a()));
149    assert_eq!(sync_result.get(&key_c), Some(&payload_c()));
150}
151
152pub async fn test_save_and_load_range_should_only_load_matching_values<S: Storage>(adapter: &S) {
153    let key_a = StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap();
154    let key_c = StorageKey::from_parts(["BBBBB", "sync-state", "zzzzz"]).unwrap();
155
156    adapter.put(key_a.clone(), payload_a()).await;
157    adapter.put(key_c.clone(), payload_c()).await;
158
159    let actual = adapter.load_range(StorageKey::from_parts(["AAAAA"]).unwrap()).await;
160
161    assert_eq!(actual.len(), 1);
162    assert_eq!(actual.get(&key_a), Some(&payload_a()));
163}
164
165// describe("save and remove")
166pub async fn test_save_and_remove_should_be_empty_after_removing<S: Storage>(adapter: &S) {
167    let key = StorageKey::from_parts(["AAAAA", "snapshot", "xxxxx"]).unwrap();
168    adapter.put(key.clone(), payload_a()).await;
169    adapter.delete(key.clone()).await;
170
171    let range_result = adapter.load_range(StorageKey::from_parts(["AAAAA"]).unwrap()).await;
172    assert_eq!(range_result.len(), 0);
173
174    let load_result = adapter.load(key).await;
175    assert_eq!(load_result, None);
176}
177
178// describe("save and save")
179pub async fn test_save_and_save_should_overwrite<S: Storage>(adapter: &S) {
180    let key = StorageKey::from_parts(["AAAAA", "sync-state", "xxxxx"]).unwrap();
181    adapter.put(key.clone(), payload_a()).await;
182    adapter.put(key.clone(), payload_b()).await;
183
184    let result = adapter
185        .load_range(StorageKey::from_parts(["AAAAA", "sync-state"]).unwrap())
186        .await;
187
188    assert_eq!(result.len(), 1);
189    assert_eq!(result.get(&key), Some(&payload_b()));
190}