1use crate::Migration;
8use crate::{CovenMigrationPolicy, Database, DbError};
9use coven_protocol::synced_schema::{BlobDecl, SyncedTable};
10
11impl Database {
12 pub fn open_synthetic_for_test(
13 path: &std::path::Path,
14 store_dir: coven_foundation::store_dir::StoreDir,
15 tables: Vec<SyncedTable>,
16 grace: chrono::Duration,
17 transfer_limits: coven_protocol::blob::TransferLimits,
18 device_id: String,
19 clock: coven_foundation::clock::ClockRef,
20 migrations: &[Migration],
21 ) -> Result<Self, crate::OpenError> {
22 Database::open_in_store_dir_for_test(
23 path,
24 store_dir,
25 tables,
26 grace,
27 transfer_limits,
28 device_id,
29 clock,
30 CovenMigrationPolicy::ApplyPending,
31 migrations,
32 )
33 }
34
35 pub fn open_synthetic_with_hlc_for_test(
36 path: &std::path::Path,
37 store_dir: coven_foundation::store_dir::StoreDir,
38 tables: Vec<SyncedTable>,
39 grace: chrono::Duration,
40 transfer_limits: coven_protocol::blob::TransferLimits,
41 hlc: std::sync::Arc<coven_protocol::hlc::Hlc>,
42 migrations: &[Migration],
43 ) -> Result<Self, crate::OpenError> {
44 Database::open_with_hlc_in_store_dir_for_test(
45 path,
46 store_dir,
47 tables,
48 grace,
49 transfer_limits,
50 hlc,
51 CovenMigrationPolicy::ApplyPending,
52 migrations,
53 )
54 }
55}
56
57pub fn store_dir_for_test_database(
58 path: &std::path::Path,
59) -> coven_foundation::store_dir::StoreDir {
60 let store_dir = if path == std::path::Path::new(":memory:") {
61 coven_foundation::store_dir::StoreDir::new_ephemeral(
62 std::env::temp_dir().join(format!("coven-test-store-{}", uuid::Uuid::new_v4())),
63 )
64 } else {
65 coven_foundation::store_dir::StoreDir::new(
66 path.parent()
67 .filter(|parent| !parent.as_os_str().is_empty())
68 .unwrap_or_else(|| std::path::Path::new(".")),
69 )
70 };
71 store_dir
72 .ensure_created()
73 .expect("create test database payload directory");
74 store_dir
75}
76
77pub fn test_synced_tables() -> Vec<SyncedTable> {
85 vec![
86 SyncedTable::new(
87 "notes",
88 coven_protocol::synced_schema::RowIdentity::SharedKey,
89 )
90 .gated_by("shared"),
91 SyncedTable::new(
92 "note_tags",
93 coven_protocol::synced_schema::RowIdentity::SharedKey,
94 ),
95 SyncedTable::new(
96 "note_photos",
97 coven_protocol::synced_schema::RowIdentity::SharedKey,
98 ),
99 ]
100}
101
102pub fn test_synced_tables_with_blob(decl: BlobDecl) -> Vec<SyncedTable> {
109 vec![
110 SyncedTable::new(
111 "notes",
112 coven_protocol::synced_schema::RowIdentity::SharedKey,
113 )
114 .gated_by("shared"),
115 SyncedTable::new(
116 "note_tags",
117 coven_protocol::synced_schema::RowIdentity::SharedKey,
118 ),
119 SyncedTable::new(
120 "note_photos",
121 coven_protocol::synced_schema::RowIdentity::SharedKey,
122 )
123 .carries_blob(decl),
124 ]
125}
126
127pub fn test_synced_tables_remote_root_with_blob(decl: BlobDecl) -> Vec<SyncedTable> {
131 vec![
132 SyncedTable::new(
133 "notes",
134 coven_protocol::synced_schema::RowIdentity::SharedKey,
135 )
136 .remote_root(),
137 SyncedTable::new(
138 "note_tags",
139 coven_protocol::synced_schema::RowIdentity::SharedKey,
140 ),
141 SyncedTable::new(
142 "note_photos",
143 coven_protocol::synced_schema::RowIdentity::SharedKey,
144 )
145 .carries_blob(decl),
146 ]
147}
148
149pub fn test_synced_tables_with_user_and_host_blobs(
156 photo_decl: BlobDecl,
157 cover_decl: BlobDecl,
158) -> Vec<SyncedTable> {
159 vec![
160 SyncedTable::new(
161 "notes",
162 coven_protocol::synced_schema::RowIdentity::SharedKey,
163 )
164 .gated_by("shared"),
165 SyncedTable::new(
166 "note_tags",
167 coven_protocol::synced_schema::RowIdentity::SharedKey,
168 ),
169 SyncedTable::new(
170 "note_photos",
171 coven_protocol::synced_schema::RowIdentity::SharedKey,
172 )
173 .carries_blob(photo_decl),
174 SyncedTable::new(
175 "note_covers",
176 coven_protocol::synced_schema::RowIdentity::SharedKey,
177 )
178 .carries_blob(cover_decl),
179 ]
180}
181
182pub fn open_test_db_with_blob(
185 store_dir: coven_foundation::store_dir::StoreDir,
186 decl: BlobDecl,
187) -> Database {
188 open_test_db_schema(
189 store_dir,
190 test_synced_tables_with_blob(decl),
191 test_migrations(),
192 )
193}
194
195pub fn read_test_db(store_dir: coven_foundation::store_dir::StoreDir, namespace: &str) -> Database {
203 open_test_db_with_blob(
204 store_dir,
205 BlobDecl::new(
206 namespace,
207 coven_protocol::blob::Provenance::UserProvided,
208 coven_protocol::blob::CacheFill::CacheLazy,
209 ),
210 )
211}
212
213pub fn read_test_db_with_download_limit(
216 store_dir: coven_foundation::store_dir::StoreDir,
217 namespace: &str,
218 downloads: usize,
219) -> Database {
220 let tables = test_synced_tables_with_blob(BlobDecl::new(
221 namespace,
222 coven_protocol::blob::Provenance::UserProvided,
223 coven_protocol::blob::CacheFill::CacheLazy,
224 ));
225 let limits = coven_protocol::blob::TransferLimits {
226 uploads: std::num::NonZeroUsize::MIN,
227 downloads: std::num::NonZeroUsize::new(downloads).expect("downloads limit is nonzero"),
228 };
229 open_synthetic_database(
230 store_dir,
231 tables,
232 coven_protocol::blob::BLOB_TOMBSTONE_GRACE,
233 limits,
234 std::sync::Arc::new(
235 coven_protocol::hlc::Hlc::try_new(
236 "test-device".to_string(),
237 std::sync::Arc::new(coven_foundation::clock::SystemClock),
238 )
239 .expect("create test register clock"),
240 ),
241 test_migrations(),
242 )
243}
244
245pub fn open_test_db_with_user_and_host_blobs(
249 store_dir: coven_foundation::store_dir::StoreDir,
250 photo_decl: BlobDecl,
251 cover_decl: BlobDecl,
252) -> Database {
253 open_test_db_schema(
254 store_dir,
255 test_synced_tables_with_user_and_host_blobs(photo_decl, cover_decl),
256 test_migrations(),
257 )
258}
259
260pub fn test_migrations() -> Vec<Migration> {
264 vec![Migration::run(1, "test-schema", create_synced_schema)]
265}
266
267pub fn create_synced_schema(conn: &crate::MigrationContext<'_>) -> Result<(), DbError> {
270 conn.execute_batch(
271 "CREATE TABLE notes (
272 id TEXT PRIMARY KEY,
273 title TEXT NOT NULL,
274 body TEXT,
275 shared INTEGER NOT NULL DEFAULT 0,
276 _updated_at TEXT NOT NULL,
277 created_at TEXT NOT NULL
278 ) STRICT;
279 CREATE TABLE note_tags (
280 id TEXT PRIMARY KEY,
281 note_id TEXT NOT NULL,
282 tag TEXT NOT NULL,
283 _updated_at TEXT NOT NULL,
284 created_at TEXT NOT NULL,
285 FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE
286 ) STRICT;
287 CREATE TABLE note_photos (
288 id TEXT PRIMARY KEY,
289 note_id TEXT NOT NULL,
290 kind TEXT NOT NULL,
291 size INTEGER NOT NULL DEFAULT 0,
292 hash TEXT,
293 _updated_at TEXT NOT NULL,
294 created_at TEXT NOT NULL,
295 cloud_path TEXT,
296 blob_id TEXT,
297 FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE
298 ) STRICT;
299 CREATE TABLE note_covers (
300 id TEXT PRIMARY KEY,
301 note_id TEXT NOT NULL,
302 size INTEGER NOT NULL DEFAULT 0,
303 hash TEXT,
304 _updated_at TEXT NOT NULL,
305 created_at TEXT NOT NULL,
306 cloud_path TEXT,
307 FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE
308 ) STRICT;",
309 )
310 .map_err(DbError::from)
311}
312
313pub fn open_test_db(store_dir: coven_foundation::store_dir::StoreDir) -> Database {
316 open_test_db_schema(store_dir, test_synced_tables(), test_migrations())
317}
318
319pub fn test_store_dir() -> coven_foundation::store_dir::StoreDir {
321 let store_dir = coven_foundation::store_dir::StoreDir::new_ephemeral(
322 std::env::temp_dir().join(format!("coven-test-store-{}", uuid::Uuid::new_v4())),
323 );
324 store_dir
325 .ensure_created()
326 .expect("create isolated test Store directory");
327 store_dir
328}
329
330pub fn open_test_db_with_tombstone_grace(
331 store_dir: coven_foundation::store_dir::StoreDir,
332 grace: chrono::Duration,
333) -> Database {
334 open_test_db_schema_with_store_dir_and_tombstone_grace(
335 store_dir,
336 test_synced_tables(),
337 test_migrations(),
338 grace,
339 )
340}
341
342pub fn open_test_db_schema(
345 store_dir: coven_foundation::store_dir::StoreDir,
346 tables: Vec<SyncedTable>,
347 migrations: Vec<Migration>,
348) -> Database {
349 open_test_db_schema_with_store_dir_and_tombstone_grace(
350 store_dir,
351 tables,
352 migrations,
353 coven_protocol::blob::BLOB_TOMBSTONE_GRACE,
354 )
355}
356
357fn open_test_db_schema_with_store_dir_and_tombstone_grace(
358 store_dir: coven_foundation::store_dir::StoreDir,
359 tables: Vec<SyncedTable>,
360 migrations: Vec<Migration>,
361 grace: chrono::Duration,
362) -> Database {
363 let hlc = std::sync::Arc::new(
364 coven_protocol::hlc::Hlc::try_new(
365 "test-device".to_string(),
366 std::sync::Arc::new(coven_foundation::clock::SystemClock),
367 )
368 .expect("create test register clock"),
369 );
370 open_synthetic_database(
371 store_dir,
372 tables,
373 grace,
374 coven_protocol::blob::TransferLimits::one_at_a_time(),
375 hlc,
376 migrations,
377 )
378}
379
380fn open_synthetic_database(
381 store_dir: coven_foundation::store_dir::StoreDir,
382 tables: Vec<SyncedTable>,
383 grace: chrono::Duration,
384 transfer_limits: coven_protocol::blob::TransferLimits,
385 hlc: std::sync::Arc<coven_protocol::hlc::Hlc>,
386 migrations: Vec<Migration>,
387) -> Database {
388 let database = Database::open_with_hlc_in_store_dir_for_test(
389 std::path::Path::new(":memory:"),
390 store_dir.clone(),
391 tables,
392 grace,
393 transfer_limits,
394 hlc,
395 CovenMigrationPolicy::ApplyPending,
396 &migrations,
397 )
398 .expect("open test database");
399 database
400}
401
402pub fn open_test_db_with_hlc(
409 store_dir: coven_foundation::store_dir::StoreDir,
410 hlc: std::sync::Arc<coven_protocol::hlc::Hlc>,
411 seed: impl for<'connection> Fn(&crate::MigrationContext<'connection>) -> Result<(), DbError>
412 + Send
413 + Sync
414 + 'static,
415) -> Database {
416 let migrations = vec![Migration::run(1, "test-schema", move |conn| {
417 create_synced_schema(conn)?;
418 seed(conn)
419 })];
420 open_synthetic_database(
421 store_dir,
422 test_synced_tables(),
423 coven_protocol::blob::BLOB_TOMBSTONE_GRACE,
424 coven_protocol::blob::TransferLimits::one_at_a_time(),
425 hlc,
426 migrations,
427 )
428}
429pub fn store_database(db: &Database) -> crate::StoreDatabase {
432 crate::StoreDatabase::new(db)
433}
434
435pub fn photo_decl() -> BlobDecl {
437 BlobDecl::new(
438 "photos",
439 coven_protocol::blob::Provenance::HostProvided,
440 coven_protocol::blob::CacheFill::CacheEager,
441 )
442}
443
444pub fn remote_root_db(
446 store_dir: coven_foundation::store_dir::StoreDir,
447 decl: BlobDecl,
448) -> Database {
449 open_test_db_schema(
450 store_dir,
451 vec![
452 SyncedTable::new(
453 "notes",
454 coven_protocol::synced_schema::RowIdentity::SharedKey,
455 )
456 .remote_root(),
457 SyncedTable::new(
458 "note_tags",
459 coven_protocol::synced_schema::RowIdentity::SharedKey,
460 ),
461 SyncedTable::new(
462 "note_photos",
463 coven_protocol::synced_schema::RowIdentity::SharedKey,
464 )
465 .carries_blob(decl),
466 ],
467 test_migrations(),
468 )
469}