From 18238d0c3dcbad952de4f134d5e8299a986d92c6 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Tue, 8 Sep 2026 17:13:07 -0300 Subject: [PATCH] test(android): prove rotating refresh is serialized --- .../data/SessionRefreshGateTest.kt | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 android-app/app/src/test/java/com/korexlabs/dhinspeccion/data/SessionRefreshGateTest.kt diff --git a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/data/SessionRefreshGateTest.kt b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/data/SessionRefreshGateTest.kt new file mode 100644 index 0000000..071d2d4 --- /dev/null +++ b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/data/SessionRefreshGateTest.kt @@ -0,0 +1,62 @@ +package com.korexlabs.dhinspeccion.data + +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class SessionRefreshGateTest { + @Test + fun simultaneous401sConsumeTheRotatingRefreshTokenOnlyOnce() = runBlocking { + val gate = SessionRefreshGate() + val calls = AtomicInteger(0) + var stored: StoredSession? = StoredSession( + userId = "u-1", + username = "inspector", + displayName = "Inspector", + accessToken = "access-old", + refreshToken = "refresh-old", + ) + + fun response() = MobileSessionResponse( + user = MobileUser(id = "u-1", username = "inspector"), + accessToken = "access-new", + refreshToken = "refresh-new", + accessExpiresInSeconds = 900, + ) + + val results = listOf(1, 2).map { + async(Dispatchers.Default) { + gate.refreshIfNeeded( + previousRefreshToken = "refresh-old", + load = { stored }, + save = { refreshed -> + StoredSession( + userId = refreshed.user.id, + username = refreshed.user.username, + displayName = refreshed.user.username, + accessToken = refreshed.accessToken, + refreshToken = refreshed.refreshToken, + ).also { stored = it } + }, + clear = { stored = null }, + refresh = { + calls.incrementAndGet() + delay(40) + response() + }, + ) + } + }.awaitAll() + + assertEquals(1, calls.get()) + assertEquals(listOf("refresh-new", "refresh-new"), results.map { it.refreshToken }) + assertNotNull(stored) + assertEquals("access-new", stored?.accessToken) + } +}