Skip to main content

coven_database/store/store_session/test_support/
database.rs

1use super::*;
2
3impl StoreDatabase {
4    pub async fn store_write_journal_counts_for_test(&self) -> Result<(i64, i64), DbError> {
5        self.call_store(|session| session.store_write_journal_counts_for_test())
6            .await
7    }
8
9    pub async fn seed_prepared_audience_write_for_test(
10        &self,
11        write_id: WriteId,
12        changeset_hash: ObjectHash,
13    ) -> Result<(), DbError> {
14        self.call_store(move |session| {
15            session.seed_prepared_audience_write_for_test(&write_id, changeset_hash)
16        })
17        .await
18    }
19
20    pub async fn persist_prepared_audience_objects_for_test(
21        &self,
22        write_id: WriteId,
23        remotes: Vec<coven_protocol::remote_object::RemoteObjectRecord>,
24        packages: Vec<crate::PreparedAudiencePackage>,
25        blobs: Vec<crate::PreparedAudienceBlob>,
26    ) -> Result<(), DbError> {
27        self.call_store(move |session| {
28            session
29                .persist_prepared_audience_objects_for_test(&write_id, &remotes, &packages, &blobs)
30        })
31        .await
32    }
33
34    pub async fn seed_local_release_rows_for_test(
35        &self,
36        routing_encryption: Option<coven_keys::encryption::EncryptionService>,
37        note_id: &str,
38        photo_id: &str,
39        cloud_path: &str,
40        bytes: &[u8],
41    ) {
42        let note_id = note_id.to_string();
43        let photo_id = photo_id.to_string();
44        let cloud_path = cloud_path.to_string();
45        let size = i64::try_from(bytes.len()).expect("test blob size fits SQLite");
46        let hash = coven_protocol::blob::content_hash(bytes);
47        self.run_host_store_write_for_test(routing_encryption, None, move |transaction| {
48            transaction
49                .execute(
50                    "INSERT INTO notes (id, title, body, shared, _updated_at, created_at)
51                     VALUES (?1, 'Release', NULL, 0,
52                             '0000000001000-0000-A', '2026-01-01')",
53                    [&note_id],
54                )
55                .map_err(DbError::from)?;
56            transaction
57                .execute(
58                    "INSERT INTO note_photos
59                     (id, note_id, kind, size, hash, _updated_at, created_at, cloud_path)
60                     VALUES (?1, ?2, 'image', ?3, ?4,
61                             '0000000001000-0000-A', '2026-01-01', ?5)",
62                    rusqlite::params![photo_id, note_id, size, hash, cloud_path],
63                )
64                .map(|_| ())
65                .map_err(DbError::from)
66        })
67        .await
68        .expect("seed exact release rows");
69    }
70
71    pub async fn register_external_blob_for_test(
72        &self,
73        table: &str,
74        row_id: &str,
75        path: &std::path::Path,
76    ) {
77        let reference = self
78            .row_blob_ref(table, row_id)
79            .await
80            .expect("load exact Local row blob reference");
81        let path = path.to_path_buf();
82        self.call_store(move |session| session.register_external_blob_for_test(&reference, &path))
83            .await
84            .expect("register exact external blob reference");
85    }
86
87    pub async fn enqueue_blob_upload_for_test(
88        &self,
89        root_table: &str,
90        root_id: &str,
91        reference: &coven_protocol::blob::RowBlobRef,
92        source_path: &std::path::Path,
93        created_at: &str,
94    ) -> Result<(), DbError> {
95        let reference = reference.clone();
96        let root_table = root_table.to_string();
97        let root_id = root_id.to_string();
98        let source_path = source_path.to_path_buf();
99        let created_at = created_at.to_string();
100        self.call_store(move |session| {
101            session.enqueue_blob_upload_for_test(
102                &root_table,
103                &root_id,
104                &reference,
105                &source_path,
106                &created_at,
107            )
108        })
109        .await
110    }
111
112    pub async fn insert_fixture_position_for_test(
113        &self,
114        note_id: &str,
115    ) -> Result<(), crate::HostWriteError<DbError>> {
116        let note_id = note_id.to_string();
117        self.run_host_store_write_for_test(None, None, move |transaction| {
118            transaction
119                .execute(
120                    "INSERT INTO notes (id, title, shared, _updated_at, created_at)
121                     VALUES (?1, 'fixture position', 1,
122                             '0000000001000-0000-A', '2026-01-01')",
123                    [note_id],
124                )
125                .map(|_| ())
126                .map_err(DbError::from)
127        })
128        .await
129        .map(|_| ())
130    }
131
132    pub async fn run_host_store_write_for_test<R>(
133        &self,
134        routing_encryption: Option<coven_keys::encryption::EncryptionService>,
135        blob_staging: Option<Box<dyn crate::AudienceBlobMoveStaging>>,
136        operation: impl for<'context, 'connection> FnOnce(
137                crate::SqlContext<'context, 'connection>,
138            ) -> Result<R, DbError>
139            + Send
140            + 'static,
141    ) -> Result<coven_protocol::write::WriteReceipt<R>, crate::HostWriteError<DbError>>
142    where
143        R: Send + 'static,
144    {
145        crate::StoreRowWrites::new(self.clone())
146            .execute(
147                crate::HostWriteOperation::new(crate::WriteBatch::new(), operation),
148                routing_encryption,
149                blob_staging,
150            )
151            .await
152    }
153
154    pub async fn cleanup_intent_count_for_test(
155        &self,
156        namespace: &str,
157        blob_id: &str,
158    ) -> Result<i64, DbError> {
159        let namespace = namespace.to_string();
160        let blob_id = blob_id.to_string();
161        self.call_store(move |session| session.cleanup_intent_count_for_test(&namespace, &blob_id))
162            .await
163    }
164
165    pub async fn coven_table_exists_for_test(
166        &self,
167        table: crate::DatabaseTestTable,
168    ) -> Result<bool, DbError> {
169        self.call_store(move |session| session.coven_table_exists_for_test(table))
170            .await
171    }
172
173    pub async fn install_store_write_failure_trigger_for_test(&self) -> Result<(), DbError> {
174        self.call_store(|session| session.install_store_write_failure_trigger_for_test())
175            .await
176    }
177
178    pub async fn remove_store_write_failure_trigger_for_test(&self) -> Result<(), DbError> {
179        self.call_store(|session| session.remove_store_write_failure_trigger_for_test())
180            .await
181    }
182
183    pub async fn write_blob_facts_for_test(&self, write_id: WriteId) -> Result<String, DbError> {
184        self.call_store(move |session| session.write_blob_facts_for_test(&write_id))
185            .await
186    }
187
188    pub async fn install_test_active_circle(
189        &self,
190        label: String,
191    ) -> Result<coven_protocol::circle::CircleId, DbError> {
192        self.call_store(move |session| Ok(session.install_test_active_circle(&label)))
193            .await
194    }
195
196    pub async fn install_test_active_circles(
197        &self,
198        labels: Vec<String>,
199    ) -> Result<Vec<coven_protocol::circle::CircleId>, DbError> {
200        self.call_store(move |session| {
201            Ok(labels
202                .iter()
203                .map(|label| session.install_test_active_circle(label))
204                .collect())
205        })
206        .await
207    }
208
209    pub async fn install_test_inactive_circle(
210        &self,
211        label: String,
212    ) -> Result<coven_protocol::circle::CircleId, DbError> {
213        self.call_store(move |session| Ok(session.install_test_inactive_circle(&label)))
214            .await
215    }
216
217    pub async fn install_test_active_circle_with_control(
218        &self,
219        label: String,
220    ) -> Result<
221        (
222            coven_protocol::circle::CircleId,
223            coven_protocol::circle::CircleControlCoord,
224        ),
225        DbError,
226    > {
227        self.call_store(move |session| Ok(session.install_test_active_circle_with_control(&label)))
228            .await
229    }
230
231    pub async fn insert_write_status_for_test(
232        &self,
233        write_id: WriteId,
234        status: coven_protocol::write::WriteStatus,
235    ) -> Result<(), DbError> {
236        let base = serde_json::json!({ "dependencies": {} }).to_string();
237        let status = serde_json::to_string(&status)
238            .map_err(|error| DbError::context("serialize write status", error))?;
239        self.call_store(move |session| {
240            session.insert_write_status_for_test(&write_id, &status, &base)
241        })
242        .await
243    }
244
245    pub async fn delete_write_for_test(&self, write_id: WriteId) -> Result<(), DbError> {
246        self.call_store(move |session| session.delete_write_for_test(&write_id))
247            .await
248    }
249
250    pub async fn store_write_partition_for_test(
251        &self,
252        write_id: &WriteId,
253    ) -> Result<Vec<u8>, DbError> {
254        let write_id = write_id.clone();
255        self.call_store(move |session| session.store_write_partition_for_test(&write_id))
256            .await
257    }
258
259    pub async fn write_blob_lease_count_for_test(
260        &self,
261        write_id: &WriteId,
262    ) -> Result<i64, DbError> {
263        let write_id = write_id.clone();
264        self.call_store(move |session| session.write_blob_lease_count_for_test(&write_id))
265            .await
266    }
267
268    pub async fn latest_materialized_commit_coordinate_for_test(
269        &self,
270    ) -> Result<(String, u64), DbError> {
271        self.call_store(|session| session.latest_materialized_commit_coordinate_for_test())
272            .await
273    }
274
275    /// The first column of `sql`'s first row, as text, or `None` when the query
276    /// matched nothing. The only way a test reads a database a join or restore
277    /// installed, which it never holds a handle to.
278    pub async fn test_query_optional_text(&self, sql: String) -> Result<Option<String>, DbError> {
279        self.call_store(move |session| session.test_query_optional_text(&sql))
280            .await
281    }
282
283    pub async fn replay_row_count_for_test(
284        &self,
285        root: coven_protocol::store_commit::StoreRootRef,
286        table: String,
287    ) -> Result<i64, DbError> {
288        self.call_store(move |session| session.replay_row_count_for_test(&root, &table))
289            .await
290    }
291
292    pub async fn compare_circle_bootstrap_replay_with_missing_coverage_for_test(
293        &self,
294        root: coven_protocol::store_commit::StoreRootRef,
295        routing_key: coven_protocol::circle::RowRoutingKey,
296        historical_id: String,
297        late_id: String,
298    ) -> Result<(i64, i64, i64, i64), DbError> {
299        self.call_store(move |session| {
300            session.compare_circle_bootstrap_replay_with_missing_coverage_for_test(
301                &root,
302                &routing_key,
303                &historical_id,
304                &late_id,
305            )
306        })
307        .await
308    }
309
310    pub async fn circle_bootstrap_coverage_count_for_test(
311        &self,
312        circle_id: coven_protocol::circle::CircleId,
313    ) -> Result<i64, DbError> {
314        self.call_store(move |session| session.circle_bootstrap_coverage_count_for_test(circle_id))
315            .await
316    }
317
318    pub async fn reject_missing_circle_bootstrap_payload_claim_for_test(
319        &self,
320        circle_id: coven_protocol::circle::CircleId,
321    ) -> Result<String, DbError> {
322        self.call_store(move |session| {
323            session.reject_missing_circle_bootstrap_payload_claim_for_test(circle_id)
324        })
325        .await
326    }
327
328    pub async fn reject_changed_circle_bootstrap_image_hash_for_test(
329        &self,
330        circle_id: coven_protocol::circle::CircleId,
331        root: coven_protocol::store_commit::StoreRootRef,
332        activation_commit: &StoreBatchCommitRef,
333    ) -> Result<String, DbError> {
334        let activation_commit = activation_commit.clone();
335        self.call_store(move |session| {
336            session.reject_changed_circle_bootstrap_image_hash_for_test(
337                circle_id,
338                &root,
339                &activation_commit,
340            )
341        })
342        .await
343    }
344
345    pub async fn circle_bootstrap_failure_state_for_test(
346        &self,
347        blob_id: String,
348        circle_id: coven_protocol::circle::CircleId,
349        control: String,
350        remote_object_id: String,
351    ) -> Result<(bool, bool, bool, bool), DbError> {
352        self.call_store(move |session| {
353            session.circle_bootstrap_failure_state_for_test(
354                &blob_id,
355                circle_id,
356                &control,
357                remote_object_id,
358            )
359        })
360        .await
361    }
362
363    pub async fn circle_bootstrap_replay_for_control_for_test(
364        &self,
365        circle_id: coven_protocol::circle::CircleId,
366        control: coven_protocol::circle::CircleControlCoord,
367    ) -> Result<Option<coven_protocol::circle_activation::VerifiedCircleImage>, DbError> {
368        self.call_store(move |session| {
369            session.circle_bootstrap_replay_for_control_for_test(circle_id, &control)
370        })
371        .await
372    }
373
374    pub async fn forge_circle_close_exclusion_for_test(
375        &self,
376        circle_id: coven_protocol::circle::CircleId,
377    ) -> Result<(), DbError> {
378        self.call_store(move |session| session.forge_circle_close_exclusion_for_test(circle_id))
379            .await
380    }
381
382    pub async fn transfer_prepared_write_to_for_test(
383        &self,
384        destination: &Self,
385        write_id: &WriteId,
386    ) -> Result<(), DbError> {
387        let source_write_id = write_id.clone();
388        let transfer = self
389            .call_store(move |session| session.export_prepared_write(&source_write_id))
390            .await?;
391
392        let destination_write_id = write_id.clone();
393        destination
394            .call_store(move |session| {
395                session.import_prepared_write(&destination_write_id, transfer)
396            })
397            .await
398    }
399}