Coverage Summary for Class: TrackingFailureExtensionKt (cloud.mindbox.mobile_sdk.inapp.domain.extensions)

Class Method, % Branch, % Line, % Instruction, %
TrackingFailureExtensionKt 75% (9/12) 61.5% (32/52) 45.1% (23/51) 45.8% (147/321)
TrackingFailureExtensionKt$executeWithFailureTracking$1 0% (0/1) 0% (0/1) 0% (0/1)
TrackingFailureExtensionKt$parseOperationBody$1 100% (1/1) 50% (11/22) 100% (10/10) 91.8% (78/85)
Total 71.4% (10/14) 58.1% (43/74) 53.2% (33/62) 55.3% (225/407)


 package cloud.mindbox.mobile_sdk.inapp.domain.extensions
 
 import cloud.mindbox.mobile_sdk.getErrorResponseBodyData
 import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppFailureTracker
 import cloud.mindbox.mobile_sdk.inapp.domain.models.TargetingData
 import cloud.mindbox.mobile_sdk.models.operation.request.OperationBodyRequest
 import cloud.mindbox.mobile_sdk.utils.loggingRunCatching
 import com.android.volley.NoConnectionError
 import com.android.volley.TimeoutError
 import com.android.volley.VolleyError
 import com.bumptech.glide.load.HttpException
 import com.bumptech.glide.load.engine.GlideException
 import com.google.gson.Gson
 import java.net.ConnectException
 import java.net.SocketTimeoutException
 import java.net.UnknownHostException
 import cloud.mindbox.mobile_sdk.logger.mindboxLogE
 import cloud.mindbox.mobile_sdk.models.operation.request.FailureReason
 import kotlinx.coroutines.TimeoutCancellationException
 
 internal fun VolleyError.isTimeoutError(): Boolean {
     return this is TimeoutError || cause is SocketTimeoutException
 }
 
 internal fun VolleyError.isNoConnectionError(): Boolean {
     return this is NoConnectionError
 }
 
 internal fun VolleyError.isServerError(): Boolean {
     val statusCode = networkResponse?.statusCode ?: return false
     return statusCode in 500..599
 }
 
 internal fun Throwable.shouldTrackTargetingError(): Boolean {
     val volleyError = cause.asVolleyError() ?: return false
     return volleyError.isServerError() && !volleyError.isTimeoutError() && !volleyError.isNoConnectionError()
 }
 
 internal fun Throwable.shouldTrackImageDownloadError(): Boolean {
     if (cause is TimeoutCancellationException) return false
     val glideException = cause as? GlideException ?: return true
     return glideException.rootCauses.none { rootCause ->
         when {
             rootCause is SocketTimeoutException || rootCause.cause is SocketTimeoutException -> true
             rootCause is HttpException && rootCause.statusCode <= 0 ->
                 rootCause.cause is UnknownHostException || rootCause.cause is ConnectException
             else -> false
         }
     }
 }
 
 internal fun Throwable?.asVolleyError(): VolleyError? = this as? VolleyError
 
 internal fun Throwable.getVolleyErrorDetails(): String {
     val volleyError = this.asVolleyError() ?: return "volleyError = null"
     val statusCode = volleyError.networkResponse?.statusCode ?: "timeout error"
     val networkTimeMs = volleyError.networkTimeMs
     val body = volleyError.getErrorResponseBodyData()
     return "statusCode=$statusCode, networkTimeMs=$networkTimeMs, body=$body"
 }
 
 internal fun TargetingData.getProductFromTargetingData(): Pair<String, String>? {
     if (this !is TargetingData.OperationBody) return null
     return parseOperationBody(this.operationBody)
 }
 
 private fun parseOperationBody(operationBody: String?): Pair<String, String>? =
     loggingRunCatching(null) {
         val body = Gson().fromJson(operationBody, OperationBodyRequest::class.java) ?: return@loggingRunCatching null
         body.viewProductRequest
             ?.product
             ?.ids
             ?.ids
             ?.entries
             ?.firstOrNull()
             ?.takeIf { entry ->
                 entry.value?.isNotBlank() == true
             }
             ?.let { entry -> entry.key to entry.value!! }
     }
 
 internal fun InAppFailureTracker.sendPresentationFailure(
     inAppId: String,
     errorDescription: String,
     throwable: Throwable? = null
 ) {
     val errorDetails = when {
         throwable != null -> "$errorDescription: ${throwable.message ?: "Unknown error"}"
         else -> errorDescription
     }
     mindboxLogE(errorDetails)
     sendFailure(
         inAppId = inAppId,
         failureReason = FailureReason.PRESENTATION_FAILED,
         errorDetails = errorDetails
     )
 }
 
 internal fun InAppFailureTracker.sendFailureWithContext(
     inAppId: String,
     failureReason: FailureReason,
     errorDescription: String,
     throwable: Throwable? = null
 ) {
     val errorDetails = when {
         throwable != null -> "$errorDescription: ${throwable.message ?: "Unknown error"}"
         else -> errorDescription
     }
     mindboxLogE(errorDetails)
     sendFailure(
         inAppId = inAppId,
         failureReason = failureReason,
         errorDetails = errorDetails
     )
 }
 
 internal inline fun <T> InAppFailureTracker.executeWithFailureTracking(
     inAppId: String,
     failureReason: FailureReason,
     errorDescription: String,
     crossinline onFailure: () -> Unit = {},
     block: () -> T
 ): Result<T> {
     return runCatching(block).onFailure { throwable ->
         sendFailureWithContext(
             inAppId = inAppId,
             failureReason = failureReason,
             errorDescription = errorDescription,
             throwable = throwable
         )
         onFailure()
     }
 }