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()
     }
 }