diff --git a/app/src/integrationTest/kotlin/com/subtracks/data/sync/MultiSourceSyncIntegrationTest.kt b/app/src/integrationTest/kotlin/com/subtracks/data/sync/MultiSourceSyncIntegrationTest.kt index 12553d60..3e764de7 100644 --- a/app/src/integrationTest/kotlin/com/subtracks/data/sync/MultiSourceSyncIntegrationTest.kt +++ b/app/src/integrationTest/kotlin/com/subtracks/data/sync/MultiSourceSyncIntegrationTest.kt @@ -1,11 +1,7 @@ package com.subtracks.data.sync -import android.content.Context -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Source import com.subtracks.data.source.subsonic.TestServers import kotlinx.coroutines.runBlocking @@ -18,12 +14,7 @@ class MultiSourceSyncIntegrationTest { @Test fun syncsEveryServerIntoOneLibraryWithoutInterference() = runBlocking { - val context = ApplicationProvider.getApplicationContext() - val db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + val db = createTestDatabase() try { TestServers.all.forEachIndexed { index, server -> val id = index + 1L diff --git a/app/src/integrationTest/kotlin/com/subtracks/data/sync/PruneSyncIntegrationTest.kt b/app/src/integrationTest/kotlin/com/subtracks/data/sync/PruneSyncIntegrationTest.kt index 524432ea..f26ddd04 100644 --- a/app/src/integrationTest/kotlin/com/subtracks/data/sync/PruneSyncIntegrationTest.kt +++ b/app/src/integrationTest/kotlin/com/subtracks/data/sync/PruneSyncIntegrationTest.kt @@ -1,11 +1,7 @@ package com.subtracks.data.sync -import android.content.Context -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Source import com.subtracks.data.source.subsonic.SubsonicClient import com.subtracks.data.source.subsonic.SubsonicSource @@ -28,12 +24,7 @@ class PruneSyncIntegrationTest { val removed = File(musicDir, REMOVED_ALBUM_DIR) check(removed.isDirectory) { "$removed is missing; the harness must prepare the prune library" } - val context = ApplicationProvider.getApplicationContext() - val db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + val db = createTestDatabase() try { db.sourcesDao().upsertSource( Source(id = 1, name = "prune", address = baseUrl, isActive = true, createdAt = 0), diff --git a/app/src/integrationTest/kotlin/com/subtracks/data/sync/SyncServiceIntegrationTest.kt b/app/src/integrationTest/kotlin/com/subtracks/data/sync/SyncServiceIntegrationTest.kt index 49a203f1..5bf06f18 100644 --- a/app/src/integrationTest/kotlin/com/subtracks/data/sync/SyncServiceIntegrationTest.kt +++ b/app/src/integrationTest/kotlin/com/subtracks/data/sync/SyncServiceIntegrationTest.kt @@ -1,11 +1,7 @@ package com.subtracks.data.sync -import android.content.Context -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Source import com.subtracks.data.source.subsonic.TestServers import kotlinx.coroutines.runBlocking @@ -19,12 +15,7 @@ class SyncServiceIntegrationTest { fun syncMirrorsTheLibraryIntoRoomForEveryServer() = runBlocking { servers.forEach { server -> - val context = ApplicationProvider.getApplicationContext() - val db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + val db = createTestDatabase() try { db.sourcesDao().upsertSource( Source(id = 1, name = server.name, address = server.baseUrl, isActive = true, createdAt = 0), diff --git a/app/src/main/kotlin/com/subtracks/data/db/Databases.kt b/app/src/main/kotlin/com/subtracks/data/db/Databases.kt index 8d5b8639..15be2da4 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/Databases.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/Databases.kt @@ -2,6 +2,7 @@ package com.subtracks.data.db import android.content.Context import androidx.room3.Room +import androidx.room3.RoomDatabase import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.subtracks.log.Log @@ -11,7 +12,9 @@ fun createAndroidDatabase(context: Context): SubtracksDatabase { .databaseBuilder(context, "subtracks.db") .setDriver(driver) .addMigrations(*MIGRATIONS.map(::LoggedMigration).toTypedArray()) - .addCallback(ArtistSearchTriggerOptimization) - .addCallback(SqliteLifecycleLogging) + .addSubtracksDatabaseCallbacks() .build() } + +internal fun RoomDatabase.Builder.addSubtracksDatabaseCallbacks(): RoomDatabase.Builder = + addCallback(ArtistSearchTriggerOptimization).addCallback(SqliteLifecycleLogging) diff --git a/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt index cacf6417..0d5c10c0 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt @@ -1,9 +1,5 @@ package com.subtracks.data.db -import android.content.Context -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.model.ArtworkSeed import com.subtracks.data.model.Source @@ -22,12 +18,7 @@ class ArtworkSeedDaoTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() } @After diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt index e97d74a0..a68c761f 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt @@ -1,12 +1,8 @@ package com.subtracks.data.db -import android.content.Context import androidx.paging.PagingSource -import androidx.room3.Room import androidx.room3.useReaderConnection import androidx.room3.useWriterConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil import com.subtracks.data.model.Album @@ -39,12 +35,7 @@ class LibraryDaoTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() } @After @@ -895,6 +886,26 @@ class LibraryDaoTest { assertEquals(listOf("art-2", "art-1"), dao.artistOnce(sourceId, "ar-1")!!.downloadedCoverArts) } + @Test + fun freshDatabaseOptimizesArtistSearchTriggers() = + runTest { + val triggers = + db.useReaderConnection { connection -> + connection.usePrepared( + "SELECT sql FROM sqlite_master WHERE type = 'trigger' " + + "AND name IN ('room_fts_content_sync_artist_search_BEFORE_UPDATE', " + + "'room_fts_content_sync_artist_search_AFTER_UPDATE')", + ) { statement -> + buildList { + while (statement.step()) add(statement.getText(0)) + } + } + } + + assertEquals(2, triggers.size) + assertTrue(triggers.all { it.contains("UPDATE OF `name`, `sortName`") }) + } + @Test fun artistNameUpdatesKeepTheSearchIndexValid() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt index 85c90b86..6eb2eb9c 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt @@ -1,11 +1,7 @@ package com.subtracks.data.db -import android.content.Context import androidx.paging.PagingSource -import androidx.room3.Room import androidx.room3.useWriterConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.model.Album import com.subtracks.data.model.Artist @@ -27,12 +23,7 @@ class LibraryHomeDaoTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() } @After diff --git a/app/src/test/kotlin/com/subtracks/data/db/QueryTimingMeasurementTest.kt b/app/src/test/kotlin/com/subtracks/data/db/QueryTimingMeasurementTest.kt index 30370648..6bf05942 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/QueryTimingMeasurementTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/QueryTimingMeasurementTest.kt @@ -1,12 +1,9 @@ package com.subtracks.data.db -import android.content.Context import androidx.paging.PagingSource -import androidx.room3.Room import androidx.room3.immediateTransaction import androidx.room3.useWriterConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.model.Album import com.subtracks.data.model.Artist @@ -40,14 +37,8 @@ class QueryTimingMeasurementTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() val driver = LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it } - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(driver) - .addCallback(ArtistSearchTriggerOptimization) - .build() + db = createTestDatabase(driver) } @After diff --git a/app/src/test/kotlin/com/subtracks/data/db/TestDatabase.kt b/app/src/test/kotlin/com/subtracks/data/db/TestDatabase.kt new file mode 100644 index 00000000..10c54a55 --- /dev/null +++ b/app/src/test/kotlin/com/subtracks/data/db/TestDatabase.kt @@ -0,0 +1,16 @@ +package com.subtracks.data.db + +import android.content.Context +import androidx.room3.Room +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import androidx.test.core.app.ApplicationProvider + +fun createTestDatabase(driver: SQLiteDriver = BundledSQLiteDriver()): SubtracksDatabase { + val context = ApplicationProvider.getApplicationContext() + return Room + .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) + .setDriver(driver) + .addSubtracksDatabaseCallbacks() + .build() +} diff --git a/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt index ffb7a453..b571ffc3 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt @@ -3,13 +3,12 @@ package com.subtracks.data.repo import android.content.Context import android.content.res.Resources import android.net.Uri -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkFetcher import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.FakeDownloadEngine @@ -84,11 +83,7 @@ class DownloadRepositoryTest { fun setUp() { val context = ApplicationProvider.getApplicationContext() resources = context.resources - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) sources = SourceRepository(db, OkHttpClient(), prefs, artwork, scope = scope) diff --git a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt index 4fe24b0e..8498ccde 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt @@ -4,12 +4,11 @@ import android.content.Context import android.content.res.Resources import androidx.paging.PagingDataEvent import androidx.paging.PagingDataPresenter -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.model.Album import com.subtracks.data.model.ArtistBio @@ -63,11 +62,7 @@ class LibraryRepositoryTest { fun setUp() { val context = ApplicationProvider.getApplicationContext() resources = context.resources - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope) diff --git a/app/src/test/kotlin/com/subtracks/data/repo/PendingServerActionSinkTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/PendingServerActionSinkTest.kt index ab2a57fa..d6b914a0 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/PendingServerActionSinkTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/PendingServerActionSinkTest.kt @@ -1,13 +1,10 @@ package com.subtracks.data.repo -import android.content.Context -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.PendingActionKind import com.subtracks.data.model.Source import com.subtracks.data.source.ServerActionSink @@ -82,12 +79,7 @@ class PendingServerActionSinkTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) sourceId = runBlocking { db.sourcesDao().insertSource(Source(name = "server", address = "http://x/", createdAt = 0)) } delegate = RecordingSink() diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt index b1ba328e..2edbe94e 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt @@ -1,12 +1,9 @@ package com.subtracks.data.repo -import android.content.Context -import androidx.room3.Room import androidx.room3.useReaderConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Album import com.subtracks.data.model.PlaylistSong import com.subtracks.data.model.Song @@ -26,12 +23,7 @@ class QueueKeysetTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() repository = QueueRepository(db) } diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt index 775b6eee..19952949 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt @@ -1,13 +1,10 @@ package com.subtracks.data.repo -import android.content.Context -import androidx.room3.Room import androidx.room3.useReaderConnection import androidx.room3.useWriterConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Album import com.subtracks.data.model.DownloadStatus import com.subtracks.data.model.Playlist @@ -33,12 +30,7 @@ class QueueRepositoryTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() repository = QueueRepository(db) } diff --git a/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt index 1ba2508c..832db934 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt @@ -2,13 +2,12 @@ package com.subtracks.data.repo import android.content.Context import android.content.res.Resources -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.artistImageKey @@ -58,11 +57,7 @@ class SourceRepositoryTest { fun setUp() { val context = ApplicationProvider.getApplicationContext() resources = context.resources - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) prefs = fakeUserPreferences() scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) diff --git a/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt b/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt index 81c53d7d..f0e139f2 100644 --- a/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt @@ -2,13 +2,12 @@ package com.subtracks.data.sync import android.content.Context import android.content.res.Resources -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.prefs.UserPreferences import com.subtracks.data.prefs.fakeUserPreferences @@ -55,11 +54,7 @@ class SyncManagerTest { fun setUp() { val context = ApplicationProvider.getApplicationContext() resources = context.resources - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope) diff --git a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt index 4d78a764..54fa5b22 100644 --- a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt @@ -1,17 +1,15 @@ package com.subtracks.data.sync -import android.content.Context import androidx.paging.PagingSource -import androidx.room3.Room import androidx.room3.useWriterConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.db.LoggingSQLiteDriver import com.subtracks.data.db.SqlExecution import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.artistPages import com.subtracks.data.db.artistsPageQuery +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.model.Album import com.subtracks.data.model.Artist import com.subtracks.data.model.ArtistRef @@ -44,12 +42,7 @@ class SyncServiceTest { @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() } @After @@ -1846,13 +1839,8 @@ class SyncServiceTest { @Test fun anIdenticalSyncSkipsTheAggregateQueries() = runTest { - val context = ApplicationProvider.getApplicationContext() val executions = Collections.synchronizedList(mutableListOf()) - val loggingDb = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it }) - .build() + val loggingDb = createTestDatabase(LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it }) try { loggingDb.sourcesDao().upsertSource( Source(id = 1, name = "test", address = "http://localhost", isActive = true, createdAt = 0), diff --git a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt index e65c7c3a..a572cc93 100644 --- a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt +++ b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt @@ -3,9 +3,7 @@ package com.subtracks.playback import android.content.Context import android.content.res.Resources import androidx.media3.common.PlaybackException -import androidx.room3.Room import androidx.room3.useWriterConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.R @@ -14,6 +12,7 @@ import com.subtracks.UiMessage import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.model.Album @@ -83,11 +82,7 @@ class PlaybackControllerTest { fun setUp() { val context = ApplicationProvider.getApplicationContext() resources = context.resources - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() prefs = fakeUserPreferences() networkMode = MutableStateFlow(NetworkMode.Wifi) sources = diff --git a/app/src/test/kotlin/com/subtracks/ui/ViewModelRepositoryFixture.kt b/app/src/test/kotlin/com/subtracks/ui/ViewModelRepositoryFixture.kt new file mode 100644 index 00000000..e75895d6 --- /dev/null +++ b/app/src/test/kotlin/com/subtracks/ui/ViewModelRepositoryFixture.kt @@ -0,0 +1,79 @@ +package com.subtracks.ui + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.subtracks.cancelAndJoinBlocking +import com.subtracks.data.db.createTestDatabase +import com.subtracks.data.download.ArtworkStore +import com.subtracks.data.download.FakeDownloadEngine +import com.subtracks.data.prefs.fakeUserPreferences +import com.subtracks.data.repo.DownloadRepository +import com.subtracks.data.repo.LibraryRepository +import com.subtracks.data.repo.NetworkServerActionSink +import com.subtracks.data.repo.QueueRepository +import com.subtracks.data.repo.SourceRepository +import com.subtracks.playback.FakePlayerConnection +import com.subtracks.playback.FakePlayerHandle +import com.subtracks.playback.PlaybackController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import okhttp3.OkHttpClient +import java.io.File +import java.util.concurrent.Executors + +@OptIn(ExperimentalCoroutinesApi::class) +class ViewModelRepositoryFixture { + val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + init { + Dispatchers.setMain(dispatcher) + } + + val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + val controllerScope = CoroutineScope(SupervisorJob() + dispatcher) + val context = ApplicationProvider.getApplicationContext() + val db = createTestDatabase() + val artworkStore = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) + val downloadEngine = FakeDownloadEngine() + val downloadsDir = File(context.cacheDir, "downloads-${System.nanoTime()}") + val sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artworkStore, scope = repoScope) + val downloadRepository = + DownloadRepository( + db, + sourceRepository, + downloadEngine, + downloadsDir, + artworkStore = artworkStore, + artworkFetcher = { ByteArray(0) }, + scope = repoScope, + ) + val queueRepository = QueueRepository(db) + val controller = + PlaybackController( + sourceRepository, + queueRepository, + FakePlayerConnection(FakePlayerHandle()), + downloadRepository, + scope = controllerScope, + ) + val libraryRepository = + LibraryRepository( + db, + sourceRepository, + NetworkServerActionSink(sourceRepository), + fakeUserPreferences(), + scope = repoScope, + ) + + fun close() { + cancelAndJoinBlocking(controllerScope, repoScope) + db.close() + Dispatchers.resetMain() + dispatcher.close() + } +} diff --git a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt index 1adcde8c..148c80df 100644 --- a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt @@ -1,40 +1,16 @@ package com.subtracks.ui.downloads -import android.content.Context import androidx.lifecycle.viewModelScope -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil -import com.subtracks.cancelAndJoinBlocking -import com.subtracks.data.db.SubtracksDatabase -import com.subtracks.data.download.ArtworkStore -import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.model.DownloadList import com.subtracks.data.model.DownloadStatus import com.subtracks.data.model.Song import com.subtracks.data.model.SongDownload -import com.subtracks.data.prefs.fakeUserPreferences -import com.subtracks.data.repo.DownloadRepository -import com.subtracks.data.repo.LibraryRepository -import com.subtracks.data.repo.NetworkServerActionSink -import com.subtracks.data.repo.QueueRepository -import com.subtracks.data.repo.SourceRepository -import com.subtracks.playback.FakePlayerConnection -import com.subtracks.playback.FakePlayerHandle -import com.subtracks.playback.PlaybackController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.subtracks.ui.ViewModelRepositoryFixture import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import okhttp3.OkHttpClient import org.junit.After import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -42,61 +18,23 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import java.io.File -import java.util.concurrent.Executors -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class DownloadsViewModelTest { - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - - private lateinit var db: SubtracksDatabase - private lateinit var sourceRepository: SourceRepository - private lateinit var downloadRepository: DownloadRepository - private lateinit var controller: PlaybackController + private lateinit var fixture: ViewModelRepositoryFixture + private val db get() = fixture.db + private val sourceRepository get() = fixture.sourceRepository + private val downloadRepository get() = fixture.downloadRepository + private val controller get() = fixture.controller private lateinit var viewModel: DownloadsViewModel - private val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private val controllerScope = CoroutineScope(SupervisorJob() + dispatcher) private var sourceId: Long = 0 @Before fun setUp() { - Dispatchers.setMain(dispatcher) - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() - val artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) - sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artwork, scope = repoScope) - downloadRepository = - DownloadRepository( - db, - sourceRepository, - FakeDownloadEngine(), - File(context.cacheDir, "downloads-${System.nanoTime()}"), - artworkStore = artwork, - artworkFetcher = { ByteArray(0) }, - scope = repoScope, - ) - controller = - PlaybackController( - sourceRepository, - QueueRepository(db), - FakePlayerConnection(FakePlayerHandle()), - downloadRepository, - scope = controllerScope, - ) + fixture = ViewModelRepositoryFixture() viewModel = DownloadsViewModel( - LibraryRepository( - db, - sourceRepository, - NetworkServerActionSink(sourceRepository), - fakeUserPreferences(), - scope = repoScope, - ), + fixture.libraryRepository, downloadRepository, controller, "Unknown artist", @@ -107,10 +45,7 @@ class DownloadsViewModelTest { @After fun tearDown() { runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } - cancelAndJoinBlocking(controllerScope, repoScope) - db.close() - Dispatchers.resetMain() - dispatcher.close() + fixture.close() } @Test diff --git a/app/src/test/kotlin/com/subtracks/ui/playback/NowPlayingRouteTest.kt b/app/src/test/kotlin/com/subtracks/ui/playback/NowPlayingRouteTest.kt index 659611de..1bc4d9e8 100644 --- a/app/src/test/kotlin/com/subtracks/ui/playback/NowPlayingRouteTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/playback/NowPlayingRouteTest.kt @@ -5,14 +5,13 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.model.Song @@ -81,11 +80,7 @@ class NowPlayingRouteTest { private fun setUpState() { val context = ApplicationProvider.getApplicationContext() val artworkStore = ArtworkStore(File(context.cacheDir, "art")) - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() val sources = SourceRepository(db, OkHttpClient(), prefs, artworkStore, scope = repoScope) val queues = QueueRepository(db) downloads = diff --git a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt index 6b1b6df1..4e9a5ffa 100644 --- a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt @@ -2,14 +2,13 @@ package com.subtracks.ui.playback import android.content.Context import androidx.lifecycle.viewModelScope -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.model.AlbumSongItem @@ -68,11 +67,7 @@ class QueueViewModelTest { fun setUp() { Dispatchers.setMain(dispatcher) val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() sources = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = repoScope) queues = QueueRepository(db) diff --git a/app/src/test/kotlin/com/subtracks/ui/search/SearchViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/search/SearchViewModelTest.kt index 7629e088..ac24e605 100644 --- a/app/src/test/kotlin/com/subtracks/ui/search/SearchViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/search/SearchViewModelTest.kt @@ -1,43 +1,19 @@ package com.subtracks.ui.search -import android.content.Context import androidx.lifecycle.viewModelScope -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil -import com.subtracks.cancelAndJoinBlocking -import com.subtracks.data.db.SubtracksDatabase -import com.subtracks.data.download.ArtworkStore -import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.model.Album import com.subtracks.data.model.Artist import com.subtracks.data.model.Playlist import com.subtracks.data.model.QueueKind import com.subtracks.data.model.Song -import com.subtracks.data.prefs.fakeUserPreferences -import com.subtracks.data.repo.DownloadRepository -import com.subtracks.data.repo.LibraryRepository -import com.subtracks.data.repo.NetworkServerActionSink -import com.subtracks.data.repo.QueueRepository -import com.subtracks.data.repo.SourceRepository -import com.subtracks.playback.FakePlayerConnection -import com.subtracks.playback.FakePlayerHandle -import com.subtracks.playback.PlaybackController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.subtracks.ui.ViewModelRepositoryFixture import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import kotlinx.coroutines.withTimeout -import okhttp3.OkHttpClient import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -45,70 +21,24 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import java.io.File -import java.util.concurrent.Executors -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class SearchViewModelTest { - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - - private lateinit var db: SubtracksDatabase - private lateinit var sourceRepository: SourceRepository + private lateinit var fixture: ViewModelRepositoryFixture + private val db get() = fixture.db + private val sourceRepository get() = fixture.sourceRepository private lateinit var viewModel: SearchViewModel - private val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private val controllerScope = CoroutineScope(SupervisorJob() + dispatcher) @Before fun setUp() { - Dispatchers.setMain(dispatcher) - val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() - val artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) - sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artwork, scope = repoScope) - val downloads = - DownloadRepository( - db, - sourceRepository, - FakeDownloadEngine(), - File(context.cacheDir, "downloads-${System.nanoTime()}"), - artworkStore = artwork, - artworkFetcher = { ByteArray(0) }, - scope = repoScope, - ) - val controller = - PlaybackController( - sourceRepository, - QueueRepository(db), - FakePlayerConnection(FakePlayerHandle()), - downloads, - scope = controllerScope, - ) - viewModel = - SearchViewModel( - LibraryRepository( - db, - sourceRepository, - NetworkServerActionSink(sourceRepository), - fakeUserPreferences(), - scope = repoScope, - ), - sourceRepository, - controller, - ) + fixture = ViewModelRepositoryFixture() + viewModel = SearchViewModel(fixture.libraryRepository, sourceRepository, fixture.controller) } @After fun tearDown() { runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } - cancelAndJoinBlocking(controllerScope, repoScope) - db.close() - Dispatchers.resetMain() - dispatcher.close() + fixture.close() } @Test diff --git a/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt index ea30f01a..53624377 100644 --- a/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt @@ -2,13 +2,12 @@ package com.subtracks.ui.settings import android.content.Context import androidx.lifecycle.viewModelScope -import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.awaitUntil import com.subtracks.cancelAndJoinBlocking import com.subtracks.data.db.SubtracksDatabase +import com.subtracks.data.db.createTestDatabase import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.prefs.fakeUserPreferences @@ -57,11 +56,7 @@ class AddSourceViewModelTest { fun setUp() { Dispatchers.setMain(dispatcher) val context = ApplicationProvider.getApplicationContext() - db = - Room - .inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java) - .setDriver(BundledSQLiteDriver()) - .build() + db = createTestDatabase() val artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artwork, scope = repoScope) downloadRepository =