@@ -7,17 +7,21 @@ import co.touchlab.kermit.Severity
77import co.touchlab.kermit.TestConfig
88import co.touchlab.kermit.TestLogWriter
99import com.powersync.DatabaseDriverFactory
10- import kotlinx.coroutines.CoroutineScope
11- import kotlinx.coroutines.Job
12- import kotlinx.coroutines.currentCoroutineContext
13- import kotlinx.coroutines.job
14- import kotlinx.coroutines.launch
10+ import com.powersync.PowerSyncDatabase
11+ import com.powersync.connectors.PowerSyncBackendConnector
12+ import com.powersync.connectors.PowerSyncCredentials
13+ import com.powersync.db.PowerSyncDatabaseImpl
14+ import com.powersync.db.schema.Schema
15+ import com.powersync.sync.SyncLine
16+ import com.powersync.sync.SyncStream
17+ import dev.mokkery.answering.returns
18+ import dev.mokkery.everySuspend
19+ import dev.mokkery.mock
20+ import kotlinx.coroutines.channels.Channel
1521import kotlinx.coroutines.test.TestScope
1622import kotlinx.coroutines.test.runTest
17- import kotlinx.coroutines.withContext
1823import kotlinx.io.files.Path
19- import kotlinx.io.files.SystemFileSystem
20- import kotlin.coroutines.CoroutineContext
24+ import kotlinx.serialization.json.JsonObject
2125
2226expect val factory: DatabaseDriverFactory
2327
@@ -37,8 +41,26 @@ fun generatePrintLogWriter() =
3741 }
3842 }
3943
44+ internal fun databaseTest (
45+ testBody : suspend ActiveDatabaseTest .() -> Unit
46+ ) = runTest {
47+ val running = ActiveDatabaseTest (this )
48+ // Make sure the database is initialized, we're using internal APIs that expect initialization.
49+ running.database = running.openDatabaseAndInitialize()
50+
51+ try {
52+ running.testBody()
53+ } finally {
54+ running.cleanup()
55+ }
56+ }
57+
4058@OptIn(ExperimentalKermitApi ::class )
41- class DatabaseTestScope : CoroutineContext .Element {
59+ internal class ActiveDatabaseTest (val scope : TestScope ) {
60+ private val cleanupItems: MutableList < suspend () -> Unit > = mutableListOf ()
61+
62+ lateinit var database: PowerSyncDatabaseImpl
63+
4264 val logWriter =
4365 TestLogWriter (
4466 loggable = Severity .Debug ,
@@ -51,31 +73,71 @@ class DatabaseTestScope : CoroutineContext.Element {
5173 ),
5274 )
5375
54- val testDirectory by lazy {
55- getTempDir() ? : SystemFileSystem .resolve(Path (" ." )).name
56- }
76+ val syncLines = Channel <SyncLine >()
5777
78+ val testDirectory by lazy { getTempDir() }
5879 val databaseName by lazy {
5980 val allowedChars = (' A' .. ' Z' ) + (' a' .. ' z' ) + (' 0' .. ' 9' )
60- CharArray (8 ) { allowedChars.random() }.concatToString()
81+ val suffix = CharArray (8 ) { allowedChars.random() }.concatToString()
82+
83+ " db-$suffix "
6184 }
6285
63- private val cleanupItems: MutableList < suspend () -> Unit > = mutableListOf ()
86+ val connector = mock<PowerSyncBackendConnector > {
87+ everySuspend { getCredentialsCached() } returns
88+ PowerSyncCredentials (
89+ token = " test-token" ,
90+ userId = " test-user" ,
91+ endpoint = " https://test.com" ,
92+ )
93+
94+ everySuspend { invalidateCredentials() } returns Unit
95+ }
96+
97+ fun openDatabase (): PowerSyncDatabaseImpl {
98+ logger.d { " Opening database $databaseName in directory $testDirectory " }
99+ val db = PowerSyncDatabase (
100+ factory = factory,
101+ schema = Schema (UserRow .table),
102+ dbFilename = databaseName,
103+ dbDirectory = testDirectory,
104+ logger = logger,
105+ scope = scope,
106+ )
107+ doOnCleanup { db.close() }
108+ return db as PowerSyncDatabaseImpl
109+ }
64110
65- override val key: CoroutineContext .Key <* >
66- get() = Companion
111+ suspend fun openDatabaseAndInitialize (): PowerSyncDatabaseImpl {
112+ return openDatabase().also { it.readLock { } }
113+ }
67114
68- companion object : CoroutineContext .Key <DatabaseTestScope >
69- }
115+ fun syncStream (): SyncStream {
116+ val client = MockSyncService (syncLines)
117+ return SyncStream (
118+ bucketStorage = database.bucketStorage,
119+ connector = connector,
120+ httpEngine = client,
121+ uploadCrud = { },
122+ retryDelayMs = 10 ,
123+ logger = logger,
124+ params = JsonObject (emptyMap()),
125+ )
126+ }
70127
71- val CoroutineContext .database: DatabaseTestScope get() = get(DatabaseTestScope ) ? : error(" Not in PowerSync test: $this " )
128+ fun doOnCleanup (action : suspend () -> Unit ) {
129+ cleanupItems + = action
130+ }
72131
73- fun databaseTest (
74- testBody : suspend TestScope .() -> Unit
75- ) = runTest {
76- val test = DatabaseTestScope ()
132+ suspend fun cleanup () {
133+ for (item in cleanupItems) {
134+ item()
135+ }
77136
78- withContext(test) {
79- testBody()
137+ var path = databaseName
138+ testDirectory?.let {
139+ path = Path (it, path).name
140+ }
141+ cleanup(path)
80142 }
81143}
0 commit comments