Coverage Summary for Class: MobileConfigRepositoryImpl (cloud.mindbox.mobile_sdk.inapp.data.repositories)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| MobileConfigRepositoryImpl |
63.2%
(12/19)
|
42.5%
(62/146)
|
64.9%
(109/168)
|
67.1%
(899/1340)
|
| MobileConfigRepositoryImpl$1 |
100%
(1/1)
|
|
100%
(2/2)
|
100%
(19/19)
|
| MobileConfigRepositoryImpl$1$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(12/12)
|
| MobileConfigRepositoryImpl$fetchMobileConfig$1 |
|
| MobileConfigRepositoryImpl$getABTests$1 |
|
| MobileConfigRepositoryImpl$getInAppsSection$1 |
|
| MobileConfigRepositoryImpl$getMonitoringSection$1 |
|
| MobileConfigRepositoryImpl$getOperations$1 |
|
| MobileConfigRepositoryImpl$processConfigUpdate$1 |
|
| Total |
66.7%
(14/21)
|
42.5%
(62/146)
|
65.5%
(112/171)
|
67.8%
(930/1371)
|
package cloud.mindbox.mobile_sdk.inapp.data.repositories
import androidx.annotation.VisibleForTesting
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.getOrNull
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.data.managers.data_filler.DataManager
import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper
import cloud.mindbox.mobile_sdk.inapp.data.validators.*
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.FeatureToggleManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.MobileConfigSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.validators.InAppValidator
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppConfig
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppTtlData
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.logger.mindboxLogE
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.managers.DbManager
import cloud.mindbox.mobile_sdk.managers.GatewayManager
import cloud.mindbox.mobile_sdk.managers.InappSettingsManager
import cloud.mindbox.mobile_sdk.managers.MobileConfigSettingsManager
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.models.TimeSpan
import cloud.mindbox.mobile_sdk.models.operation.response.*
import cloud.mindbox.mobile_sdk.monitoring.data.validators.MonitoringValidator
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
internal class MobileConfigRepositoryImpl(
private val inAppMapper: InAppMapper,
private val mobileConfigSerializationManager: MobileConfigSerializationManager,
private val inAppValidator: InAppValidator,
private val monitoringValidator: MonitoringValidator,
private val abTestValidator: ABTestValidator,
private val operationNameValidator: OperationNameValidator,
private val operationValidator: OperationValidator,
private val gatewayManager: GatewayManager,
private val defaultDataManager: DataManager,
private val ttlParametersValidator: TtlParametersValidator,
private val inAppConfigTtlValidator: InAppConfigTtlValidator,
private val sessionStorageManager: SessionStorageManager,
private val timeSpanPositiveValidator: TimeSpanPositiveValidator,
private val mobileConfigSettingsManager: MobileConfigSettingsManager,
private val integerPositiveValidator: IntegerPositiveValidator,
private val inappSettingsManager: InappSettingsManager,
private val featureToggleManager: FeatureToggleManager
) : MobileConfigRepository {
private val mutex = Mutex()
private val configState = MutableStateFlow<InAppConfig?>(null)
init {
Mindbox.mindboxScope.launch {
MindboxPreferences.inAppConfigFlow
.collectLatest { configString ->
processConfigUpdate(configString)
}
}
}
override suspend fun fetchMobileConfig() {
val configuration = DbManager.listenConfigurations().first()
MindboxPreferences.inAppConfig = gatewayManager.fetchMobileConfig(
configuration = configuration
)
MindboxPreferences.inAppConfigUpdatedTime = System.currentTimeMillis()
}
private suspend fun processConfigUpdate(inAppConfigString: String) {
mutex.withLock {
this@MobileConfigRepositoryImpl.mindboxLogD(
message = "CachedConfig : $inAppConfigString"
)
val configBlank =
mobileConfigSerializationManager.deserializeToConfigDtoBlank(inAppConfigString)
val filteredConfig = InAppConfigResponse(
inApps = runCatching { getInApps(configBlank) }.getOrNull {
mindboxLogW("Unable to get inApps $it")
},
monitoring = runCatching { getMonitoring(configBlank) }.getOrNull {
mindboxLogW("Unable to get logs $it")
},
settings = runCatching { getSettings(configBlank) }.getOrNull {
mindboxLogW("Unable to get settings $it")
},
abtests = runCatching { getABTests(configBlank) }.getOrNull {
mindboxLogW("Unable to get abtests $it")
},
)
val updatedInAppConfig = inAppMapper.mapToInAppConfig(filteredConfig)
mobileConfigSettingsManager.saveSessionTime(config = filteredConfig)
mobileConfigSettingsManager.checkPushTokenKeepalive(config = filteredConfig)
inappSettingsManager.applySettings(config = filteredConfig)
featureToggleManager.applyToggles(config = filteredConfig)
persistOperationsDomain(filteredConfig)
configState.value = updatedInAppConfig
mindboxLogI(message = "Providing config: $updatedInAppConfig")
}
}
override suspend fun getMonitoringSection() = getConfig().monitoring
override suspend fun getOperations() = getConfig().operations
override suspend fun getInAppsSection() = getConfig().inApps
override suspend fun getABTests() = getConfig().abtests
override fun resetCurrentConfig() {
configState.value = null
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getInApps(configBlank: InAppConfigResponseBlank?): List<InAppDto>? {
val isValidConfig = inAppConfigTtlValidator.isValid(
InAppTtlData(
ttl = getInAppTtl(configBlank),
shouldCheckInAppTtl = sessionStorageManager.configFetchingError
)
)
if (!isValidConfig) return emptyList()
return configBlank?.inApps
?.filter { inAppDtoBlank ->
inAppValidator.validateInAppVersion(inAppDtoBlank)
}
?.map { inAppDtoBlank ->
inAppMapper.mapToInAppDto(
inAppDtoBlank = inAppDtoBlank,
delayTime = inAppDtoBlank.delayTime
?.let { TimeSpan.fromStringOrNull(it) }
?.takeIf { timeSpanPositiveValidator.isValid(it) },
formDto = defaultDataManager.fillFormData(
mobileConfigSerializationManager.deserializeToInAppFormDto(
inAppDtoBlank.form
)
),
frequencyDto = defaultDataManager.fillFrequencyData(mobileConfigSerializationManager.deserializeToFrequencyDto(inAppDtoBlank.frequency)),
targetingDto = mobileConfigSerializationManager.deserializeToInAppTargetingDto(
inAppDtoBlank.targeting
)
)
}?.filter { inAppDto ->
inAppValidator.validateInApp(inAppDto)
}
}
private fun getMonitoring(configBlank: InAppConfigResponseBlank?): List<LogRequestDto>? =
configBlank?.monitoring?.logs?.filter { logRequestDtoBlank ->
monitoringValidator.validateLogRequestDtoBlank(logRequestDtoBlank)
}?.map { logRequestDtoBlank ->
inAppMapper.mapToLogRequestDto(logRequestDtoBlank)
}
private fun getSettings(configBlank: InAppConfigResponseBlank?): SettingsDto {
val operations = configBlank?.settings?.operations
?.filter { (name, operation) ->
operationNameValidator.isValid(name) &&
operationValidator.isValid(operation)
}?.map { (name, operation) ->
name!! to OperationDto(operation!!.systemName)
}?.toMap()
?: emptyMap()
val ttl = runCatching { getInAppTtl(configBlank) }.getOrElse {
mindboxLogW("Unable to get InAppTtl settings $it")
null
}
val slidingExpiration = runCatching { getConfigSession(configBlank) }.getOrNull {
mindboxLogW("Unable to get slidingExpiration settings $it")
}
val inappSettings = runCatching { getInappSettings(configBlank) }.getOrNull {
mindboxLogW("Unable to get inapp settings $it")
}
val featureToggles = runCatching { getFeatureToggles(configBlank) }.getOrNull {
mindboxLogW("Unable to get featureToggles settings $it")
}
val baseAddresses = runCatching { getBaseAddresses(configBlank) }.getOrNull {
mindboxLogW("Unable to get baseAddresses settings $it")
}
return SettingsDto(operations, ttl, slidingExpiration, inappSettings, featureToggles, baseAddresses)
}
private fun getBaseAddresses(configBlank: InAppConfigResponseBlank?): BaseAddressesDto? {
val operations = configBlank?.settings?.baseAddresses?.operations
?.trim()
?.takeIf { it.isNotBlank() }
?: return null
return BaseAddressesDto(operations = operations)
}
private fun persistOperationsDomain(config: InAppConfigResponse) {
val raw = config.settings?.baseAddresses?.operations
val stored = MindboxPreferences.operationsDomainFromConfig
when (val action = operationsDomainConfigPolicyAction(raw, stored)) {
is OperationsDomainConfigPolicyAction.Save -> {
mindboxLogD("operationsDomain: saving '${action.value}'")
MindboxPreferences.operationsDomainFromConfig = action.value
}
is OperationsDomainConfigPolicyAction.Clear -> {
mindboxLogD("operationsDomain: clearing stored value '$stored'")
MindboxPreferences.operationsDomainFromConfig = null
}
is OperationsDomainConfigPolicyAction.Keep -> {
mindboxLogD("operationsDomain: keeping existing value '$stored'")
}
}
}
private fun getInAppTtl(configBlank: InAppConfigResponseBlank?): TtlDto? =
try {
configBlank?.settings?.ttl?.takeIf { ttlParametersDtoBlank ->
ttlParametersValidator.isValid(ttlParametersDtoBlank)
}?.let { ttlParametersDtoBlank ->
inAppMapper.mapToTtlDto(ttlParametersDtoBlank)
}
} catch (e: java.lang.Exception) {
mindboxLogE("Error parse inapps ttl", e)
null
}
private fun getConfigSession(configBlank: InAppConfigResponseBlank?): SlidingExpirationDto? =
try {
SlidingExpirationDto(
config = configBlank?.settings?.slidingExpiration?.config
?.takeIf { slidingExpirationConfig ->
timeSpanPositiveValidator.isValid(slidingExpirationConfig)
}
?.toMillis()
?.let { Milliseconds(it) },
pushTokenKeepalive = configBlank?.settings?.slidingExpiration?.pushTokenKeepalive
?.takeIf { pushTokenKeepaliveDtoBlank ->
timeSpanPositiveValidator.isValid(pushTokenKeepaliveDtoBlank)
}
?.toMillis()
?.let { Milliseconds(it) }
)
} catch (e: Exception) {
mindboxLogE("Error parse config session time", e)
null
}
private fun getInappSettings(configBlank: InAppConfigResponseBlank?): InappSettingsDto? =
try {
InappSettingsDto(
maxInappsPerSession = configBlank?.settings?.inappSettings?.maxInappsPerSession
?.takeIf { maxInappsPerSession ->
integerPositiveValidator.isValid(maxInappsPerSession)
},
maxInappsPerDay = configBlank?.settings?.inappSettings?.maxInappsPerDay
?.takeIf { maxInappsPerDay ->
integerPositiveValidator.isValid(maxInappsPerDay)
},
minIntervalBetweenShows = configBlank?.settings?.inappSettings?.minIntervalBetweenShows
?.takeIf { minIntervalBetweenShows ->
timeSpanPositiveValidator.isValid(minIntervalBetweenShows)
}
?.toMillis()
?.let { Milliseconds(it) }
)
} catch (e: Exception) {
mindboxLogE("Error parse config inapp settings", e)
null
}
private fun getFeatureToggles(configBlank: InAppConfigResponseBlank?): Map<String, Boolean?>? =
configBlank?.settings?.featureToggles?.toggles
private fun getABTests(configBlank: InAppConfigResponseBlank?): List<ABTestDto> {
return try {
if (configBlank?.abtests == null) return listOf()
return configBlank.abtests.takeIf { abtests ->
abtests.all { abTestValidator.isValid(it) }
} ?: listOf()
} catch (e: Exception) {
mindboxLogE("Error parse abtests", e)
listOf()
}
}
private suspend fun getConfig(): InAppConfig {
return configState
.filterNotNull()
.first()
}
}