1
1
package com.powersync
2
2
3
3
import app.cash.turbine.turbineScope
4
+ import co.touchlab.kermit.ExperimentalKermitApi
4
5
import co.touchlab.kermit.Logger
5
6
import co.touchlab.kermit.Severity
6
7
import co.touchlab.kermit.TestConfig
8
+ import co.touchlab.kermit.TestLogWriter
7
9
import com.powersync.bucket.BucketChecksum
8
10
import com.powersync.bucket.BucketPriority
9
11
import com.powersync.bucket.Checkpoint
@@ -18,6 +20,7 @@ import com.powersync.sync.SyncStream
18
20
import com.powersync.testutils.MockSyncService
19
21
import com.powersync.testutils.UserRow
20
22
import com.powersync.testutils.cleanup
23
+ import com.powersync.testutils.factory
21
24
import com.powersync.testutils.waitFor
22
25
import com.powersync.utils.JsonUtil
23
26
import dev.mokkery.answering.returns
@@ -35,16 +38,22 @@ import kotlin.test.BeforeTest
35
38
import kotlin.test.Test
36
39
import kotlin.test.assertEquals
37
40
import kotlin.test.assertFalse
41
+ import kotlin.test.assertNotNull
38
42
import kotlin.test.assertTrue
39
43
import kotlin.time.Duration.Companion.seconds
40
44
41
- @OptIn(co.touchlab.kermit. ExperimentalKermitApi ::class )
45
+ @OptIn(ExperimentalKermitApi ::class )
42
46
class SyncIntegrationTest {
47
+ private val logWriter =
48
+ TestLogWriter (
49
+ loggable = Severity .Debug ,
50
+ )
51
+
43
52
private val logger =
44
53
Logger (
45
54
TestConfig (
46
55
minSeverity = Severity .Debug ,
47
- logWriterList = listOf (),
56
+ logWriterList = listOf (logWriter ),
48
57
),
49
58
)
50
59
private lateinit var database: PowerSyncDatabaseImpl
@@ -54,6 +63,7 @@ class SyncIntegrationTest {
54
63
@BeforeTest
55
64
fun setup () {
56
65
cleanup(" testdb" )
66
+ logWriter.reset()
57
67
database = openDb()
58
68
connector =
59
69
mock<PowerSyncBackendConnector > {
@@ -75,12 +85,15 @@ class SyncIntegrationTest {
75
85
76
86
@AfterTest
77
87
fun teardown () {
88
+ runBlocking {
89
+ database.close()
90
+ }
78
91
cleanup(" testdb" )
79
92
}
80
93
81
94
private fun openDb () =
82
95
PowerSyncDatabase (
83
- factory = com.powersync.testutils. factory,
96
+ factory = factory,
84
97
schema = Schema (UserRow .table),
85
98
dbFilename = " testdb" ,
86
99
) as PowerSyncDatabaseImpl
@@ -271,6 +284,26 @@ class SyncIntegrationTest {
271
284
syncLines.close()
272
285
}
273
286
287
+ @Test
288
+ fun setsConnectingState () =
289
+ runTest {
290
+ turbineScope(timeout = 10.0 .seconds) {
291
+ val syncStream = syncStream()
292
+ val turbine = database.currentStatus.asFlow().testIn(this )
293
+
294
+ database.connect(syncStream, 1000L )
295
+ turbine.waitFor { it.connecting }
296
+
297
+ database.disconnect()
298
+
299
+ turbine.waitFor { ! it.connecting && ! it.connected }
300
+ turbine.cancel()
301
+ }
302
+
303
+ database.close()
304
+ syncLines.close()
305
+ }
306
+
274
307
@Test
275
308
fun testMultipleSyncsDoNotCreateMultipleStatusEntries () =
276
309
runTest {
@@ -312,6 +345,123 @@ class SyncIntegrationTest {
312
345
turbine.cancel()
313
346
}
314
347
348
+ database.close()
349
+ syncLines.close()
350
+ }
351
+
352
+ @Test
353
+ fun warnsMultipleConnectionAttempts () =
354
+ runTest {
355
+ val db2 =
356
+ PowerSyncDatabase (
357
+ factory = factory,
358
+ schema = Schema (UserRow .table),
359
+ dbFilename = " testdb" ,
360
+ logger = logger,
361
+ ) as PowerSyncDatabaseImpl
362
+
363
+ turbineScope(timeout = 10.0 .seconds) {
364
+ // Connect the first database
365
+ database.connect(connector, 1000L )
366
+ db2.connect(connector)
367
+
368
+ waitFor {
369
+ assertNotNull(
370
+ logWriter.logs.find {
371
+ it.message == PowerSyncDatabaseImpl .streamConflictMessage
372
+ },
373
+ )
374
+ }
375
+
376
+ db2.disconnect()
377
+ database.disconnect()
378
+ }
379
+
380
+ db2.close()
381
+ database.close()
382
+ syncLines.close()
383
+ }
384
+
385
+ @Test
386
+ fun queuesMultipleConnectionAttempts () =
387
+ runTest {
388
+ val db2 =
389
+ PowerSyncDatabase (
390
+ factory = factory,
391
+ schema = Schema (UserRow .table),
392
+ dbFilename = " testdb" ,
393
+ logger = Logger ,
394
+ ) as PowerSyncDatabaseImpl
395
+
396
+ turbineScope(timeout = 10.0 .seconds) {
397
+ val turbine1 = database.currentStatus.asFlow().testIn(this )
398
+ val turbine2 = db2.currentStatus.asFlow().testIn(this )
399
+
400
+ // Connect the first database
401
+ database.connect(connector, 1000L )
402
+
403
+ turbine1.waitFor { it.connecting }
404
+ db2.connect(connector)
405
+
406
+ // Should not be connecting yet
407
+ assertEquals(false , db2.currentStatus.connecting)
408
+
409
+ database.disconnect()
410
+ turbine1.waitFor { ! it.connecting }
411
+
412
+ // Should start connecting after the other database disconnected
413
+ turbine2.waitFor { it.connecting }
414
+ db2.disconnect()
415
+ turbine2.waitFor { ! it.connecting }
416
+
417
+ turbine1.cancel()
418
+ turbine2.cancel()
419
+ }
420
+
421
+ db2.close()
422
+ database.close()
423
+ syncLines.close()
424
+ }
425
+
426
+ @Test
427
+ fun reconnectsAfterDisconnecting () =
428
+ runTest {
429
+ turbineScope(timeout = 10.0 .seconds) {
430
+ val turbine = database.currentStatus.asFlow().testIn(this )
431
+
432
+ database.connect(connector, 1000L )
433
+ turbine.waitFor { it.connecting }
434
+
435
+ database.disconnect()
436
+ turbine.waitFor { ! it.connecting }
437
+
438
+ database.connect(connector, 1000L )
439
+ turbine.waitFor { it.connecting }
440
+ database.disconnect()
441
+ turbine.waitFor { ! it.connecting }
442
+
443
+ turbine.cancel()
444
+ }
445
+
446
+ database.close()
447
+ syncLines.close()
448
+ }
449
+
450
+ @Test
451
+ fun reconnects () =
452
+ runTest {
453
+ turbineScope(timeout = 10.0 .seconds) {
454
+ val turbine = database.currentStatus.asFlow().testIn(this )
455
+
456
+ database.connect(connector, 1000L , retryDelayMs = 5000 )
457
+ turbine.waitFor { it.connecting }
458
+
459
+ database.connect(connector, 1000L , retryDelayMs = 5000 )
460
+ turbine.waitFor { it.connecting }
461
+
462
+ turbine.cancel()
463
+ }
464
+
315
465
database.close()
316
466
syncLines.close()
317
467
}
0 commit comments