test: share Room database and repository fixtures

This commit is contained in:
austinried
2026-11-04 11:08:31 +00:00
parent d6ff3ee086
commit 599924bdd0
24 changed files with 171 additions and 327 deletions
@@ -1,11 +1,7 @@
package com.subtracks.data.sync 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 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.model.Source
import com.subtracks.data.source.subsonic.TestServers import com.subtracks.data.source.subsonic.TestServers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -18,12 +14,7 @@ class MultiSourceSyncIntegrationTest {
@Test @Test
fun syncsEveryServerIntoOneLibraryWithoutInterference() = fun syncsEveryServerIntoOneLibraryWithoutInterference() =
runBlocking { runBlocking {
val context = ApplicationProvider.getApplicationContext<Context>() val db = createTestDatabase()
val db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
try { try {
TestServers.all.forEachIndexed { index, server -> TestServers.all.forEachIndexed { index, server ->
val id = index + 1L val id = index + 1L
@@ -1,11 +1,7 @@
package com.subtracks.data.sync 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 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.model.Source
import com.subtracks.data.source.subsonic.SubsonicClient import com.subtracks.data.source.subsonic.SubsonicClient
import com.subtracks.data.source.subsonic.SubsonicSource import com.subtracks.data.source.subsonic.SubsonicSource
@@ -28,12 +24,7 @@ class PruneSyncIntegrationTest {
val removed = File(musicDir, REMOVED_ALBUM_DIR) val removed = File(musicDir, REMOVED_ALBUM_DIR)
check(removed.isDirectory) { "$removed is missing; the harness must prepare the prune library" } check(removed.isDirectory) { "$removed is missing; the harness must prepare the prune library" }
val context = ApplicationProvider.getApplicationContext<Context>() val db = createTestDatabase()
val db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
try { try {
db.sourcesDao().upsertSource( db.sourcesDao().upsertSource(
Source(id = 1, name = "prune", address = baseUrl, isActive = true, createdAt = 0), Source(id = 1, name = "prune", address = baseUrl, isActive = true, createdAt = 0),
@@ -1,11 +1,7 @@
package com.subtracks.data.sync 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 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.model.Source
import com.subtracks.data.source.subsonic.TestServers import com.subtracks.data.source.subsonic.TestServers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -19,12 +15,7 @@ class SyncServiceIntegrationTest {
fun syncMirrorsTheLibraryIntoRoomForEveryServer() = fun syncMirrorsTheLibraryIntoRoomForEveryServer() =
runBlocking { runBlocking {
servers.forEach { server -> servers.forEach { server ->
val context = ApplicationProvider.getApplicationContext<Context>() val db = createTestDatabase()
val db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
try { try {
db.sourcesDao().upsertSource( db.sourcesDao().upsertSource(
Source(id = 1, name = server.name, address = server.baseUrl, isActive = true, createdAt = 0), Source(id = 1, name = server.name, address = server.baseUrl, isActive = true, createdAt = 0),
@@ -2,6 +2,7 @@ package com.subtracks.data.db
import android.content.Context import android.content.Context
import androidx.room3.Room import androidx.room3.Room
import androidx.room3.RoomDatabase
import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import com.subtracks.log.Log import com.subtracks.log.Log
@@ -11,7 +12,9 @@ fun createAndroidDatabase(context: Context): SubtracksDatabase {
.databaseBuilder<SubtracksDatabase>(context, "subtracks.db") .databaseBuilder<SubtracksDatabase>(context, "subtracks.db")
.setDriver(driver) .setDriver(driver)
.addMigrations(*MIGRATIONS.map(::LoggedMigration).toTypedArray()) .addMigrations(*MIGRATIONS.map(::LoggedMigration).toTypedArray())
.addCallback(ArtistSearchTriggerOptimization) .addSubtracksDatabaseCallbacks()
.addCallback(SqliteLifecycleLogging)
.build() .build()
} }
internal fun RoomDatabase.Builder<SubtracksDatabase>.addSubtracksDatabaseCallbacks(): RoomDatabase.Builder<SubtracksDatabase> =
addCallback(ArtistSearchTriggerOptimization).addCallback(SqliteLifecycleLogging)
@@ -1,9 +1,5 @@
package com.subtracks.data.db 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 androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.ArtworkSeed import com.subtracks.data.model.ArtworkSeed
import com.subtracks.data.model.Source import com.subtracks.data.model.Source
@@ -22,12 +18,7 @@ class ArtworkSeedDaoTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
} }
@After @After
@@ -1,12 +1,8 @@
package com.subtracks.data.db package com.subtracks.data.db
import android.content.Context
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room3.Room
import androidx.room3.useReaderConnection import androidx.room3.useReaderConnection
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
@@ -39,12 +35,7 @@ class LibraryDaoTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
} }
@After @After
@@ -895,6 +886,26 @@ class LibraryDaoTest {
assertEquals(listOf("art-2", "art-1"), dao.artistOnce(sourceId, "ar-1")!!.downloadedCoverArts) 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 @Test
fun artistNameUpdatesKeepTheSearchIndexValid() = fun artistNameUpdatesKeepTheSearchIndexValid() =
runTest { runTest {
@@ -1,11 +1,7 @@
package com.subtracks.data.db package com.subtracks.data.db
import android.content.Context
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room3.Room
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist import com.subtracks.data.model.Artist
@@ -27,12 +23,7 @@ class LibraryHomeDaoTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
} }
@After @After
@@ -1,12 +1,9 @@
package com.subtracks.data.db package com.subtracks.data.db
import android.content.Context
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room3.Room
import androidx.room3.immediateTransaction import androidx.room3.immediateTransaction
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist import com.subtracks.data.model.Artist
@@ -40,14 +37,8 @@ class QueryTimingMeasurementTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
val driver = LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it } val driver = LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it }
db = db = createTestDatabase(driver)
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(driver)
.addCallback(ArtistSearchTriggerOptimization)
.build()
} }
@After @After
@@ -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<Context>()
return Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(driver)
.addSubtracksDatabaseCallbacks()
.build()
}
@@ -3,13 +3,12 @@ package com.subtracks.data.repo
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkFetcher import com.subtracks.data.download.ArtworkFetcher
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.download.FakeDownloadEngine
@@ -84,11 +83,7 @@ class DownloadRepositoryTest {
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
resources = context.resources resources = context.resources
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}"))
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
sources = SourceRepository(db, OkHttpClient(), prefs, artwork, scope = scope) sources = SourceRepository(db, OkHttpClient(), prefs, artwork, scope = scope)
@@ -4,12 +4,11 @@ import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import androidx.paging.PagingDataEvent import androidx.paging.PagingDataEvent
import androidx.paging.PagingDataPresenter import androidx.paging.PagingDataPresenter
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.ArtistBio import com.subtracks.data.model.ArtistBio
@@ -63,11 +62,7 @@ class LibraryRepositoryTest {
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
resources = context.resources resources = context.resources
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
sourceRepository = sourceRepository =
SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope) SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope)
@@ -1,13 +1,10 @@
package com.subtracks.data.repo 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 androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.model.PendingActionKind import com.subtracks.data.model.PendingActionKind
import com.subtracks.data.model.Source import com.subtracks.data.model.Source
import com.subtracks.data.source.ServerActionSink import com.subtracks.data.source.ServerActionSink
@@ -82,12 +79,7 @@ class PendingServerActionSinkTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
sourceId = runBlocking { db.sourcesDao().insertSource(Source(name = "server", address = "http://x/", createdAt = 0)) } sourceId = runBlocking { db.sourcesDao().insertSource(Source(name = "server", address = "http://x/", createdAt = 0)) }
delegate = RecordingSink() delegate = RecordingSink()
@@ -1,12 +1,9 @@
package com.subtracks.data.repo package com.subtracks.data.repo
import android.content.Context
import androidx.room3.Room
import androidx.room3.useReaderConnection import androidx.room3.useReaderConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.PlaylistSong import com.subtracks.data.model.PlaylistSong
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
@@ -26,12 +23,7 @@ class QueueKeysetTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
repository = QueueRepository(db) repository = QueueRepository(db)
} }
@@ -1,13 +1,10 @@
package com.subtracks.data.repo package com.subtracks.data.repo
import android.content.Context
import androidx.room3.Room
import androidx.room3.useReaderConnection import androidx.room3.useReaderConnection
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.DownloadStatus import com.subtracks.data.model.DownloadStatus
import com.subtracks.data.model.Playlist import com.subtracks.data.model.Playlist
@@ -33,12 +30,7 @@ class QueueRepositoryTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
repository = QueueRepository(db) repository = QueueRepository(db)
} }
@@ -2,13 +2,12 @@ package com.subtracks.data.repo
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.TEST_TIMEOUT_MS
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.model.artistImageKey import com.subtracks.data.model.artistImageKey
@@ -58,11 +57,7 @@ class SourceRepositoryTest {
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
resources = context.resources resources = context.resources
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}"))
prefs = fakeUserPreferences() prefs = fakeUserPreferences()
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@@ -2,13 +2,12 @@ package com.subtracks.data.sync
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.TEST_TIMEOUT_MS
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.prefs.UserPreferences import com.subtracks.data.prefs.UserPreferences
import com.subtracks.data.prefs.fakeUserPreferences import com.subtracks.data.prefs.fakeUserPreferences
@@ -55,11 +54,7 @@ class SyncManagerTest {
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
resources = context.resources resources = context.resources
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
sourceRepository = sourceRepository =
SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope) SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = scope)
@@ -1,17 +1,15 @@
package com.subtracks.data.sync package com.subtracks.data.sync
import android.content.Context
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room3.Room
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.LoggingSQLiteDriver import com.subtracks.data.db.LoggingSQLiteDriver
import com.subtracks.data.db.SqlExecution import com.subtracks.data.db.SqlExecution
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.artistPages import com.subtracks.data.db.artistPages
import com.subtracks.data.db.artistsPageQuery import com.subtracks.data.db.artistsPageQuery
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist import com.subtracks.data.model.Artist
import com.subtracks.data.model.ArtistRef import com.subtracks.data.model.ArtistRef
@@ -44,12 +42,7 @@ class SyncServiceTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() db = createTestDatabase()
db =
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
} }
@After @After
@@ -1846,13 +1839,8 @@ class SyncServiceTest {
@Test @Test
fun anIdenticalSyncSkipsTheAggregateQueries() = fun anIdenticalSyncSkipsTheAggregateQueries() =
runTest { runTest {
val context = ApplicationProvider.getApplicationContext<Context>()
val executions = Collections.synchronizedList(mutableListOf<SqlExecution>()) val executions = Collections.synchronizedList(mutableListOf<SqlExecution>())
val loggingDb = val loggingDb = createTestDatabase(LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it })
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(LoggingSQLiteDriver(BundledSQLiteDriver()) { executions += it })
.build()
try { try {
loggingDb.sourcesDao().upsertSource( loggingDb.sourcesDao().upsertSource(
Source(id = 1, name = "test", address = "http://localhost", isActive = true, createdAt = 0), Source(id = 1, name = "test", address = "http://localhost", isActive = true, createdAt = 0),
@@ -3,9 +3,7 @@ package com.subtracks.playback
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.room3.Room
import androidx.room3.useWriterConnection import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.R import com.subtracks.R
@@ -14,6 +12,7 @@ import com.subtracks.UiMessage
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.download.FakeDownloadEngine
import com.subtracks.data.model.Album import com.subtracks.data.model.Album
@@ -83,11 +82,7 @@ class PlaybackControllerTest {
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
resources = context.resources resources = context.resources
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
prefs = fakeUserPreferences() prefs = fakeUserPreferences()
networkMode = MutableStateFlow(NetworkMode.Wifi) networkMode = MutableStateFlow(NetworkMode.Wifi)
sources = sources =
@@ -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<Context>()
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()
}
}
@@ -1,40 +1,16 @@
package com.subtracks.ui.downloads package com.subtracks.ui.downloads
import android.content.Context
import androidx.lifecycle.viewModelScope 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 androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil 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.DownloadList
import com.subtracks.data.model.DownloadStatus import com.subtracks.data.model.DownloadStatus
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
import com.subtracks.data.model.SongDownload import com.subtracks.data.model.SongDownload
import com.subtracks.data.prefs.fakeUserPreferences import com.subtracks.ui.ViewModelRepositoryFixture
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.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import okhttp3.OkHttpClient
import org.junit.After import org.junit.After
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
@@ -42,61 +18,23 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File
import java.util.concurrent.Executors
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class DownloadsViewModelTest { class DownloadsViewModelTest {
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private lateinit var fixture: ViewModelRepositoryFixture
private val db get() = fixture.db
private lateinit var db: SubtracksDatabase private val sourceRepository get() = fixture.sourceRepository
private lateinit var sourceRepository: SourceRepository private val downloadRepository get() = fixture.downloadRepository
private lateinit var downloadRepository: DownloadRepository private val controller get() = fixture.controller
private lateinit var controller: PlaybackController
private lateinit var viewModel: DownloadsViewModel private lateinit var viewModel: DownloadsViewModel
private val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val controllerScope = CoroutineScope(SupervisorJob() + dispatcher)
private var sourceId: Long = 0 private var sourceId: Long = 0
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(dispatcher) fixture = ViewModelRepositoryFixture()
val context = ApplicationProvider.getApplicationContext<Context>()
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,
)
viewModel = viewModel =
DownloadsViewModel( DownloadsViewModel(
LibraryRepository( fixture.libraryRepository,
db,
sourceRepository,
NetworkServerActionSink(sourceRepository),
fakeUserPreferences(),
scope = repoScope,
),
downloadRepository, downloadRepository,
controller, controller,
"Unknown artist", "Unknown artist",
@@ -107,10 +45,7 @@ class DownloadsViewModelTest {
@After @After
fun tearDown() { fun tearDown() {
runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() }
cancelAndJoinBlocking(controllerScope, repoScope) fixture.close()
db.close()
Dispatchers.resetMain()
dispatcher.close()
} }
@Test @Test
@@ -5,14 +5,13 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag 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.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.TEST_TIMEOUT_MS
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.download.FakeDownloadEngine
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
@@ -81,11 +80,7 @@ class NowPlayingRouteTest {
private fun setUpState() { private fun setUpState() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
val artworkStore = ArtworkStore(File(context.cacheDir, "art")) val artworkStore = ArtworkStore(File(context.cacheDir, "art"))
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
val sources = SourceRepository(db, OkHttpClient(), prefs, artworkStore, scope = repoScope) val sources = SourceRepository(db, OkHttpClient(), prefs, artworkStore, scope = repoScope)
val queues = QueueRepository(db) val queues = QueueRepository(db)
downloads = downloads =
@@ -2,14 +2,13 @@ package com.subtracks.ui.playback
import android.content.Context import android.content.Context
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.TEST_TIMEOUT_MS import com.subtracks.TEST_TIMEOUT_MS
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.download.FakeDownloadEngine
import com.subtracks.data.model.AlbumSongItem import com.subtracks.data.model.AlbumSongItem
@@ -68,11 +67,7 @@ class QueueViewModelTest {
fun setUp() { fun setUp() {
Dispatchers.setMain(dispatcher) Dispatchers.setMain(dispatcher)
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
sources = sources =
SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = repoScope) SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")), scope = repoScope)
queues = QueueRepository(db) queues = QueueRepository(db)
@@ -1,43 +1,19 @@
package com.subtracks.ui.search package com.subtracks.ui.search
import android.content.Context
import androidx.lifecycle.viewModelScope 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 androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil 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.Album
import com.subtracks.data.model.Artist import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist import com.subtracks.data.model.Playlist
import com.subtracks.data.model.QueueKind import com.subtracks.data.model.QueueKind
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
import com.subtracks.data.prefs.fakeUserPreferences import com.subtracks.ui.ViewModelRepositoryFixture
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.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import okhttp3.OkHttpClient
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@@ -45,70 +21,24 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File
import java.util.concurrent.Executors
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SearchViewModelTest { class SearchViewModelTest {
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private lateinit var fixture: ViewModelRepositoryFixture
private val db get() = fixture.db
private lateinit var db: SubtracksDatabase private val sourceRepository get() = fixture.sourceRepository
private lateinit var sourceRepository: SourceRepository
private lateinit var viewModel: SearchViewModel private lateinit var viewModel: SearchViewModel
private val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val controllerScope = CoroutineScope(SupervisorJob() + dispatcher)
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(dispatcher) fixture = ViewModelRepositoryFixture()
val context = ApplicationProvider.getApplicationContext<Context>() viewModel = SearchViewModel(fixture.libraryRepository, sourceRepository, fixture.controller)
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,
)
} }
@After @After
fun tearDown() { fun tearDown() {
runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() }
cancelAndJoinBlocking(controllerScope, repoScope) fixture.close()
db.close()
Dispatchers.resetMain()
dispatcher.close()
} }
@Test @Test
@@ -2,13 +2,12 @@ package com.subtracks.ui.settings
import android.content.Context import android.content.Context
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.room3.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.awaitUntil import com.subtracks.awaitUntil
import com.subtracks.cancelAndJoinBlocking import com.subtracks.cancelAndJoinBlocking
import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.createTestDatabase
import com.subtracks.data.download.ArtworkStore import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine import com.subtracks.data.download.FakeDownloadEngine
import com.subtracks.data.prefs.fakeUserPreferences import com.subtracks.data.prefs.fakeUserPreferences
@@ -57,11 +56,7 @@ class AddSourceViewModelTest {
fun setUp() { fun setUp() {
Dispatchers.setMain(dispatcher) Dispatchers.setMain(dispatcher)
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
db = db = createTestDatabase()
Room
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
val artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}")) val artwork = ArtworkStore(File(context.cacheDir, "art-${System.nanoTime()}"))
sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artwork, scope = repoScope) sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), artwork, scope = repoScope)
downloadRepository = downloadRepository =