Skip to content

Commit

Permalink
Add test
Browse files Browse the repository at this point in the history
  • Loading branch information
stoyicker committed Jun 24, 2024
1 parent 5bd9c52 commit 803dc73
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 2 deletions.
20 changes: 18 additions & 2 deletions auth/src/main/kotlin/com/tidal/sdk/auth/TokenRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import com.tidal.sdk.common.TidalMessage
import com.tidal.sdk.common.UnexpectedError
import com.tidal.sdk.common.d
import com.tidal.sdk.common.logger
import java.net.HttpURLConnection
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.runBlocking
import java.net.HttpURLConnection
import java.util.concurrent.atomic.AtomicInteger

internal class TokenRepository(
private val authConfig: AuthConfig,
Expand All @@ -34,6 +35,12 @@ internal class TokenRepository(
private val bus: MutableSharedFlow<TidalMessage>,
) {

var getCredentialsCalls = AtomicInteger(0)
var refreshesBranchSkipOrOuterSkip = AtomicInteger(0)
var refreshesBranchToken = AtomicInteger(0)
var refreshesBranchSecret = AtomicInteger(0)
var refreshesBranchLogout = AtomicInteger(0)
var upgrades = AtomicInteger(0)

private fun needsCredentialsUpgrade(): Boolean {
val storedCredentials = getLatestTokens()?.credentials
Expand All @@ -60,6 +67,7 @@ internal class TokenRepository(

@Suppress("UnusedPrivateMember")
suspend fun getCredentials(apiErrorSubStatus: String?): AuthResult<Credentials> {
getCredentialsCalls.incrementAndGet()
logger.d { "Received subStatus: $apiErrorSubStatus" }
val latestTokens = getLatestTokens()
if ((latestTokens?.credentials?.isExpired(timeProvider) != false) ||
Expand All @@ -85,6 +93,7 @@ internal class TokenRepository(
}
}
}
refreshesBranchSkipOrOuterSkip.incrementAndGet()
return success(latestTokens.credentials)
}

Expand All @@ -95,26 +104,33 @@ internal class TokenRepository(
storedTokens?.credentials?.isExpired(timeProvider) == false &&
apiErrorSubStatus.shouldRefreshToken().not() -> {
logger.d { "Refresh skipped" }
refreshesBranchSkipOrOuterSkip.incrementAndGet()
success(storedTokens.credentials)
}
// if a refreshToken is available, we'll use it
storedTokens?.refreshToken != null -> {
val refreshToken = storedTokens.refreshToken
logger.d { "Refreshing via refresh token" }
refreshesBranchToken.incrementAndGet()
runBlocking { refreshCredentials { refreshUserCredentials(refreshToken) } }
}

// if nothing is stored, we will try and refresh using a client secret
authConfig.clientSecret != null -> {
logger.d { "Refreshing via client secret" }
refreshesBranchSecret.incrementAndGet()
runBlocking { refreshCredentials { getClientCredentials(authConfig.clientSecret) } }
}

// as a last resort we return a token-less Credentials, we're logged out
else -> logout()
else -> {
refreshesBranchLogout.incrementAndGet()
logout()
}
}

private suspend fun upgradeTokens(storedTokens: Tokens): AuthResult<Tokens> {
upgrades.incrementAndGet()
val response = retryWithPolicy(upgradeBackoffPolicy) {
with(storedTokens) {
tokenService.upgradeToken(
Expand Down
44 changes: 44 additions & 0 deletions auth/src/test/kotlin/com/tidal/sdk/auth/TokenRepositoryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.tidal.sdk.auth.login.FakeTokensStore
import com.tidal.sdk.auth.model.ApiErrorSubStatus
import com.tidal.sdk.auth.model.AuthConfig
import com.tidal.sdk.auth.model.AuthResult
import com.tidal.sdk.auth.model.Credentials
import com.tidal.sdk.auth.model.CredentialsUpdatedMessage
import com.tidal.sdk.auth.model.Tokens
import com.tidal.sdk.auth.util.RetryPolicy
Expand All @@ -18,14 +19,19 @@ import com.tidal.sdk.util.TEST_CLIENT_ID
import com.tidal.sdk.util.TEST_CLIENT_UNIQUE_KEY
import com.tidal.sdk.util.TEST_TIME_PROVIDER
import com.tidal.sdk.util.makeCredentials
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import okio.IOException
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class TokenRepositoryTest {

Expand Down Expand Up @@ -755,4 +761,42 @@ class TokenRepositoryTest {
"No calls to the backend should have been made"
}
}

@Test
fun `getCredentials called from many threads`() = runTest {
val credentials = makeCredentials(
userId = "valid",
isExpired = true,
)
val tokens = Tokens(
credentials,
"refreshToken",
)
createTokenRepository(
FakeTokenService(),
FakeTokensStore(authConfig.credentialsKey, tokens),
)
val deferreds = mutableSetOf<Deferred<AuthResult<Credentials>>>()
val threads = mutableSetOf<Thread>()
repeat(1_000) {
deferreds.add(
async { tokenRepository.getCredentials(null) },
)
threads.add(
Thread {
runBlocking { tokenRepository.getCredentials(null) }
}.apply {
start()
},
)
}
deferreds.awaitAll()
threads.onEach { it.join() }
assertEquals(2_000, tokenRepository.getCredentialsCalls.get())
assertEquals(1_999, tokenRepository.refreshesBranchSkipOrOuterSkip.get())
assertEquals(1, tokenRepository.refreshesBranchToken.get())
assertEquals(0, tokenRepository.refreshesBranchSecret.get())
assertEquals(0, tokenRepository.refreshesBranchLogout.get())
assertEquals(0, tokenRepository.upgrades.get())
}
}

0 comments on commit 803dc73

Please sign in to comment.