Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-01 22:26:21 +05:00
parent c509046a8b
commit adb8c50ae2
13 changed files with 448 additions and 1 deletions

View file

@ -0,0 +1,50 @@
package com.tangem.data.settings
import androidx.datastore.preferences.core.longPreferencesKey
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.domain.settings.UsedeskTokenTtlManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
/**
* Development implementation of [UsedeskTokenTtlManager].
*
* Reads and writes the Usedesk chat token TTL from [AppPreferencesStore],
* allowing testers to override it via the Tester Menu.
* The preference [kotlinx.coroutines.flow.Flow] is converted to a [StateFlow] on construction,
* so [getTokenTtlMillisSync] can be called from non-suspending contexts.
* Defaults to [UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS] when no value is stored.
*/
internal class DevUsedeskTokenTtlManager(
private val appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
) : UsedeskTokenTtlManager {
private val ttlMillisState: StateFlow<Long> =
appPreferencesStore
.get(key = USEDESK_TOKEN_TTL_MILLIS_KEY, default = UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS)
.stateIn(
scope = CoroutineScope(dispatchers.io + SupervisorJob()),
started = SharingStarted.Eagerly,
initialValue = UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS,
)
override fun getTokenTtlMillis(): StateFlow<Long> = ttlMillisState
override fun getTokenTtlMillisSync(): Long = ttlMillisState.value
override suspend fun setTokenTtlMillis(millis: Long) {
appPreferencesStore.editData { preferences ->
preferences[USEDESK_TOKEN_TTL_MILLIS_KEY] = millis
}
}
private companion object {
val USEDESK_TOKEN_TTL_MILLIS_KEY = longPreferencesKey(name = "usedeskTokenTtlMillis")
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.data.settings
import com.tangem.domain.settings.UsedeskTokenTtlManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Production implementation of [UsedeskTokenTtlManager].
*
* The Usedesk chat token TTL is fixed to [UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS] in production
* a tester override must never leak into release builds.
* [setTokenTtlMillis] is a no-op since the TTL cannot be changed at runtime.
*/
internal class ProdUsedeskTokenTtlManager : UsedeskTokenTtlManager {
private val state: StateFlow<Long> = MutableStateFlow(UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS)
override fun getTokenTtlMillis(): StateFlow<Long> = state
override fun getTokenTtlMillisSync(): Long = UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS
override suspend fun setTokenTtlMillis(millis: Long) = Unit
}

View file

@ -6,11 +6,14 @@ import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultPermissionRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.data.settings.DevHotWalletRestrictionManager
import com.tangem.data.settings.DevUsedeskTokenTtlManager
import com.tangem.data.settings.ProdHotWalletRestrictionManager
import com.tangem.data.settings.ProdUsedeskTokenTtlManager
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.HotWalletRestrictionManager
import com.tangem.domain.settings.UsedeskTokenTtlManager
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.SettingsRepository
@ -72,4 +75,17 @@ internal object SettingsDataModule {
ProdHotWalletRestrictionManager()
}
}
@Provides
@Singleton
fun provideUsedeskTokenTtlManager(
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): UsedeskTokenTtlManager {
return if (BuildConfig.TESTER_MENU_ENABLED) {
DevUsedeskTokenTtlManager(appPreferencesStore, dispatchers)
} else {
ProdUsedeskTokenTtlManager()
}
}
}

View file

@ -0,0 +1,104 @@
package com.tangem.data.settings
import androidx.datastore.preferences.core.Preferences
import com.google.common.truth.Truth.assertThat
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.domain.settings.UsedeskTokenTtlManager
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DevUsedeskTokenTtlManagerTest {
private val appPreferencesStore = mockk<AppPreferencesStore>(relaxed = true)
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
@BeforeEach
fun setup() {
mockkStatic("com.tangem.datasource.local.preferences.utils.PreferencesDataStoreExtKt")
}
@AfterEach
fun tearDown() {
unmockkStatic("com.tangem.datasource.local.preferences.utils.PreferencesDataStoreExtKt")
}
@Test
fun `GIVEN stored ttl WHEN getTokenTtlMillis THEN emits stored value`() = runTest {
// Arrange
val storedTtl = 15L * 60 * 1000
every {
appPreferencesStore.get(key = any<Preferences.Key<Long>>(), default = DEFAULT)
} returns flowOf(storedTtl)
val manager = DevUsedeskTokenTtlManager(appPreferencesStore, dispatchers)
// Act
val emitted = getEmittedValues(manager.getTokenTtlMillis())
// Assert
assertThat(emitted).containsExactly(storedTtl)
}
@Test
fun `GIVEN no stored ttl WHEN getTokenTtlMillisSync THEN returns default`() = runTest {
// Arrange
every {
appPreferencesStore.get(key = any<Preferences.Key<Long>>(), default = DEFAULT)
} returns flowOf(DEFAULT)
val manager = DevUsedeskTokenTtlManager(appPreferencesStore, dispatchers)
// Act & Assert
assertThat(manager.getTokenTtlMillisSync()).isEqualTo(DEFAULT)
}
@Test
fun `GIVEN stored ttl WHEN getTokenTtlMillisSync THEN returns cached value`() = runTest {
// Arrange
val storedTtl = 60L * 60 * 1000
every {
appPreferencesStore.get(key = any<Preferences.Key<Long>>(), default = DEFAULT)
} returns flowOf(storedTtl)
val manager = DevUsedeskTokenTtlManager(appPreferencesStore, dispatchers)
// Act & Assert
assertThat(manager.getTokenTtlMillisSync()).isEqualTo(storedTtl)
}
@Test
fun `WHEN setTokenTtlMillis THEN opens editData transaction`() = runTest {
// Arrange
every {
appPreferencesStore.get(key = any<Preferences.Key<Long>>(), default = DEFAULT)
} returns flowOf(DEFAULT)
coEvery { appPreferencesStore.editData(any()) } returns mockk(relaxed = true)
val manager = DevUsedeskTokenTtlManager(appPreferencesStore, dispatchers)
// Act
manager.setTokenTtlMillis(millis = 15L * 60 * 1000)
// Assert
coVerify { appPreferencesStore.editData(any()) }
}
private companion object {
val DEFAULT = UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.data.settings
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.settings.UsedeskTokenTtlManager
import com.tangem.test.core.getEmittedValues
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ProdUsedeskTokenTtlManagerTest {
private val manager = ProdUsedeskTokenTtlManager()
@Test
fun `WHEN getTokenTtlMillis THEN always emits default`() = runTest {
val emitted = getEmittedValues(manager.getTokenTtlMillis())
assertThat(emitted).containsExactly(UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS)
}
@Test
fun `WHEN getTokenTtlMillisSync THEN returns default`() = runTest {
assertThat(manager.getTokenTtlMillisSync()).isEqualTo(UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS)
}
@Test
fun `GIVEN override attempt WHEN setTokenTtlMillis THEN sync still returns default`() = runTest {
manager.setTokenTtlMillis(millis = 15L * 60 * 1000)
assertThat(manager.getTokenTtlMillisSync()).isEqualTo(UsedeskTokenTtlManager.DEFAULT_TTL_MILLIS)
}
}