diff --git a/build.gradle.kts b/build.gradle.kts index 3a2f4f628..22b098277 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.androidx.room) apply false alias(libs.plugins.androidx.room3) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.kotlin.compose) apply false diff --git a/core/item/build.gradle.kts b/core/item/build.gradle.kts index 1c22b3418..4ee338c05 100644 --- a/core/item/build.gradle.kts +++ b/core/item/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(libs.plugins.keygo.android.compose) alias(libs.plugins.keygo.android.protobuf) - alias(libs.plugins.androidx.room) + alias(libs.plugins.androidx.room3) alias(libs.plugins.google.ksp) } @@ -19,9 +19,8 @@ android { dependencies { // Room - implementation(libs.androidx.room.runtime) - implementation(libs.androidx.room.ktx) - ksp(libs.androidx.room.compiler) + implementation(libs.androidx.room3.runtime) + ksp(libs.androidx.room3.compiler) implementation(projects.automation) ksp(projects.automationProcessor) @@ -44,7 +43,7 @@ dependencies { } } -room { +room3 { schemaDirectory("$projectDir/schemas") } diff --git a/core/item/consumer-rules.pro b/core/item/consumer-rules.pro index 087733ca4..138772d5f 100644 --- a/core/item/consumer-rules.pro +++ b/core/item/consumer-rules.pro @@ -13,4 +13,6 @@ } # DataStore Proto --keep class androidx.datastore.** { *; } \ No newline at end of file +-keep class androidx.datastore.** { *; } + +-keep class * extends androidx.room3.RoomDatabase { (); } \ No newline at end of file diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/converter/YearMonthConverter.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/converter/YearMonthConverter.kt index 640d9f2d7..338ad4aec 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/converter/YearMonthConverter.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/converter/YearMonthConverter.kt @@ -1,16 +1,16 @@ package de.davis.keygo.core.item.data.local.converter -import androidx.room.TypeConverter +import androidx.room3.ColumnTypeConverter import java.time.YearMonth internal object YearMonthConverter { - @TypeConverter + @ColumnTypeConverter fun fromYearMonth(yearMonth: YearMonth?): Int? = yearMonth?.let { yearMonth.year * 100 + yearMonth.monthValue } - @TypeConverter + @ColumnTypeConverter fun fromInt(value: Int?): YearMonth? = value?.let { YearMonth.of(it / 100, it % 100) } diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/CreditCardDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/CreditCardDao.kt index 9f783ea29..1bcd7d7b4 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/CreditCardDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/CreditCardDao.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Transaction +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.CreditCardEntity import de.davis.keygo.core.item.data.local.pojo.CreditCardProjection import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/DomainInfoDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/DomainInfoDao.kt index 1fe206cb5..b54dd6e72 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/DomainInfoDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/DomainInfoDao.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Transaction +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.DomainInfoEntity import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDao.kt index c01cf11f5..16a2799ad 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDao.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Transaction +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.ItemEntity import de.davis.keygo.core.item.data.local.pojo.ItemWrappedKeyRecord import de.davis.keygo.core.item.data.local.pojo.LightweightItem diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/LoginDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/LoginDao.kt index f2743a9b1..ca0b6e04d 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/LoginDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/LoginDao.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Transaction +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.LoginEntity import de.davis.keygo.core.item.data.local.pojo.LightweightLogin import de.davis.keygo.core.item.data.local.pojo.LoginProjection diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasskeyDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasskeyDao.kt index 65af96f4a..cbeaad9b1 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasskeyDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasskeyDao.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.Query +import androidx.room3.Transaction import de.davis.keygo.core.item.data.local.entity.credential.PasskeyEntity import de.davis.keygo.core.item.data.local.pojo.PasskeyMetadataPojo import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasswordDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasswordDao.kt index 5d4a0ce09..7a1f1b721 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasswordDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/PasswordDao.kt @@ -1,8 +1,8 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.credential.PasswordEntity import de.davis.keygo.core.item.data.local.pojo.PasswordScoreProjection import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TagDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TagDao.kt index d211e7dd1..bdf5e2ce1 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TagDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TagDao.kt @@ -1,10 +1,10 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Transaction import de.davis.keygo.core.item.data.local.entity.TagCrossRef import de.davis.keygo.core.item.data.local.entity.TagEntity import de.davis.keygo.core.item.data.local.pojo.ItemTagProjection diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TotpDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TotpDao.kt index a98f50de9..0a7bdb3c2 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TotpDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/TotpDao.kt @@ -1,8 +1,8 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Upsert +import androidx.room3.Dao +import androidx.room3.Query +import androidx.room3.Upsert import de.davis.keygo.core.item.data.local.entity.credential.TotpEntity import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/VaultDao.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/VaultDao.kt index a7c14aab5..3bb7efc4b 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/VaultDao.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/dao/VaultDao.kt @@ -1,10 +1,10 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Update +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Update import de.davis.keygo.core.item.data.local.entity.KeyInformation import de.davis.keygo.core.item.data.local.entity.VaultEntity import de.davis.keygo.core.item.data.local.pojo.VaultMetadata diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/datasource/ItemDatabase.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/datasource/ItemDatabase.kt index 4c1f22574..df794b87b 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/datasource/ItemDatabase.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/datasource/ItemDatabase.kt @@ -1,10 +1,11 @@ package de.davis.keygo.core.item.data.local.datasource import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverters +import androidx.room3.ColumnTypeConverters +import androidx.room3.Database +import androidx.room3.Room +import androidx.room3.RoomDatabase +import androidx.sqlite.driver.AndroidSQLiteDriver import de.davis.keygo.core.item.data.local.converter.YearMonthConverter import de.davis.keygo.core.item.data.local.dao.CreditCardDao import de.davis.keygo.core.item.data.local.dao.DomainInfoDao @@ -43,7 +44,7 @@ import org.koin.core.annotation.Single ], version = 1, ) -@TypeConverters(YearMonthConverter::class) +@ColumnTypeConverters(YearMonthConverter::class) internal abstract class ItemDatabase : RoomDatabase() { abstract fun vaultDao(): VaultDao @@ -69,11 +70,12 @@ internal class DatabaseModule { @Single fun provideDatabase(context: Context): ItemDatabase = - Room.databaseBuilder( + Room.databaseBuilder( context, - ItemDatabase::class.java, "keygo_database", - ).build() + ) + .setDriver(AndroidSQLiteDriver()) + .build() @Single fun provideVaultDao(db: ItemDatabase) = db.vaultDao() diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/CreditCardEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/CreditCardEntity.kt index 47c2ee1a1..b05f51634 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/CreditCardEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/CreditCardEntity.kt @@ -1,10 +1,10 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.model.EncryptedPayload import java.time.YearMonth diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/DomainInfoEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/DomainInfoEntity.kt index 18c818b74..a8ecbd576 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/DomainInfoEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/DomainInfoEntity.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.Index import de.davis.keygo.core.item.domain.alias.ItemId @Entity( diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/ItemEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/ItemEntity.kt index e117fb391..abd86e902 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/ItemEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/ItemEntity.kt @@ -1,11 +1,11 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.Index +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.alias.VaultId import de.davis.keygo.core.item.generated.domain.model.VaultItemType diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/KeyInformation.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/KeyInformation.kt index 6d2266a7b..5bf5c1745 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/KeyInformation.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/KeyInformation.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo +import androidx.room3.ColumnInfo internal class KeyInformation( @ColumnInfo(name = "wrapped_key") diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/LoginEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/LoginEntity.kt index 15e373029..a3341f7a4 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/LoginEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/LoginEntity.kt @@ -1,8 +1,8 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.domain.alias.ItemId @Entity( diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagCrossRef.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagCrossRef.kt index c6c43bf90..309eaa640 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagCrossRef.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagCrossRef.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.Index import de.davis.keygo.core.item.domain.alias.ItemId @Entity( diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagEntity.kt index e653d2ba4..14acda79b 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/TagEntity.kt @@ -1,8 +1,8 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey +import androidx.room3.Entity +import androidx.room3.Index +import androidx.room3.PrimaryKey @Entity( tableName = "tag", diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/Timestamp.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/Timestamp.kt index d26345ff0..7d9de5331 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/Timestamp.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/Timestamp.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo +import androidx.room3.ColumnInfo internal data class Timestamp( @ColumnInfo("created_at") diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/VaultEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/VaultEntity.kt index 3f8c40233..a99ec8e56 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/VaultEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/VaultEntity.kt @@ -1,9 +1,9 @@ package de.davis.keygo.core.item.data.local.entity -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.domain.alias.VaultId import de.davis.keygo.core.item.domain.model.Vault diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasskeyEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasskeyEntity.kt index 4a7442496..05030aac5 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasskeyEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasskeyEntity.kt @@ -1,11 +1,11 @@ package de.davis.keygo.core.item.data.local.entity.credential -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.Index +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.data.local.entity.LoginEntity import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.model.EncryptedPayload diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasswordEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasswordEntity.kt index e1f2ba56e..47189a37e 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasswordEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/PasswordEntity.kt @@ -1,10 +1,10 @@ package de.davis.keygo.core.item.data.local.entity.credential -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.data.local.entity.LoginEntity import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.model.EncryptedPayload diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/TotpEntity.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/TotpEntity.kt index 17aa70085..04700074b 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/TotpEntity.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/credential/TotpEntity.kt @@ -1,10 +1,10 @@ package de.davis.keygo.core.item.data.local.entity.credential -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.ForeignKey +import androidx.room3.PrimaryKey import de.davis.keygo.core.item.data.local.entity.LoginEntity import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.model.EncryptedPayload diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/CreditCardProjection.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/CreditCardProjection.kt index 8f31b6ef0..e6abd0895 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/CreditCardProjection.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/CreditCardProjection.kt @@ -1,7 +1,7 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.Embedded -import androidx.room.Relation +import androidx.room3.Embedded +import androidx.room3.Relation import de.davis.keygo.core.item.data.local.entity.CreditCardEntity import de.davis.keygo.core.item.data.local.entity.ItemEntity @@ -10,8 +10,8 @@ internal data class CreditCardProjection( val creditCardEntity: CreditCardEntity, @Relation( - parentColumn = "id", - entityColumn = "id", + parentColumns = ["id"], + entityColumns = ["id"], entity = ItemEntity::class, ) val item: ItemProjection diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemProjection.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemProjection.kt index b651ce0a0..c47d16484 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemProjection.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemProjection.kt @@ -1,8 +1,8 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.Embedded -import androidx.room.Junction -import androidx.room.Relation +import androidx.room3.Embedded +import androidx.room3.Junction +import androidx.room3.Relation import de.davis.keygo.core.item.data.local.entity.ItemEntity import de.davis.keygo.core.item.data.local.entity.TagCrossRef import de.davis.keygo.core.item.data.local.entity.TagEntity @@ -11,12 +11,12 @@ internal data class ItemProjection( @Embedded val itemEntity: ItemEntity, @Relation( - parentColumn = "id", - entityColumn = "id", + parentColumns = ["id"], + entityColumns = ["id"], associateBy = Junction( value = TagCrossRef::class, - parentColumn = "item_id", - entityColumn = "tag_id", + parentColumns = ["item_id"], + entityColumns = ["tag_id"], ), ) val tags: Set = emptySet(), diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemWrappedKeyRecord.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemWrappedKeyRecord.kt index ae01bdedb..b964ed0b0 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemWrappedKeyRecord.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/ItemWrappedKeyRecord.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.Embedded +import androidx.room3.Embedded import de.davis.keygo.core.item.data.local.entity.KeyInformation import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.alias.VaultId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LightweightLogin.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LightweightLogin.kt index b3373abdf..bd9edd7e2 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LightweightLogin.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LightweightLogin.kt @@ -1,7 +1,7 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.ColumnInfo -import androidx.room.Relation +import androidx.room3.ColumnInfo +import androidx.room3.Relation import de.davis.keygo.core.item.data.local.entity.DomainInfoEntity import de.davis.keygo.core.item.domain.alias.ItemId @@ -14,8 +14,8 @@ internal data class LightweightLogin( val hasPassword: Boolean, @Relation( - parentColumn = "id", - entityColumn = "login_id", + parentColumns = ["id"], + entityColumns = ["login_id"], ) val domains: List, ) diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LoginProjection.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LoginProjection.kt index 286e27235..97e5aa8a5 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LoginProjection.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/LoginProjection.kt @@ -1,7 +1,7 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.Embedded -import androidx.room.Relation +import androidx.room3.Embedded +import androidx.room3.Relation import de.davis.keygo.core.item.data.local.entity.DomainInfoEntity import de.davis.keygo.core.item.data.local.entity.ItemEntity import de.davis.keygo.core.item.data.local.entity.LoginEntity @@ -15,34 +15,34 @@ internal data class LoginProjection( val loginEntity: LoginEntity, @Relation( - parentColumn = "id", - entityColumn = "id", + parentColumns = ["id"], + entityColumns = ["id"], entity = ItemEntity::class, ) val item: ItemProjection, @Relation( - parentColumn = "id", - entityColumn = "login_id", + parentColumns = ["id"], + entityColumns = ["login_id"], ) val passwordEntity: PasswordEntity?, @Relation( - parentColumn = "id", - entityColumn = "login_id", + parentColumns = ["id"], + entityColumns = ["login_id"], entity = PasskeyEntity::class ) val passkeys: List, @Relation( - parentColumn = "id", - entityColumn = "login_id", + parentColumns = ["id"], + entityColumns = ["login_id"], ) val domains: List, @Relation( - parentColumn = "id", - entityColumn = "login_id", + parentColumns = ["id"], + entityColumns = ["login_id"], ) val totp: TotpEntity?, ) diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/MovableItemPojo.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/MovableItemPojo.kt index 364240e03..2e1e9dccf 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/MovableItemPojo.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/MovableItemPojo.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.Embedded +import androidx.room3.Embedded import de.davis.keygo.core.item.data.local.entity.KeyInformation import de.davis.keygo.core.item.domain.alias.ItemId diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyMetadataPojo.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyMetadataPojo.kt index 0992aa9d8..9098d83cd 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyMetadataPojo.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyMetadataPojo.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.ColumnInfo +import androidx.room3.ColumnInfo internal class PasskeyMetadataPojo( @ColumnInfo(name = "vault_name") diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyRefPojo.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyRefPojo.kt index a634e1400..8d1fff73f 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyRefPojo.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasskeyRefPojo.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.ColumnInfo +import androidx.room3.ColumnInfo /** The columns of a passkey row that identify it, without touching its key material. */ internal data class PasskeyRefPojo( diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasswordScoreProjection.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasswordScoreProjection.kt index 1cad148d5..a9f942b67 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasswordScoreProjection.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/pojo/PasswordScoreProjection.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.pojo -import androidx.room.ColumnInfo +import androidx.room3.ColumnInfo import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.model.PasswordScore diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImpl.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImpl.kt index 561af0684..3ac33b343 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImpl.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImpl.kt @@ -1,9 +1,7 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction import de.davis.keygo.core.item.data.local.dao.CreditCardDao import de.davis.keygo.core.item.data.local.dao.ItemDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.pojo.CreditCardProjection import de.davis.keygo.core.item.data.mapper.toCreditCardEntity import de.davis.keygo.core.item.data.mapper.toData @@ -12,6 +10,7 @@ import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.alias.VaultId import de.davis.keygo.core.item.domain.model.CreditCard import de.davis.keygo.core.item.domain.repository.CreditCardRepository +import de.davis.keygo.core.item.domain.repository.TransactionRunner import de.davis.keygo.core.util.Result import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -19,14 +18,14 @@ import org.koin.core.annotation.Single @Single internal class CreditCardRepositoryImpl( - private val database: ItemDatabase, + private val transactionRunner: TransactionRunner, private val itemDao: ItemDao, private val creditCardDao: CreditCardDao, ) : CreditCardRepository { override suspend fun createOrUpdateCreditCard(card: CreditCard): Result = runCatching { - database.withTransaction { + transactionRunner.runInTransaction { itemDao.upsert(card.toData()) creditCardDao.upsert(card.toCreditCardEntity()) diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImpl.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImpl.kt index 8bf2cc6e9..abafce64d 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImpl.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImpl.kt @@ -1,9 +1,7 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction import de.davis.keygo.core.item.data.local.dao.ItemDao import de.davis.keygo.core.item.data.local.dao.TagDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.entity.TagEntity import de.davis.keygo.core.item.data.local.pojo.LightweightItem import de.davis.keygo.core.item.data.local.pojo.LightweightItemSearchResult @@ -20,6 +18,7 @@ import de.davis.keygo.core.item.domain.model.Tag import de.davis.keygo.core.item.domain.model.lite.LiteItem import de.davis.keygo.core.item.domain.model.lite.LiteItemSearchResult import de.davis.keygo.core.item.domain.repository.ItemRepository +import de.davis.keygo.core.item.domain.repository.TransactionRunner import de.davis.keygo.core.item.generated.domain.model.VaultItemType import de.davis.keygo.core.util.Result import kotlinx.coroutines.flow.Flow @@ -28,20 +27,21 @@ import org.koin.core.annotation.Single @Single internal class ItemRepositoryImpl( - private val database: ItemDatabase, + private val transactionRunner: TransactionRunner, private val itemDao: ItemDao, private val tagDao: TagDao, ) : ItemRepository { - override suspend fun deleteItems(itemIds: Set): Unit = database.withTransaction { - if (itemIds.isEmpty()) return@withTransaction + override suspend fun deleteItems(itemIds: Set): Unit = + transactionRunner.runInTransaction { + if (itemIds.isEmpty()) return@runInTransaction - // Collected before the delete: once the rows are gone the cross refs are too, so the - // orphan sweep would have nothing left to look at. - val tagIds = itemIds.flatMap { tagDao.tagIdsForItem(it) }.distinct() - itemDao.delete(itemIds) - if (tagIds.isNotEmpty()) tagDao.pruneOrphans(tagIds) - } + // Collected before the delete: once the rows are gone the cross refs are too, so the + // orphan sweep would have nothing left to look at. + val tagIds = itemIds.flatMap { tagDao.tagIdsForItem(it) }.distinct() + itemDao.delete(itemIds) + if (tagIds.isNotEmpty()) tagDao.pruneOrphans(tagIds) + } override suspend fun createOrUpdateVaultItem(item: Item): ItemId { itemDao.upsert(item.toData()) diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImpl.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImpl.kt index 7c6f3f38e..0a2bb52a6 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImpl.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImpl.kt @@ -1,6 +1,5 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction import de.davis.keygo.core.item.data.local.dao.DomainInfoDao import de.davis.keygo.core.item.data.local.dao.ItemDao import de.davis.keygo.core.item.data.local.dao.LoginDao @@ -8,7 +7,6 @@ import de.davis.keygo.core.item.data.local.dao.PasskeyDao import de.davis.keygo.core.item.data.local.dao.PasswordDao import de.davis.keygo.core.item.data.local.dao.TagDao import de.davis.keygo.core.item.data.local.dao.TotpDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.pojo.LightweightLogin import de.davis.keygo.core.item.data.local.pojo.LoginProjection import de.davis.keygo.core.item.data.mapper.toData @@ -25,6 +23,7 @@ import de.davis.keygo.core.item.domain.model.Login import de.davis.keygo.core.item.domain.model.PasswordScore import de.davis.keygo.core.item.domain.model.lite.LiteLogin import de.davis.keygo.core.item.domain.repository.LoginRepository +import de.davis.keygo.core.item.domain.repository.TransactionRunner import de.davis.keygo.core.util.Result import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,7 +31,7 @@ import org.koin.core.annotation.Single @Single internal class LoginRepositoryImpl( - private val database: ItemDatabase, + private val transactionRunner: TransactionRunner, private val itemDao: ItemDao, private val loginDao: LoginDao, private val passwordDao: PasswordDao, @@ -44,7 +43,7 @@ internal class LoginRepositoryImpl( override suspend fun createOrUpdateLogin(login: Login): Result = runCatching { - database.withTransaction { + transactionRunner.runInTransaction { itemDao.upsert((login as Item).toData()) loginDao.upsert(login.toLoginEntity()) @@ -74,7 +73,7 @@ internal class LoginRepositoryImpl( domainInfos: Set, ): Result = runCatching { - database.withTransaction { + transactionRunner.runInTransaction { val dataDomains = domainInfos.map { it.toData(itemId) }.toSet() domainInfoDao.upsertAll(dataDomains) } diff --git a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImpl.kt b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImpl.kt index f228be101..96e08bb22 100644 --- a/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImpl.kt +++ b/core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImpl.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction +import androidx.room3.withWriteTransaction import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.domain.repository.TransactionRunner import org.koin.core.annotation.Single @@ -10,5 +10,5 @@ internal class TransactionRunnerImpl( private val database: ItemDatabase, ) : TransactionRunner { override suspend fun runInTransaction(block: suspend () -> R): R = - database.withTransaction { block() } + database.withWriteTransaction { block() } } diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDaoSearchTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDaoSearchTest.kt index 867257d5e..45360c2da 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDaoSearchTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDaoSearchTest.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Room +import androidx.room3.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.entity.ItemEntity diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/TagDaoTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/TagDaoTest.kt index 67c1c0e23..a68e408b1 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/TagDaoTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/TagDaoTest.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.local.dao -import androidx.room.Room +import androidx.room3.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.entity.ItemEntity diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImplTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImplTest.kt index b5a866883..1fb28f7dd 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImplTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImplTest.kt @@ -1,9 +1,8 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction +import de.davis.keygo.core.item.FakeTransactionRunner import de.davis.keygo.core.item.data.local.dao.CreditCardDao import de.davis.keygo.core.item.data.local.dao.ItemDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.entity.CreditCardEntity import de.davis.keygo.core.item.data.local.entity.ItemEntity import de.davis.keygo.core.item.data.local.pojo.CreditCardProjection @@ -22,14 +21,10 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import java.time.YearMonth -import kotlin.test.AfterTest -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -39,29 +34,15 @@ import de.davis.keygo.core.item.data.local.entity.Timestamp as EntityTimestamp class CreditCardRepositoryImplTest { - private val database = mockk() private val itemDao = mockk(relaxed = true) private val creditCardDao = mockk(relaxed = true) private val repository = CreditCardRepositoryImpl( - database = database, + transactionRunner = FakeTransactionRunner(), itemDao = itemDao, creditCardDao = creditCardDao, ) - @BeforeTest - fun setUp() { - mockkStatic("androidx.room.RoomDatabaseKt") - coEvery { database.withTransaction(any Any?>()) } coAnswers { - secondArg Any?>().invoke() - } - } - - @AfterTest - fun tearDown() { - unmockkStatic("androidx.room.RoomDatabaseKt") - } - @Test fun `createOrUpdateCreditCard returns Success with card id`() = runTest { val card = testCreditCard() @@ -91,7 +72,7 @@ class CreditCardRepositoryImplTest { val result = repository.createOrUpdateCreditCard(testCreditCard()) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -102,7 +83,7 @@ class CreditCardRepositoryImplTest { val result = repository.createOrUpdateCreditCard(testCreditCard()) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/FailureAssertions.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/FailureAssertions.kt new file mode 100644 index 000000000..dfb50c987 --- /dev/null +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/FailureAssertions.kt @@ -0,0 +1,22 @@ +package de.davis.keygo.core.item.data.repository + +import kotlin.test.assertSame + +/** + * Asserts that [actual] reports the failure raised as [expected]. + * + * A transaction runner runs its block in a separate coroutine, and kotlinx.coroutines rebuilds an + * exception that crosses a coroutine boundary so its stack trace points back at the caller. The + * rebuilt exception keeps the original class and message and holds the original as its cause, but + * it is a new instance. Comparing references directly would therefore only hold for a runner that + * skipped the context switch, which neither Room nor [de.davis.keygo.core.item.FakeTransactionRunner] + * does. + */ +internal fun assertFailedWith(expected: Throwable, actual: Throwable?) { + val raised = if (actual === expected) actual else actual?.cause + assertSame( + expected, + raised, + "expected the failure to carry $expected, but was $actual", + ) +} diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImplTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImplTest.kt index 571a4930d..67c21012c 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImplTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/ItemRepositoryImplTest.kt @@ -1,9 +1,8 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction +import de.davis.keygo.core.item.FakeTransactionRunner import de.davis.keygo.core.item.data.local.dao.ItemDao import de.davis.keygo.core.item.data.local.dao.TagDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.pojo.ItemTagProjection import de.davis.keygo.core.item.domain.alias.ItemId import de.davis.keygo.core.item.domain.alias.newItemId @@ -13,39 +12,23 @@ import io.mockk.coVerify import io.mockk.coVerifyOrder import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import kotlin.test.AfterTest -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class ItemRepositoryImplTest { - private val database = mockk() private val itemDao = mockk(relaxed = true) private val tagDao = mockk(relaxed = true) private val repository = ItemRepositoryImpl( - database = database, + transactionRunner = FakeTransactionRunner(), itemDao = itemDao, tagDao = tagDao, ) - @BeforeTest - fun setUp() { - mockkStatic("androidx.room.RoomDatabaseKt") - coEvery { database.withTransaction(any Any?>()) } coAnswers { - secondArg Any?>().invoke() - } - } - - @AfterTest - fun tearDown() = unmockkStatic("androidx.room.RoomDatabaseKt") - @Test fun `deleteItems captures tag ids, deletes items, then prunes those tags`() = runTest { val id = newItemId() diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImplTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImplTest.kt index e0e284c38..96f8ee8fb 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImplTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImplTest.kt @@ -1,6 +1,6 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction +import de.davis.keygo.core.item.FakeTransactionRunner import de.davis.keygo.core.item.data.local.dao.DomainInfoDao import de.davis.keygo.core.item.data.local.dao.ItemDao import de.davis.keygo.core.item.data.local.dao.LoginDao @@ -8,7 +8,6 @@ import de.davis.keygo.core.item.data.local.dao.PasskeyDao import de.davis.keygo.core.item.data.local.dao.PasswordDao import de.davis.keygo.core.item.data.local.dao.TagDao import de.davis.keygo.core.item.data.local.dao.TotpDao -import de.davis.keygo.core.item.data.local.datasource.ItemDatabase import de.davis.keygo.core.item.data.local.entity.TagEntity import de.davis.keygo.core.item.data.local.entity.credential.PasswordEntity import de.davis.keygo.core.item.data.local.entity.credential.TotpEntity @@ -31,18 +30,13 @@ import de.davis.keygo.core.util.isSuccess import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import kotlin.test.AfterTest -import kotlin.test.BeforeTest +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -import kotlinx.coroutines.test.runTest class LoginRepositoryImplTest { - private val database = mockk() private val itemDao = mockk(relaxed = true) private val loginDao = mockk(relaxed = true) private val passwordDao = mockk(relaxed = true) @@ -52,7 +46,7 @@ class LoginRepositoryImplTest { private val passkeyDao = mockk(relaxed = true) private val repository = LoginRepositoryImpl( - database = database, + transactionRunner = FakeTransactionRunner(), itemDao = itemDao, loginDao = loginDao, passwordDao = passwordDao, @@ -62,19 +56,6 @@ class LoginRepositoryImplTest { passkeyDao = passkeyDao, ) - @BeforeTest - fun setUp() { - mockkStatic("androidx.room.RoomDatabaseKt") - coEvery { database.withTransaction(any Any?>()) } coAnswers { - secondArg Any?>().invoke() - } - } - - @AfterTest - fun tearDown() { - unmockkStatic("androidx.room.RoomDatabaseKt") - } - @Test fun `createOrUpdateLogin upserts totp row when login has a totp`() = runTest { val login = testLogin { id -> testTotp(loginId = id) } @@ -115,7 +96,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin(totpProvider = null)) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -126,7 +107,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin { id -> testTotp(loginId = id) }) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -166,7 +147,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin(passwordCredential = null)) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -178,7 +159,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin(passwordCredential = pwd)) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -210,7 +191,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin(totpProvider = null)) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } @Test @@ -248,7 +229,7 @@ class LoginRepositoryImplTest { val result = repository.createOrUpdateLogin(testLogin(totpProvider = null)) assertTrue(result.isFailure()) - assertEquals(error, result.error) + assertFailedWith(error, result.error) } private fun testLogin( diff --git a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImplTest.kt b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImplTest.kt index e20535410..21dee918d 100644 --- a/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImplTest.kt +++ b/core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/TransactionRunnerImplTest.kt @@ -1,44 +1,47 @@ package de.davis.keygo.core.item.data.repository -import androidx.room.withTransaction +import androidx.room3.Room +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import de.davis.keygo.core.item.data.local.datasource.ItemDatabase -import io.mockk.coEvery -import io.mockk.coVerify +import de.davis.keygo.core.item.data.local.entity.VaultEntity +import de.davis.keygo.core.item.domain.alias.VaultId +import de.davis.keygo.core.item.domain.alias.newVaultId +import de.davis.keygo.core.item.domain.model.Vault import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import de.davis.keygo.core.item.data.local.entity.KeyInformation as EntityKeyInformation /** - * Verifies that [TransactionRunnerImpl] delegates to [ItemDatabase.withTransaction] - * transparently: it returns whatever the block returns, propagates whatever the block throws, and - * invokes the block exactly once inside the transaction. + * Verifies that [TransactionRunnerImpl] delegates to Room transparently: it returns whatever the + * block returns, propagates whatever the block throws, invokes the block exactly once, and rolls + * back what the block wrote when it throws. * - * This does not cover real SQLite commit/rollback. That is Room's own behaviour, and this project - * has no way to exercise it in a JVM unit test without Robolectric, which is not used here. + * This runs against a real in-memory database on the bundled SQLite driver, the same setup the DAO + * tests use, so the rollback is SQLite's own rather than a stub standing in for it. */ internal class TransactionRunnerImplTest { - private val database = mockk() - private val runner = TransactionRunnerImpl(database) + private lateinit var database: ItemDatabase + private lateinit var runner: TransactionRunnerImpl @BeforeTest fun setUp() { - mockkStatic("androidx.room.RoomDatabaseKt") - coEvery { database.withTransaction(any Any?>()) } coAnswers { - secondArg Any?>().invoke() - } + database = Room.inMemoryDatabaseBuilder(mockk(relaxed = true), ItemDatabase::class.java) + .setDriver(BundledSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) + .build() + runner = TransactionRunnerImpl(database) } @AfterTest - fun tearDown() { - unmockkStatic("androidx.room.RoomDatabaseKt") - } + fun tearDown() = database.close() @Test fun `returns the block result`() = runTest { @@ -53,16 +56,37 @@ internal class TransactionRunnerImplTest { runner.runInTransaction { throw error } } - assertEquals(error, thrown) + assertFailedWith(error, thrown) } @Test - fun `invokes the block exactly once inside the transaction`() = runTest { + fun `invokes the block exactly once`() = runTest { var invocations = 0 runner.runInTransaction { invocations++ } assertEquals(1, invocations) - coVerify(exactly = 1) { database.withTransaction(any Any?>()) } } + + @Test + fun `rolls back what the block wrote when it throws`() = runTest { + val vaultId = newVaultId() + + assertFailsWith { + runner.runInTransaction { + database.vaultDao().insert(vault(vaultId)) + error("boom") + } + } + + assertNull(database.vaultDao().getVaultMetadata(vaultId)) + } + + private fun vault(id: VaultId) = VaultEntity( + id = id, + name = "Vault", + icon = Vault.Icon.Person, + createdAt = 0L, + keyInformation = EntityKeyInformation(byteArrayOf(), byteArrayOf()), + ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ddee4dd9f..f3410ef27 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,6 @@ mockk = "1.14.11" semver = "0.2.5" koinBOM = "4.2.1" koin-plugin = "1.0.0" -room = "2.8.4" room3 = "3.0.0" sqlite = "2.6.2" ksp = "2.3.6" @@ -95,11 +94,8 @@ koin-annotations = { group = "io.insert-koin", name = "koin-annotations" } koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose" } koin-androidx-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager" } -androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } androidx-room3-runtime = { group = "androidx.room3", name = "room3-runtime", version.ref = "room3" } androidx-room3-testing = { group = "androidx.room3", name = "room3-testing", version.ref = "room3" } -androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } -androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } androidx-room3-compiler = { group = "androidx.room3", name = "room3-compiler", version.ref = "room3" } androidx-sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled-jvm", version.ref = "sqlite" } @@ -142,7 +138,6 @@ test-common = ["kotlin-test", "kotlinx-coroutines-test"] google-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } google-protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } android-application = { id = "com.android.application", version.ref = "agp" } -androidx-room = { id = "androidx.room", version.ref = "room" } androidx-room3 = { id = "androidx.room3", version.ref = "room3" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }