library: fix credited fallback and stale marker writes

Restore the both-tables-empty fallback in the credit recompute, skip
redundant marker writes, append albumless tracks after album tracks, and make
the paged artist query observe sources. Add tests for an interrupted sync and
the partial-relation migration guard.
This commit is contained in:
austinried
2026-10-18 07:00:04 +00:00
parent 9d9550be48
commit a41f77d907
6 changed files with 85 additions and 7 deletions
@@ -246,6 +246,32 @@ class MigrationsTest {
}
}
@Test
fun theCreditedBackfillKeepsArtistsVisibleWhileRelationsAreIncomplete() =
runTest {
driver.open(":memory:").use { migrated ->
applySchema(migrated, 31)
migrated.execSQL(
"INSERT INTO sources (id, name, address, isActive, createdAt) VALUES (1, 's', 'http://x', 1, 0)",
)
migrated.execSQL("INSERT INTO artists (sourceId, id, name, albumCount) VALUES (1, 'a1', 'Alpha', 1)")
migrated.execSQL("INSERT INTO artists (sourceId, id, name, albumCount) VALUES (1, 'combo', 'Alpha & Beta', 1)")
migrated.execSQL(
"INSERT INTO albums (sourceId, id, artistId, name, albumArtist, created, songCount) " +
"VALUES (1, 'al1', 'a1', 'Album', 'Alpha', 0, 0)",
)
migrated.execSQL(
"INSERT INTO album_artists (sourceId, albumId, position, artistId, name) " +
"VALUES (1, 'al1', 0, 'a1', 'Alpha')",
)
MIGRATION_31_32.migrate(migrated)
assertEquals(1L, long(migrated, "SELECT credited FROM artists WHERE id = 'a1'"))
assertEquals(1L, long(migrated, "SELECT credited FROM artists WHERE id = 'combo'"))
}
}
private fun text(
connection: SQLiteConnection,
sql: String,
@@ -1167,6 +1167,44 @@ class SyncServiceTest {
)
}
@Test
fun anInterruptedSyncLeavesTheCreditMarkerStale() =
runTest {
insertSource()
val source =
FakeMusicSource(
artists = listOf(artist("a1", "Alpha"), artist("combo", "Alpha & Beta")),
albums = listOf(album("al1")),
songs = listOf(song("s1")),
songsFailure = java.io.IOException("boom"),
)
runCatching { SyncService(db, source).sync() }
assertEquals(true, db.sourcesDao().creditsStale(1))
assertEquals(
listOf("a1", "combo"),
db
.libraryDao()
.artistsByName(1, 0, "")
.allRows()
.map { it.id },
)
source.songsFailure = null
SyncService(db, source).sync()
assertEquals(false, db.sourcesDao().creditsStale(1))
assertEquals(
listOf("a1"),
db
.libraryDao()
.artistsByName(1, 0, "")
.allRows()
.map { it.id },
)
}
@Test
fun aStaleCreditMarkerForcesARecomputeOnTheNextSync() =
runTest {
@@ -1513,6 +1551,7 @@ private class FakeMusicSource(
var songs: List<Song> = emptyList(),
var playlists: List<Playlist> = emptyList(),
var playlistSongs: List<PlaylistSong> = emptyList(),
var songsFailure: Throwable? = null,
private val batchSize: Int = 1000,
) : MusicSource {
val requestedPlaylistIds = mutableListOf<List<String>>()
@@ -1535,7 +1574,12 @@ private class FakeMusicSource(
override fun albums(): Flow<List<Album>> = batches(albums)
override fun songs(): Flow<List<Song>> = batches(songs)
override fun songs(): Flow<List<Song>> =
if (songsFailure == null) {
batches(songs)
} else {
flow { throw songsFailure!! }
}
override fun playlists(): Flow<List<Playlist>> = batches(playlists)