address review: probe the active source so media falls back too

An existing token-auth source with an already-synced library would fail playback before
any API call ran: stream and cover-art URLs are built by uri() and fetched by
ExoPlayer/Coil, outside stream(). Probe the active source once when it uses token auth so
the 41 flips and persists before any URL is built.

Also base the retry on the flag the request actually used (concurrent requests no longer
lose their retry), report the fallback from ping() so Add Source flips the toggle instead
of reporting success and saving token auth on, and make the callback non-null.
This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent 59706f28c1
commit 9c97f64381
5 changed files with 98 additions and 23 deletions
@@ -7,11 +7,18 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.prefs.fakeUserPreferences
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -20,6 +27,7 @@ import org.junit.runner.RunWith
class SourceRepositoryTest {
private lateinit var db: SubtracksDatabase
private lateinit var repository: SourceRepository
private val messages = ArrayList<String>()
@Before
fun setUp() {
@@ -29,7 +37,7 @@ class SourceRepositoryTest {
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
.setDriver(BundledSQLiteDriver())
.build()
repository = SourceRepository(db, OkHttpClient(), fakeUserPreferences())
repository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), showMessage = { messages += it })
}
@After
@@ -57,4 +65,49 @@ class SourceRepositoryTest {
.address,
)
}
@Test
fun aTokenAuthSourceIsProbedAndSwitchedToPassword() =
runBlocking {
withServer { server ->
server.enqueue(MockResponse().setBody(failed(41)))
server.enqueue(MockResponse().setBody(OK))
repository.addSource("nav", server.url("/").toString(), "u", "s3cret", true)
val config = withTimeout(5_000) { repository.activeConfig().first { it != null && !it.useTokenAuth } }
assertFalse(config!!.useTokenAuth)
withTimeout(5_000) { while (messages.isEmpty()) delay(10) }
assertEquals(1, messages.size)
}
}
@Test
fun pingReportsWhetherItFellBackToThePassword() =
runBlocking {
withServer { server ->
server.enqueue(MockResponse().setBody(failed(41)))
server.enqueue(MockResponse().setBody(OK))
server.enqueue(MockResponse().setBody(OK))
assertTrue(repository.ping(server.url("/").toString(), "u", "s3cret", true).getOrThrow())
assertFalse(repository.ping(server.url("/").toString(), "u", "s3cret", false).getOrThrow())
}
}
private suspend fun withServer(block: suspend (MockWebServer) -> Unit) {
val server = MockWebServer()
server.start()
try {
block(server)
} finally {
server.shutdown()
}
}
private fun failed(code: Int) = "<subsonic-response status=\"failed\"><error code=\"$code\" message=\"nope\"/></subsonic-response>"
private companion object {
const val OK = "<subsonic-response status=\"ok\" version=\"1.16.1\"/>"
}
}
@@ -27,7 +27,7 @@ class SubsonicClientTest {
private fun client(
tokenAuth: Boolean,
onTokenAuthUnsupported: (() -> Unit)? = null,
onTokenAuthUnsupported: () -> Unit = {},
) = SubsonicClient(
baseUrl = server.url("/"),
username = "guest",