music: add cache repository test

Add tests for the cache repository and cache data structure.
This commit is contained in:
Alexander Capehart 2023-08-18 19:24:13 -06:00
parent 881fb58648
commit ad672ed919
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 271 additions and 42 deletions

View file

@ -149,6 +149,8 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:1.13.7"
testImplementation "org.robolectric:robolectric:4.9"
testImplementation 'androidx.test:core-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2023 Auxio Project
* StubTest.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class StubTest {
// TODO: Make tests
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.oxycblt.auxio.debug", appContext.packageName)
}
}

View file

@ -63,9 +63,9 @@ data class CachedSong(
/** @see RawSong */
var durationMs: Long,
/** @see RawSong.replayGainTrackAdjustment */
val replayGainTrackAdjustment: Float?,
val replayGainTrackAdjustment: Float? = null,
/** @see RawSong.replayGainAlbumAdjustment */
val replayGainAlbumAdjustment: Float?,
val replayGainAlbumAdjustment: Float? = null,
/** @see RawSong.musicBrainzId */
var musicBrainzId: String? = null,
/** @see RawSong.name */

View file

@ -43,6 +43,7 @@ interface Separators {
const val SLASH = '/'
const val PLUS = '+'
const val AND = '&'
/**
* Creates a new instance from the **current state** of the given [MusicSettings]'s
* user-defined separator configuration.

View file

@ -0,0 +1,266 @@
/*
* Copyright (c) 2023 Auxio Project
* CacheRepositoryTest.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.cache
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerifyAll
import io.mockk.coVerifySequence
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import java.lang.IllegalStateException
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date
class CacheRepositoryTest {
@Test
fun cache_read_noInvalidate() {
val dao =
mockk<CachedSongsDao> {
coEvery { readSongs() }.returnsMany(listOf(CACHED_SONG_A, CACHED_SONG_B))
}
val cacheRepository = CacheRepositoryImpl(dao)
val cache = requireNotNull(runBlocking { cacheRepository.readCache() })
coVerifyAll { dao.readSongs() }
assertFalse(cache.invalidated)
val songA = RawSong(mediaStoreId = 0, dateAdded = 1, dateModified = 2)
assertTrue(cache.populate(songA))
assertEquals(RAW_SONG_A, songA)
assertFalse(cache.invalidated)
val songB = RawSong(mediaStoreId = 9, dateAdded = 10, dateModified = 11)
assertTrue(cache.populate(songB))
assertEquals(RAW_SONG_B, songB)
assertFalse(cache.invalidated)
}
@Test
fun cache_read_invalidate() {
val dao =
mockk<CachedSongsDao> {
coEvery { readSongs() }.returnsMany(listOf(CACHED_SONG_A, CACHED_SONG_B))
}
val cacheRepository = CacheRepositoryImpl(dao)
val cache = requireNotNull(runBlocking { cacheRepository.readCache() })
coVerifyAll { dao.readSongs() }
assertFalse(cache.invalidated)
val nullStart = RawSong(mediaStoreId = 0, dateAdded = 0, dateModified = 0)
val nullEnd = RawSong(mediaStoreId = 0, dateAdded = 0, dateModified = 0)
assertFalse(cache.populate(nullStart))
assertEquals(nullStart, nullEnd)
assertTrue(cache.invalidated)
val songB = RawSong(mediaStoreId = 9, dateAdded = 10, dateModified = 11)
assertTrue(cache.populate(songB))
assertEquals(RAW_SONG_B, songB)
assertTrue(cache.invalidated)
}
@Test
fun cache_read_crashes() {
val dao = mockk<CachedSongsDao> { coEvery { readSongs() } throws IllegalStateException() }
val cacheRepository = CacheRepositoryImpl(dao)
assertEquals(null, runBlocking { cacheRepository.readCache() })
coVerifyAll { dao.readSongs() }
}
@Test
fun cache_write() {
var currentlyStoredSongs = listOf<CachedSong>()
val insertSongsArg = slot<List<CachedSong>>()
val dao =
mockk<CachedSongsDao> {
coEvery { nukeSongs() } answers { currentlyStoredSongs = listOf() }
coEvery { insertSongs(capture(insertSongsArg)) } answers
{
currentlyStoredSongs = insertSongsArg.captured
}
}
val cacheRepository = CacheRepositoryImpl(dao)
val rawSongs = listOf(RAW_SONG_A, RAW_SONG_B)
runBlocking { cacheRepository.writeCache(rawSongs) }
val cachedSongs = listOf(CACHED_SONG_A, CACHED_SONG_B)
coVerifySequence {
dao.nukeSongs()
dao.insertSongs(cachedSongs)
}
assertEquals(cachedSongs, currentlyStoredSongs)
}
@Test
fun cache_write_nukeCrashes() {
val dao =
mockk<CachedSongsDao> {
coEvery { nukeSongs() } throws IllegalStateException()
coEvery { insertSongs(listOf()) } just Runs
}
val cacheRepository = CacheRepositoryImpl(dao)
runBlocking { cacheRepository.writeCache(listOf()) }
coVerifyAll { dao.nukeSongs() }
}
@Test
fun cache_write_insertCrashes() {
val dao =
mockk<CachedSongsDao> {
coEvery { nukeSongs() } just Runs
coEvery { insertSongs(listOf()) } throws IllegalStateException()
}
val cacheRepository = CacheRepositoryImpl(dao)
runBlocking { cacheRepository.writeCache(listOf()) }
coVerifySequence {
dao.nukeSongs()
dao.insertSongs(listOf())
}
}
private companion object {
val CACHED_SONG_A =
CachedSong(
mediaStoreId = 0,
dateAdded = 1,
dateModified = 2,
size = 3,
durationMs = 4,
replayGainTrackAdjustment = 5.5f,
replayGainAlbumAdjustment = 6.6f,
musicBrainzId = "Song MBID A",
name = "Song Name A",
sortName = "Song Sort Name A",
track = 7,
disc = 8,
subtitle = "Subtitle A",
date = Date.from("2020-10-10"),
albumMusicBrainzId = "Album MBID A",
albumName = "Album Name A",
albumSortName = "Album Sort Name A",
releaseTypes = listOf("Release Type A"),
artistMusicBrainzIds = listOf("Artist MBID A"),
artistNames = listOf("Artist Name A"),
artistSortNames = listOf("Artist Sort Name A"),
albumArtistMusicBrainzIds = listOf("Album Artist MBID A"),
albumArtistNames = listOf("Album Artist Name A"),
albumArtistSortNames = listOf("Album Artist Sort Name A"),
genreNames = listOf("Genre Name A"),
)
val RAW_SONG_A =
RawSong(
mediaStoreId = 0,
dateAdded = 1,
dateModified = 2,
size = 3,
durationMs = 4,
replayGainTrackAdjustment = 5.5f,
replayGainAlbumAdjustment = 6.6f,
musicBrainzId = "Song MBID A",
name = "Song Name A",
sortName = "Song Sort Name A",
track = 7,
disc = 8,
subtitle = "Subtitle A",
date = Date.from("2020-10-10"),
albumMusicBrainzId = "Album MBID A",
albumName = "Album Name A",
albumSortName = "Album Sort Name A",
releaseTypes = listOf("Release Type A"),
artistMusicBrainzIds = listOf("Artist MBID A"),
artistNames = listOf("Artist Name A"),
artistSortNames = listOf("Artist Sort Name A"),
albumArtistMusicBrainzIds = listOf("Album Artist MBID A"),
albumArtistNames = listOf("Album Artist Name A"),
albumArtistSortNames = listOf("Album Artist Sort Name A"),
genreNames = listOf("Genre Name A"),
)
val CACHED_SONG_B =
CachedSong(
mediaStoreId = 9,
dateAdded = 10,
dateModified = 11,
size = 12,
durationMs = 13,
replayGainTrackAdjustment = 14.14f,
replayGainAlbumAdjustment = 15.15f,
musicBrainzId = "Song MBID B",
name = "Song Name B",
sortName = "Song Sort Name B",
track = 16,
disc = 17,
subtitle = "Subtitle B",
date = Date.from("2021-11-11"),
albumMusicBrainzId = "Album MBID B",
albumName = "Album Name B",
albumSortName = "Album Sort Name B",
releaseTypes = listOf("Release Type B"),
artistMusicBrainzIds = listOf("Artist MBID B"),
artistNames = listOf("Artist Name B"),
artistSortNames = listOf("Artist Sort Name B"),
albumArtistMusicBrainzIds = listOf("Album Artist MBID B"),
albumArtistNames = listOf("Album Artist Name B"),
albumArtistSortNames = listOf("Album Artist Sort Name B"),
genreNames = listOf("Genre Name B"),
)
val RAW_SONG_B =
RawSong(
mediaStoreId = 9,
dateAdded = 10,
dateModified = 11,
size = 12,
durationMs = 13,
replayGainTrackAdjustment = 14.14f,
replayGainAlbumAdjustment = 15.15f,
musicBrainzId = "Song MBID B",
name = "Song Name B",
sortName = "Song Sort Name B",
track = 16,
disc = 17,
subtitle = "Subtitle B",
date = Date.from("2021-11-11"),
albumMusicBrainzId = "Album MBID B",
albumName = "Album Name B",
albumSortName = "Album Sort Name B",
releaseTypes = listOf("Release Type B"),
artistMusicBrainzIds = listOf("Artist MBID B"),
artistNames = listOf("Artist Name B"),
artistSortNames = listOf("Artist Sort Name B"),
albumArtistMusicBrainzIds = listOf("Album Artist MBID B"),
albumArtistNames = listOf("Album Artist Name B"),
albumArtistSortNames = listOf("Album Artist Sort Name B"),
genreNames = listOf("Genre Name B"),
)
}
}