From 6ef411fdb2cb1c9f71e2a08f0da5a00f19a12270 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 07:52:55 +0000 Subject: [PATCH] Refactor TokenPlugin to use createClientPlugin I refactored TokenPlugin to use the createClientPlugin factory function from Ktor. This change addresses a runtime IR linking error encountered on iOS. The TokenPlugin's functionality remains the same: - It adds a token to request headers. - Configuration includes setting a token header name and a token provider. I also added unit tests for TokenPlugin to verify: - Correct token insertion into headers. - Handling of null tokens from the provider. - Validation of plugin configuration for null tokenHeaderName or tokenProvider. --- .../moko/network/plugins/TokenPlugin.kt | 45 +++++----- .../moko/network/plugins/TokenPluginTest.kt | 85 +++++++++++++++++++ 2 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 network/src/commonTest/kotlin/dev/icerock/moko/network/plugins/TokenPluginTest.kt diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt index 9639369..ab831dc 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt @@ -4,40 +4,35 @@ package dev.icerock.moko.network.plugins -import io.ktor.client.HttpClient -import io.ktor.client.plugins.HttpClientPlugin -import io.ktor.client.request.HttpRequestPipeline +import io.ktor.client.plugins.createClientPlugin import io.ktor.client.request.header -import io.ktor.util.AttributeKey -class TokenPlugin private constructor( - private val tokenHeaderName: String, - private val tokenProvider: TokenProvider +val TokenPlugin = createClientPlugin( + name = "TokenPlugin", + createConfiguration = ::TokenPluginConfig ) { + val tokenHeaderName = pluginConfig.tokenHeaderName + val tokenProvider = pluginConfig.tokenProvider - class Config { - var tokenHeaderName: String? = null - var tokenProvider: TokenProvider? = null - fun build() = TokenPlugin( - tokenHeaderName ?: throw IllegalArgumentException("HeaderName should be contain"), - tokenProvider ?: throw IllegalArgumentException("TokenProvider should be contain") - ) + if (tokenHeaderName == null) { + throw IllegalArgumentException("HeaderName should be contain") } - companion object Plugin : HttpClientPlugin { - override val key = AttributeKey("TokenPlugin") - - override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() + if (tokenProvider == null) { + throw IllegalArgumentException("TokenProvider should be contain") + } - override fun install(plugin: TokenPlugin, scope: HttpClient) { - scope.requestPipeline.intercept(HttpRequestPipeline.State) { - plugin.tokenProvider.getToken()?.apply { - context.headers.remove(plugin.tokenHeaderName) - context.header(plugin.tokenHeaderName, this) - } - } + onRequest { request, _ -> + tokenProvider.getToken()?.apply { + request.headers.remove(tokenHeaderName) + request.headers.append(tokenHeaderName, this) } } +} + +class TokenPluginConfig { + var tokenHeaderName: String? = null + var tokenProvider: TokenProvider? = null fun interface TokenProvider { fun getToken(): String? diff --git a/network/src/commonTest/kotlin/dev/icerock/moko/network/plugins/TokenPluginTest.kt b/network/src/commonTest/kotlin/dev/icerock/moko/network/plugins/TokenPluginTest.kt new file mode 100644 index 0000000..536e31d --- /dev/null +++ b/network/src/commonTest/kotlin/dev/icerock/moko/network/plugins/TokenPluginTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2024 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.icerock.moko.network.plugins + +import dev.icerock.moko.network.plugins.TokenPluginConfig.TokenProvider +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.request.get +import io.ktor.http.HttpHeaders +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class TokenPluginTest { + + private val mockToken = "mockTokenValue" + private val mockHeaderName = "X-Auth-Token" + + @Test + fun `token successfully added`() = runTest { + val mockEngine = MockEngine { request -> + assertTrue(request.headers.contains(mockHeaderName, mockToken)) + respond("") + } + + val client = HttpClient(mockEngine) { + install(TokenPlugin) { + this.tokenHeaderName = mockHeaderName + this.tokenProvider = TokenProvider { mockToken } + } + } + + client.get("http://localhost/test") + } + + @Test + fun `null token`() = runTest { + val mockEngine = MockEngine { request -> + assertFalse(request.headers.contains(mockHeaderName)) + respond("") + } + + val client = HttpClient(mockEngine) { + install(TokenPlugin) { + this.tokenHeaderName = mockHeaderName + this.tokenProvider = TokenProvider { null } + } + } + + client.get("http://localhost/test") + } + + @Test + fun `config validation - null tokenHeaderName`() = runTest { + val exception = assertFailsWith { + HttpClient(MockEngine { respond("") }) { + install(TokenPlugin) { + this.tokenHeaderName = null + this.tokenProvider = TokenProvider { mockToken } + } + } + } + assertEquals("HeaderName should be contain", exception.message) + } + + @Test + fun `config validation - null tokenProvider`() = runTest { + val exception = assertFailsWith { + HttpClient(MockEngine { respond("") }) { + install(TokenPlugin) { + this.tokenHeaderName = mockHeaderName + this.tokenProvider = null + } + } + } + assertEquals("TokenProvider should be contain", exception.message) + } +}