perf(#1170): remove runBlocking network IO from token refresh and WS setup (#1185)

This commit is contained in:
M57 authored and GitHub committed 2026-09-17 22:00:25 +03:00
1 parent c2cb08a563
commit 7951acc6eb
5 files changed
+258 -170

No files matched your search

@@ -5,6 +5,7 @@ import io.mockk.every
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
@@ -32,50 +33,68 @@ class DashboardSessionTokenRefresherTest {
}
@Test
fun fetchExtractsInjectedDashboardToken() {
fun fetchExtractsInjectedDashboardToken() =
runBlocking {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "new-token";</script>"""),
)
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertEquals("new-token", token)
assertEquals("/", server.takeRequest().path)
}
@Test
fun fetchReturnsNullWhenDashboardDoesNotInjectToken() =
runBlocking {
server.enqueue(MockResponse().setResponseCode(200).setBody("<html></html>"))
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun fetchReturnsNullForFailedResponse() =
runBlocking {
server.enqueue(MockResponse().setResponseCode(500))
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun fetchReturnsNullWhenTokenIsBlank() =
runBlocking {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "";</script>"""),
)
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun fetchSyncExtractsInjectedDashboardToken() {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "new-token";</script>"""),
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "sync-token";</script>"""),
)
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
val token = DashboardSessionTokenRefresher.fetchSync(server.url("/").toString(), OkHttpClient())
assertEquals("new-token", token)
assertEquals("sync-token", token)
assertEquals("/", server.takeRequest().path)
}
@Test
fun fetchReturnsNullWhenDashboardDoesNotInjectToken() {
server.enqueue(MockResponse().setResponseCode(200).setBody("<html></html>"))
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun fetchReturnsNullForFailedResponse() {
server.enqueue(MockResponse().setResponseCode(500))
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun fetchReturnsNullWhenTokenIsBlank() {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "";</script>"""),
)
val token = DashboardSessionTokenRefresher.fetch(server.url("/").toString(), OkHttpClient())
assertNull(token)
}
@Test
fun refreshUpdatesAuthManagerOnSuccess() {
mockkObject(AuthManager)
@@ -94,6 +113,25 @@ class DashboardSessionTokenRefresherTest {
verify { AuthManager.setToken("mocked-token") }
}
@Test
fun refreshAsyncUpdatesAuthManagerOnSuccess() =
runBlocking {
mockkObject(AuthManager)
every { AuthManager.baseUrl() } returns server.url("/").toString()
every { AuthManager.setToken(any()) } returns Unit
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""<script>window.__HERMES_SESSION_TOKEN__ = "async-token";</script>"""),
)
val token = DashboardSessionTokenRefresher.refreshAsync()
assertEquals("async-token", token)
verify { AuthManager.setToken("async-token") }
}
@Test
fun refreshReturnsNullOnException() {
mockkObject(AuthManager)
@@ -10,6 +10,7 @@ import com.m57.hermescontrol.data.remote.OkHttpProvider
import com.m57.hermescontrol.data.remote.ServerEndpoint
import com.m57.hermescontrol.data.remote.buildFakePersistentCookieJar
import com.m57.hermescontrol.data.session.ActiveSessionHolder
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
@@ -84,6 +85,10 @@ class HermesWsClientTest {
// the WS stack can build its OkHttp clients without app context.
CookieManager.setJarForTest(buildFakePersistentCookieJar())
mockkObject(DashboardSessionTokenRefresher)
coEvery { DashboardSessionTokenRefresher.refreshAsync() } returns null
every { DashboardSessionTokenRefresher.refresh() } returns null
// Reset state
val connectedField = HermesWsClient::class.java.getDeclaredField("connected")
connectedField.isAccessible = true
@@ -850,12 +855,11 @@ class HermesWsClientTest {
@Test
fun testDisconnectThenConnectSupersedesBlockedSocketOpen() =
runBlocking {
mockkObject(DashboardSessionTokenRefresher)
every { AuthManager.baseUrl() } returns mockWebServer.url("/").toString()
val refreshStarted = CountDownLatch(1)
val releaseRefresh = CountDownLatch(1)
val refreshCalls = AtomicInteger(0)
every { DashboardSessionTokenRefresher.fetch(any(), any()) } answers {
coEvery { DashboardSessionTokenRefresher.refreshAsync() } answers {
if (refreshCalls.getAndIncrement() == 0) {
refreshStarted.countDown()
releaseRefresh.await(5, TimeUnit.SECONDS)
@@ -1181,10 +1185,8 @@ class HermesWsClientTest {
// 5-6s latch windows routinely overshoot under parallel load).
HermesWsClient.setReconnectBackoffForTest(0L)
// The reconnect path refreshes the WS token over the network before
// opening the socket. Stub it so no real HTTP call sits inside the
// opening the socket. Stubbed in setUp() so no real HTTP call sits inside the
// test's timing window (CI network latency was the dominant flake).
mockkObject(DashboardSessionTokenRefresher)
every { DashboardSessionTokenRefresher.fetch(any(), any()) } returns null
var serverSocket1: WebSocket? = null
var serverSocket2: WebSocket? = null
@@ -1523,6 +1525,11 @@ class HermesWsClientTest {
HermesWsClient.connect()
runBlocking {
withTimeout(5000) {
HermesWsClient.connectionStatus.first { it == ConnectionStatus.DISCONNECTED }
}
}
assertEquals(ConnectionStatus.DISCONNECTED, HermesWsClient.connectionStatus.value)
ticketServer.shutdown()
}
@@ -1549,6 +1556,11 @@ class HermesWsClientTest {
HermesWsClient.connect()
runBlocking {
withTimeout(5000) {
HermesWsClient.connectionStatus.first { it == ConnectionStatus.RECONNECTING }
}
}
assertEquals(ConnectionStatus.RECONNECTING, HermesWsClient.connectionStatus.value)
}