Skip to content

Commit b5c09a1

Browse files
committed
Add tests
1 parent c4b5689 commit b5c09a1

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthImpl.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,8 @@ internal class AuthImpl(
436436
Auth.logger.d { "Session is under the threshold date. Refreshing session..." }
437437
tryImportingSession(
438438
{ handleExpiredSession(session, config.alwaysAutoRefresh) },
439-
{ importSession(session) }
439+
{ importSession(session) },
440+
{ updateStatusIfExpired(session, it) }
440441
)
441442
} else {
442443
if (config.autoSaveToStorage) {
@@ -451,7 +452,8 @@ internal class AuthImpl(
451452
launch {
452453
tryImportingSession(
453454
{ handleExpiredSession(session) },
454-
{ importSession(session, source = source) }
455+
{ importSession(session, source = source) },
456+
{ updateStatusIfExpired(session, it) }
455457
)
456458
}
457459
}
@@ -462,14 +464,15 @@ internal class AuthImpl(
462464
@Suppress("MagicNumber")
463465
private suspend fun tryImportingSession(
464466
importRefreshedSession: suspend () -> Unit,
465-
retry: suspend () -> Unit
467+
retry: suspend () -> Unit,
468+
updateStatus: suspend (RefreshFailureCause) -> Unit
466469
) {
467470
try {
468471
importRefreshedSession()
469472
} catch (e: RestException) {
470473
if (e.statusCode in 500..599) {
471474
Auth.logger.e(e) { "Couldn't refresh session due to an internal server error. Retrying in ${config.retryDelay} (Status code ${e.statusCode})..." }
472-
updateStatusIfExpired(RefreshFailureCause.InternalServerError(e))
475+
updateStatus(RefreshFailureCause.InternalServerError(e))
473476
delay(config.retryDelay)
474477
retry()
475478
} else {
@@ -479,15 +482,14 @@ internal class AuthImpl(
479482
} catch (e: Exception) {
480483
coroutineContext.ensureActive()
481484
Auth.logger.e(e) { "Couldn't reach Supabase. Either the address doesn't exist or the network might not be on. Retrying in ${config.retryDelay}..." }
482-
updateStatusIfExpired(RefreshFailureCause.NetworkError(e))
485+
updateStatus(RefreshFailureCause.NetworkError(e))
483486
delay(config.retryDelay)
484487
retry()
485488
}
486489
}
487490

488-
private fun updateStatusIfExpired(reason: RefreshFailureCause) {
489-
val currentSession = currentSessionOrNull() ?: error("No session found")
490-
if (currentSession.expiresAt <= Clock.System.now()) {
491+
private fun updateStatusIfExpired(session: UserSession, reason: RefreshFailureCause) {
492+
if (session.expiresAt <= Clock.System.now()) {
491493
Auth.logger.d { "Session expired while trying to refresh the session. Updating status..." }
492494
setSessionStatus(SessionStatus.RefreshFailure(reason))
493495
}

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/status/RefreshFailureCause.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.github.jan.supabase.exceptions.RestException
55
/**
66
* Represents the cause of a refresh error
77
*/
8+
//Note: Move this interface when removing the deprecated property from [SessionStatus.RefreshFailure]
89
sealed interface RefreshFailureCause {
910

1011
/**

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/status/SessionStatus.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ sealed interface SessionStatus {
2323

2424
/**
2525
* This status means the session expired and [Auth] is trying to refresh it
26+
* @param cause The cause of the error. This property will be removed in a future version. Use the new AuthEvent.RefreshFailure(cause) event to diagnose failures.
2627
*/
2728
data class RefreshFailure(
2829
@Deprecated("This property will be removed in a future version. Use the new AuthEvent.RefreshFailure(cause) event to diagnose failures.")

Auth/src/commonTest/kotlin/AuthTest.kt

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1+
import app.cash.turbine.test
12
import io.github.jan.supabase.SupabaseClientBuilder
23
import io.github.jan.supabase.auth.Auth
34
import io.github.jan.supabase.auth.MemorySessionManager
45
import io.github.jan.supabase.auth.auth
6+
import io.github.jan.supabase.auth.event.AuthEvent
57
import io.github.jan.supabase.auth.minimalSettings
68
import io.github.jan.supabase.auth.providers.Github
9+
import io.github.jan.supabase.auth.status.RefreshFailureCause
10+
import io.github.jan.supabase.auth.status.SessionSource
711
import io.github.jan.supabase.auth.status.SessionStatus
812
import io.github.jan.supabase.auth.user.Identity
913
import io.github.jan.supabase.auth.user.UserInfo
1014
import io.github.jan.supabase.auth.user.UserSession
15+
import io.github.jan.supabase.logging.LogLevel
1116
import io.github.jan.supabase.testing.createMockedSupabaseClient
1217
import io.github.jan.supabase.testing.pathAfterVersion
1318
import io.github.jan.supabase.testing.respondJson
19+
import io.ktor.client.engine.mock.respondError
20+
import io.ktor.http.HttpStatusCode
1421
import io.ktor.http.Url
22+
import kotlinx.coroutines.launch
1523
import kotlinx.coroutines.test.runTest
1624
import kotlinx.serialization.json.buildJsonObject
1725
import kotlin.test.Test
1826
import kotlin.test.assertEquals
1927
import kotlin.test.assertFailsWith
2028
import kotlin.test.assertIs
2129
import kotlin.test.assertNull
30+
import kotlin.time.Duration.Companion.seconds
2231

2332
class AuthTest {
2433

@@ -105,6 +114,7 @@ class AuthTest {
105114
val session = userSession(expiresIn = 0)
106115
client.auth.importSession(session)
107116
assertIs<SessionStatus.Authenticated>(client.auth.sessionStatus.value)
117+
assertIs<SessionSource.Refresh>((client.auth.sessionStatus.value as SessionStatus.Authenticated).source)
108118
assertEquals(newSession.expiresIn, client.auth.currentSessionOrNull()?.expiresIn)
109119
}
110120
}
@@ -131,10 +141,96 @@ class AuthTest {
131141
assertIs<SessionStatus.Authenticated>(client.auth.sessionStatus.value)
132142
assertEquals(session.expiresIn, client.auth.currentSessionOrNull()?.expiresIn) //The session shouldn't be refreshed automatically as alwaysAutoRefresh is false
133143
client.auth.startAutoRefreshForCurrentSession()
144+
assertIs<SessionSource.Refresh>((client.auth.sessionStatus.value as SessionStatus.Authenticated).source)
134145
assertEquals(newSession.expiresIn, client.auth.currentSessionOrNull()?.expiresIn)
135146
}
136147
}
137148

149+
@Test
150+
fun testAutoRefreshFailureNetworkValidSession() {
151+
runTest {
152+
val newSession = userSession()
153+
val client = createMockedSupabaseClient(configuration = {
154+
install(Auth) {
155+
minimalSettings(
156+
alwaysAutoRefresh = true
157+
)
158+
}
159+
defaultLogLevel = LogLevel.DEBUG
160+
}) {
161+
throw IllegalStateException("Some random error") //everything except RestException are handled as network errors
162+
}
163+
client.auth.awaitInitialization()
164+
assertIs<SessionStatus.NotAuthenticated>(client.auth.sessionStatus.value)
165+
val session = userSession(expiresIn = 1)
166+
client.auth.importSession(session)
167+
assertIs<SessionStatus.Authenticated>(client.auth.sessionStatus.value) // since the session is still valid, the status should be authenticated
168+
client.auth.events.test(timeout = 2.seconds) { //event should be emitted regardless of the session status
169+
val event = awaitItem()
170+
assertIs<AuthEvent.RefreshFailure>(event)
171+
assertIs<RefreshFailureCause.NetworkError>(event.cause)
172+
}
173+
}
174+
}
175+
176+
@Test
177+
fun testAutoRefreshFailureServerErrorValidSession() {
178+
runTest {
179+
val newSession = userSession()
180+
val client = createMockedSupabaseClient(configuration = {
181+
install(Auth) {
182+
minimalSettings(
183+
alwaysAutoRefresh = true
184+
)
185+
}
186+
defaultLogLevel = LogLevel.DEBUG
187+
}) {
188+
respondError(HttpStatusCode.InternalServerError, "{}")
189+
}
190+
assertIs<SessionStatus.NotAuthenticated>(client.auth.sessionStatus.value)
191+
val session = userSession(expiresIn = 1)
192+
client.auth.importSession(session)
193+
assertIs<SessionStatus.Authenticated>(client.auth.sessionStatus.value) // since the session is still valid, the status should be authenticated
194+
client.auth.events.test(timeout = 2.seconds) { //event should be emitted regardless of the session status
195+
val event = awaitItem()
196+
assertIs<AuthEvent.RefreshFailure>(event)
197+
assertIs<RefreshFailureCause.InternalServerError>(event.cause)
198+
}
199+
}
200+
}
201+
202+
@Test
203+
fun testAutoRefreshFailureInvalidSession() {
204+
runTest {
205+
var first = true
206+
val newSession = userSession()
207+
val client = createMockedSupabaseClient(configuration = {
208+
install(Auth) {
209+
minimalSettings(
210+
alwaysAutoRefresh = false
211+
)
212+
}
213+
defaultLogLevel = LogLevel.DEBUG
214+
}) {
215+
if(first) {
216+
first = false
217+
respondError(HttpStatusCode.InternalServerError, "{}")
218+
} else respondJson(newSession)
219+
}
220+
client.auth.awaitInitialization()
221+
assertIs<SessionStatus.NotAuthenticated>(client.auth.sessionStatus.value)
222+
val session = userSession(expiresIn = 0)
223+
launch {
224+
client.auth.events.test(timeout = 1.seconds) { //event should be emitted regardless of the session status
225+
val event = awaitItem()
226+
assertIs<SessionStatus.RefreshFailure>(client.auth.sessionStatus.value) // session expired and should be in refresh failure state
227+
assertIs<AuthEvent.RefreshFailure>(event)
228+
}
229+
}
230+
client.auth.importSession(session, autoRefresh = true)
231+
}
232+
}
233+
138234
@Test
139235
fun testGetOAuthUrl() {
140236
runTest {

0 commit comments

Comments
 (0)