Coverage Summary for Class: HttpExchangeRateRemoteSource (com.georgeci.moneysurfer.data.remote)

Class Method, % Branch, % Line, % Instruction, %
HttpExchangeRateRemoteSource 100% (2/2) 100% (9/9) 100% (69/69)
HttpExchangeRateRemoteSource$Companion
HttpExchangeRateRemoteSource$fetchLatest$1
Total 100% (2/2) 100% (9/9) 100% (69/69)


 package com.georgeci.moneysurfer.data.remote
 
 import co.touchlab.kermit.Logger
 import com.georgeci.moneysurfer.domain.model.ExchangeRateTable
 import com.georgeci.moneysurfer.domain.primitives.CurrencyCode
 import com.georgeci.moneysurfer.domain.repositories.ExchangeRateRemoteSource
 import kotlinx.coroutines.CancellationException
 import kotlinx.serialization.json.Json
 import org.koin.core.annotation.Single
 
 /**
  * Fetches the latest quotes over plain HTTP and decodes them into the domain table.
  *
  * Returns `null` for every failure mode — offline, timeout, 5xx, garbage payload — because the
  * caller's answer is the same in all of them: keep the cached table. Failures are logged, not
  * raised.
  */
 @Single(binds = [ExchangeRateRemoteSource::class])
 class HttpExchangeRateRemoteSource(
     private val config: ExchangeRateApiConfig,
 ) : ExchangeRateRemoteSource {
 
     private val log = Logger.withTag(TAG)
 
     private val json = Json { ignoreUnknownKeys = true }
 
     @Suppress("TooGenericExceptionCaught")
     override suspend fun fetchLatest(base: CurrencyCode): ExchangeRateTable? {
         val url = config.latestUrl(base)
         return try {
             val body = httpGetText(url)
             json.decodeFromString(ExchangeRateLatestDto.serializer(), body).toDomain(base)
         } catch (cancellation: CancellationException) {
             // Leaving the screen mid-fetch is not a provider failure — let the cancellation
             // unwind instead of logging it and reporting "no rates".
             throw cancellation
         } catch (error: Throwable) {
             log.w(error) { "[fetch] $url failed — cached rates stand" }
             null
         }
     }
 
     private companion object {
         const val TAG = "ExchangeRateRemote"
     }
 }