Skip to content

Commit e144fcc

Browse files
committed
Add Android integration tests
1 parent 5407758 commit e144fcc

File tree

9 files changed

+200
-44
lines changed

9 files changed

+200
-44
lines changed

core-tests-android/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88

99
dependencies {
1010
implementation(projects.core)
11+
implementation(projects.sqlite3multipleciphers)
1112
implementation(libs.androidx.core)
1213
implementation(libs.androidx.appcompat)
1314
implementation(libs.androidx.material)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.powersync
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import androidx.test.platform.app.InstrumentationRegistry
5+
import androidx.sqlite.SQLiteException
6+
import androidx.sqlite.execSQL
7+
import app.cash.turbine.turbineScope
8+
import com.powersync.db.schema.Schema
9+
import com.powersync.encryption.AndroidEncryptedDatabaseFactory
10+
import com.powersync.encryption.Key
11+
import com.powersync.testutils.UserRow
12+
import kotlinx.coroutines.*
13+
import kotlinx.coroutines.runBlocking
14+
import kotlinx.coroutines.test.runTest
15+
import org.junit.After
16+
import org.junit.Assert.*
17+
import org.junit.Before
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
21+
@RunWith(AndroidJUnit4::class)
22+
class EncryptedDatabaseTest {
23+
24+
@Test
25+
fun testEncryptedDatabase() =
26+
runTest {
27+
val context = InstrumentationRegistry.getInstrumentation().targetContext
28+
29+
val database = PowerSyncDatabase(
30+
factory = AndroidEncryptedDatabaseFactory(
31+
context,
32+
Key.Passphrase("mykey")
33+
),
34+
schema = Schema(UserRow.table),
35+
dbFilename = "encrypted_test",
36+
)
37+
38+
assertEquals("chacha20", database.get("PRAGMA cipher") { it.getString(0)!! })
39+
40+
database.execute(
41+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
42+
listOf("Test", "[email protected]"),
43+
)
44+
database.close()
45+
46+
val unencryptedFactory = DatabaseDriverFactory(context)
47+
val unencrypted = unencryptedFactory.openConnection("encrypted_test", null, false)
48+
49+
try {
50+
unencrypted.execSQL("SELECT * FROM sqlite_schema")
51+
throw IllegalStateException("Was able to read schema from encrypted database without supplying a key")
52+
} catch (_: SQLiteException) {
53+
// Expected
54+
}
55+
unencrypted.close()
56+
}
57+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
package com.powersync.encryption
22

3+
import android.content.Context
4+
5+
public class AndroidEncryptedDatabaseFactory(
6+
private val context: Context,
7+
key: Key,
8+
) : BundledSQLiteDriver(key) {
9+
override fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path
10+
}
11+
12+
private val didLoadLibrary by lazy {
13+
System.loadLibrary("sqlite3mc_bundled")
14+
}
15+
316
internal actual fun ensureJniLibraryLoaded() {
17+
didLoadLibrary
418
}

sqlite3multipleciphers/src/commonMain/kotlin/com/powersync/encryption/BaseEncryptedDatabaseFactory.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import androidx.sqlite.SQLiteConnection
44
import androidx.sqlite.execSQL
55

66
public sealed interface Key {
7-
public class Passphrase(public val passphrase: String) : Key
7+
public class Passphrase(
8+
public val passphrase: String,
9+
) : Key
810
// TODO: Add raw key api
911
}
1012

@@ -20,4 +22,4 @@ internal fun SQLiteConnection.encryptOrClose(key: Key) {
2022
close()
2123
throw e
2224
}
23-
}
25+
}

sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteConnection.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
* limitations under the License.
1515
*/
1616
@file:JvmName("BundledSQLiteConnectionKt")
17+
1718
package com.powersync.encryption
1819

1920
import androidx.sqlite.SQLiteConnection
2021
import androidx.sqlite.SQLiteStatement
2122
import androidx.sqlite.throwSQLiteException
2223

23-
internal class BundledSQLiteConnection(private val connectionPointer: Long) : SQLiteConnection {
24+
internal class BundledSQLiteConnection(
25+
private val connectionPointer: Long,
26+
) : SQLiteConnection {
2427
@Volatile private var isClosed = false
2528

2629
override fun inTransaction(): Boolean {
@@ -38,7 +41,10 @@ internal class BundledSQLiteConnection(private val connectionPointer: Long) : SQ
3841
return BundledSQLiteStatement(connectionPointer, statementPointer)
3942
}
4043

41-
internal fun loadExtension(fileName: String, entryPoint: String?) {
44+
internal fun loadExtension(
45+
fileName: String,
46+
entryPoint: String?,
47+
) {
4248
if (isClosed) {
4349
throwSQLiteException(SQLITE_MISUSE, "connection is closed")
4450
}
@@ -60,8 +66,15 @@ internal class BundledSQLiteConnection(private val connectionPointer: Long) : SQ
6066

6167
private external fun nativeInTransaction(pointer: Long): Boolean
6268

63-
private external fun nativePrepare(pointer: Long, sql: String): Long
69+
private external fun nativePrepare(
70+
pointer: Long,
71+
sql: String,
72+
): Long
6473

65-
private external fun nativeLoadExtension(pointer: Long, fileName: String, entryPoint: String?)
74+
private external fun nativeLoadExtension(
75+
pointer: Long,
76+
fileName: String,
77+
entryPoint: String?,
78+
)
6679

6780
private external fun nativeClose(pointer: Long)

sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@
1414
* limitations under the License.
1515
*/
1616
@file:JvmName("BundledSQLiteDriverKt")
17+
1718
package com.powersync.encryption
1819

1920
import androidx.sqlite.SQLiteConnection
2021
import com.powersync.PersistentConnectionFactory
2122
import com.powersync.resolvePowerSyncLoadableExtensionPath
2223

23-
public abstract class BundledSQLiteDriver internal constructor(private val key: Key): PersistentConnectionFactory {
24-
private fun open(fileName: String, flags: Int): SQLiteConnection {
24+
public abstract class BundledSQLiteDriver internal constructor(
25+
private val key: Key,
26+
) : PersistentConnectionFactory {
27+
private fun open(
28+
fileName: String,
29+
flags: Int,
30+
): SQLiteConnection {
2531
ensureJniLibraryLoaded()
2632

2733
val address = nativeOpen(fileName, flags)
@@ -36,15 +42,17 @@ public abstract class BundledSQLiteDriver internal constructor(private val key:
3642
return connection
3743
}
3844

39-
override fun openConnection(path: String, openFlags: Int): SQLiteConnection {
40-
return open(path, openFlags).also { it.encryptOrClose(key) }
41-
}
45+
override fun openConnection(
46+
path: String,
47+
openFlags: Int,
48+
): SQLiteConnection = open(path, openFlags).also { it.encryptOrClose(key) }
4249

43-
override fun openInMemoryConnection(): SQLiteConnection {
44-
return open(":memory:", 2)
45-
}
50+
override fun openInMemoryConnection(): SQLiteConnection = open(":memory:", 2)
4651
}
4752

4853
internal expect fun ensureJniLibraryLoaded()
4954

50-
private external fun nativeOpen(name: String, openFlags: Int): Long
55+
private external fun nativeOpen(
56+
name: String,
57+
openFlags: Int,
58+
): Long

sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteStatement.kt

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
@file:JvmName("BundledSQLiteStatementKt")
17+
1718
package com.powersync.encryption
1819

1920
import androidx.sqlite.SQLiteStatement
@@ -23,25 +24,36 @@ internal class BundledSQLiteStatement(
2324
private val connectionPointer: Long,
2425
private val statementPointer: Long,
2526
) : SQLiteStatement {
26-
2727
@Volatile private var isClosed = false
2828

29-
override fun bindBlob(index: Int, value: ByteArray) {
29+
override fun bindBlob(
30+
index: Int,
31+
value: ByteArray,
32+
) {
3033
throwIfClosed()
3134
nativeBindBlob(statementPointer, index, value)
3235
}
3336

34-
override fun bindDouble(index: Int, value: Double) {
37+
override fun bindDouble(
38+
index: Int,
39+
value: Double,
40+
) {
3541
throwIfClosed()
3642
nativeBindDouble(statementPointer, index, value)
3743
}
3844

39-
override fun bindLong(index: Int, value: Long) {
45+
override fun bindLong(
46+
index: Int,
47+
value: Long,
48+
) {
4049
throwIfClosed()
4150
nativeBindLong(statementPointer, index, value)
4251
}
4352

44-
override fun bindText(index: Int, value: String) {
53+
override fun bindText(
54+
index: Int,
55+
value: String,
56+
) {
4557
throwIfClosed()
4658
nativeBindText(statementPointer, index, value)
4759
}
@@ -115,7 +127,7 @@ internal class BundledSQLiteStatement(
115127

116128
private fun throwIfClosed() {
117129
if (isClosed) {
118-
throwSQLiteException(21 /* SQLITE_MISUSE */, "statement is closed")
130+
throwSQLiteException(21, "statement is closed")
119131
}
120132
}
121133

@@ -124,24 +136,71 @@ internal class BundledSQLiteStatement(
124136
}
125137
}
126138

127-
128-
private external fun nativeBindBlob(pointer: Long, index: Int, value: ByteArray)
129-
private external fun nativeBindDouble(pointer: Long, index: Int, value: Double)
130-
private external fun nativeBindLong(pointer: Long, index: Int, value: Long)
131-
private external fun nativeBindText(pointer: Long, index: Int, value: String)
132-
private external fun nativeBindNull(pointer: Long, index: Int)
139+
private external fun nativeBindBlob(
140+
pointer: Long,
141+
index: Int,
142+
value: ByteArray,
143+
)
144+
145+
private external fun nativeBindDouble(
146+
pointer: Long,
147+
index: Int,
148+
value: Double,
149+
)
150+
151+
private external fun nativeBindLong(
152+
pointer: Long,
153+
index: Int,
154+
value: Long,
155+
)
156+
157+
private external fun nativeBindText(
158+
pointer: Long,
159+
index: Int,
160+
value: String,
161+
)
162+
163+
private external fun nativeBindNull(
164+
pointer: Long,
165+
index: Int,
166+
)
133167

134168
private external fun nativeStep(pointer: Long): Boolean
135169

136-
private external fun nativeGetBlob(pointer: Long, index: Int): ByteArray
137-
private external fun nativeGetDouble(pointer: Long, index: Int): Double
138-
private external fun nativeGetLong(pointer: Long, index: Int): Long
139-
private external fun nativeGetText(pointer: Long, index: Int): String
170+
private external fun nativeGetBlob(
171+
pointer: Long,
172+
index: Int,
173+
): ByteArray
174+
175+
private external fun nativeGetDouble(
176+
pointer: Long,
177+
index: Int,
178+
): Double
179+
180+
private external fun nativeGetLong(
181+
pointer: Long,
182+
index: Int,
183+
): Long
184+
185+
private external fun nativeGetText(
186+
pointer: Long,
187+
index: Int,
188+
): String
189+
140190
private external fun nativeGetColumnCount(pointer: Long): Int
141-
private external fun nativeGetColumnName(pointer: Long, index: Int): String
142-
private external fun nativeGetColumnType(pointer: Long, index: Int): Int
191+
192+
private external fun nativeGetColumnName(
193+
pointer: Long,
194+
index: Int,
195+
): String
196+
197+
private external fun nativeGetColumnType(
198+
pointer: Long,
199+
index: Int,
200+
): Int
143201

144202
private external fun nativeReset(pointer: Long)
145203

146204
private external fun nativeClearBindings(pointer: Long)
205+
147206
private external fun nativeClose(pointer: Long)

sqlite3multipleciphers/src/jvmMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.jvm.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package com.powersync.encryption
22

33
import com.powersync.extractLib
44

5-
public class JavaEncryptedDatabaseFactory(key: Key): BundledSQLiteDriver(key) {
6-
override fun resolveDefaultDatabasePath(dbFilename: String): String {
7-
return dbFilename
8-
}
5+
public class JavaEncryptedDatabaseFactory(
6+
key: Key,
7+
) : BundledSQLiteDriver(key) {
8+
override fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename
99
}
1010

1111
private val didLoadLibrary by lazy {
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import com.powersync.db.NativeConnectionFactory
77
/**
88
* A [NativeConnectionFactory] that links sqlite3multipleciphers and opens database with a [Key].
99
*/
10-
public class NativeEncryptedDatabaseFactory(private val key: Key): NativeConnectionFactory() {
11-
override fun resolveDefaultDatabasePath(dbFilename: String): String {
12-
return appleDefaultDatabasePath(dbFilename)
13-
}
10+
public class NativeEncryptedDatabaseFactory(
11+
private val key: Key,
12+
) : NativeConnectionFactory() {
13+
override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename)
1414

15-
override fun openConnection(path: String, openFlags: Int): SQLiteConnection {
16-
return super.openConnection(path, openFlags).apply {
15+
override fun openConnection(
16+
path: String,
17+
openFlags: Int,
18+
): SQLiteConnection =
19+
super.openConnection(path, openFlags).apply {
1720
if (path != ":memory:") {
1821
// Settings keys for in-memory or temporary databases is not supported.
1922
encryptOrClose(key)
2023
}
2124
}
22-
}
2325
}

0 commit comments

Comments
 (0)