Coverage Summary for Class: WebViewNoCacheRetryPolicy (cloud.mindbox.mobile_sdk.inapp.presentation.view)

Class Class, % Method, % Branch, % Line, % Instruction, %
WebViewNoCacheRetryPolicy 100% (1/1) 100% (4/4) 100% (8/8) 100% (11/11) 100% (37/37)


 package cloud.mindbox.mobile_sdk.inapp.presentation.view
 
 import cloud.mindbox.mobile_sdk.inapp.webview.isRecoverableScriptHttpError
 
 /**
  * Decides whether an HTTP error on a page subresource warrants reloading the in-app's
  * content page with the HTTP cache bypassed.
  *
  * Exists for one failure mode: the CDN serves error responses with cacheable headers
  * (`Cache-Control: public, max-age=86400` observed live), so a transient 404 on a
  * bootstrap script gets stored by Chromium and replayed from the cache for a day —
  * the runtime never boots and every show dies on the init timeout. A single reload
  * with `LOAD_NO_CACHE` both recovers this show and overwrites the poisoned entry.
  *
  * Scope guards, in order:
  *  - only statuses >= 400 (redirects and successes are not errors);
  *  - only script resources (a broken image or stats beacon can't stop the runtime
  *    from booting — reloading a page over one would be a regression);
  *  - only before the runtime's `init` (after it the page has proven it can boot;
  *    a late lazy-module error must not tear down a live, visible in-app);
  *  - one retry per page load — if the fresh network answer is the same error, the
  *    existing init-timeout path closes the in-app as before;
  *  - the cache feature gate (the latched `MobileSdkShouldCacheInAppWebView` decision the
  *    WebView was created with — no separate retry toggle by design: with the cache off
  *    nothing can be poisoned and a reload would repeat the exact failed request), checked
  *    LAST and only for the retry decision, so [lastHttpErrorDetail] still enriches
  *    timeout telemetry when the cache is off.
  *
  * Pure logic, JVM-testable (mirrors [WebViewReadyChecker]). Writes are main-confined
  * (WebViewClient callbacks); the fields are @Volatile only because the init-timeout
  * telemetry reads them from the Timer thread.
  */
 internal class WebViewNoCacheRetryPolicy(
     private val isCacheFeatureEnabled: () -> Boolean
 ) {
 
     /** "HTTP <status> for <url>" of the last script error — telemetry context for the init timeout. */
     @Volatile
     var lastHttpErrorDetail: String? = null
         private set
 
     /** True once a retry has been granted; later errors only update [lastHttpErrorDetail]. */
     @Volatile
     var hasRetried: Boolean = false
         private set
 
     /**
      * Records the error and returns true when the caller should perform the one-shot
      * no-cache reload of the content page.
      */
     fun onHttpError(url: String?, statusCode: Int?, hasInitialized: Boolean): Boolean {
         if (!isRecoverableScriptHttpError(url, statusCode)) return false
         lastHttpErrorDetail = "HTTP $statusCode for $url"
         if (hasInitialized) return false
         if (hasRetried) return false
         if (!isCacheFeatureEnabled()) return false
         hasRetried = true
         return true
     }
 }