Repository: horizontalsystems/unstoppable-wallet-android
Issue: App crashed while trying fetching many rates at the same time

Crash log:

```
OkHttp: <-- HTTP FAILED: java.net.SocketException: socket failed: EMFILE (Too many open files)
2019-04-10 16:39:17.059 W/System.err:     at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
2019-04-10 16:39:17.060 W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
2019-04-10 16:39:17.060 W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
2019-04-10 16:39:17.060 W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
2019-04-10 16:39:17.062 W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
2019-04-10 16:39:17.062 W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
2019-04-10 16:39:17.062 W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
2019-04-10 16:39:17.062 W/System.err:     at java.lang.Thread.run(Thread.java:764)
2019-04-10 16:39:17.064 W/System.err: Caused by: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. 
2019-04-10 16:39:17.064 W/System.err:     at android.database.CursorWindow.<init>(CursorWindow.java:108)
2019-04-10 16:39:17.067 W/System.err:     at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
2019-04-10 16:39:17.067 W/System.err:     at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:138)
2019-04-10 16:39:17.067 W/System.err:     at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)
2019-04-10 16:39:17.067 W/System.err:     at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:219)
2019-04-10 16:39:17.069 W/System.err:     at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:258)
2019-04-10 16:39:17.069 W/System.err:     at io.horizontalsystems.bankwallet.core.storage.RatesDao_Impl$5.call(RatesDao_Impl.java:198)
2019-04-10 16:39:17.069 W/System.err:     at io.horizontalsystems.bankwallet.core.storage.RatesDao_Impl$5.call(RatesDao_Impl.java:187)
2019-04-10 16:39:17.069 W/System.err:     at android.arch.persistence.room.RxRoom$4.apply(RxRoom.java:111)
```
We create HttpClient for each request.
https://github.com/square/okhttp/issues/1943#issuecomment-151024020

Changed files:
app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt
app/src/main/java/io/horizontalsystems/bankwallet/core/managers/RateManager.kt
app/src/test/java/io/horizontalsystems/bankwallet/core/managers/RateManagerTest.kt
app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NetworkManager.kt
app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt

diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt
index 03ead3055..c67f3343c 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt
@@ -86,8 +86,8 @@ class App : Application() {
         coinsStorage = StorableCoinsRepository(appDatabase)
         localStorage = LocalStorageManager()
 
-        networkManager = NetworkManager()
-        rateManager = RateManager(rateStorage, networkManager, appConfigProvider)
+        networkManager = NetworkManager(appConfigProvider)
+        rateManager = RateManager(rateStorage, networkManager)
         coinManager = CoinManager(appConfigProvider, coinsStorage)
         authManager = AuthManager(secureStorage, localStorage, coinManager, rateManager, ethereumKitManager)
 
diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt
index f5c2fa0f5..dda3952ae 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt
@@ -3,6 +3,7 @@ package io.horizontalsystems.bankwallet.core
 import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
 import android.text.SpannableString
 import com.google.gson.JsonObject
+import io.horizontalsystems.bankwallet.core.managers.ServiceExchangeApi
 import io.horizontalsystems.bankwallet.entities.*
 import io.horizontalsystems.bankwallet.entities.Currency
 import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
@@ -58,9 +59,9 @@ interface IRandomProvider {
 }
 
 interface INetworkManager {
-    fun getLatestRateData(requestUrl: String, currency: String): Maybe<LatestRateData>
-    fun getRateByDay(requestUrl: String, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal>
-    fun getRateByHour(requestUrl: String, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal>
+    fun getLatestRateData(hostType: ServiceExchangeApi.HostType, currency: String): Maybe<LatestRateData>
+    fun getRateByDay(hostType: ServiceExchangeApi.HostType, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal>
+    fun getRateByHour(hostType: ServiceExchangeApi.HostType, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal>
     fun getTransaction(host: String, path: String): Flowable<JsonObject>
     fun ping(host: String, url: String): Flowable<Any>
 }
diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NetworkManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NetworkManager.kt
index f361b0e39..23f066ca3 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NetworkManager.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NetworkManager.kt
@@ -2,6 +2,7 @@ package io.horizontalsystems.bankwallet.core.managers
 
 import com.google.gson.GsonBuilder
 import com.google.gson.JsonObject
+import io.horizontalsystems.bankwallet.core.IAppConfigProvider
 import io.horizontalsystems.bankwallet.core.INetworkManager
 import io.horizontalsystems.bankwallet.entities.LatestRateData
 import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
@@ -19,28 +20,37 @@ import retrofit2.http.Url
 import java.math.BigDecimal
 import java.util.concurrent.TimeUnit
 
-class NetworkManager : INetworkManager {
+class NetworkManager(val appConfigProvider: IAppConfigProvider) : INetworkManager {
 
-    override fun getRateByDay(requestUrl: String, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal> {
-        return ServiceExchangeApi.service(requestUrl)
+    private val mainRateClient: ServiceExchangeApi.IExchangeRate = APIClient
+            .retrofit("https://${appConfigProvider.ipfsMainGateway}/ipns/${appConfigProvider.ipfsId}/")
+            .create(ServiceExchangeApi.IExchangeRate::class.java)
+
+    private val fallbackRateClient: ServiceExchangeApi.IExchangeRate = APIClient
+            .retrofit("https://${appConfigProvider.ipfsFallbackGateway}/ipns/${appConfigProvider.ipfsId}/")
+            .create(ServiceExchangeApi.IExchangeRate::class.java)
+
+    override fun getRateByDay(hostType: ServiceExchangeApi.HostType, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal> {
+        return rateApiClient(hostType)
                 .getRateByDay(coinCode, currency, DateHelper.formatDateInUTC(timestamp, "yyyy/MM/dd"))
                 .map { it.toBigDecimal() }
     }
 
-    override fun getRateByHour(requestUrl: String, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal> {
-        return ServiceExchangeApi.service(requestUrl)
+    override fun getRateByHour(hostType: ServiceExchangeApi.HostType, coinCode: String, currency: String, timestamp: Long): Maybe<BigDecimal> {
+        return rateApiClient(hostType)
                 .getRateByHour(coinCode, currency, DateHelper.formatDateInUTC(timestamp, "yyyy/MM/dd/HH"))
                 .flatMap { minuteRates ->
                     Maybe.just(minuteRates.getValue(DateHelper.formatDateInUTC(timestamp, "mm")).toBigDecimal())
                 }
     }
 
-    override fun getLatestRateData(requestUrl: String, currency: String): Maybe<LatestRateData> {
-        return ServiceExchangeApi.service(requestUrl)
+    override fun getLatestRateData(hostType: ServiceExchangeApi.HostType, currency: String): Maybe<LatestRateData> {
+        return rateApiClient(hostType)
                 .getLatestRate(currency)
                 .map { LatestRateData(it.rates, it.currency, it.timestamp / 1000) }
     }
 
+
     override fun getTransaction(host: String, path: String): Flowable<JsonObject> {
         return ServiceFullTransaction.service(host)
                 .getFullTransaction(path)
@@ -50,13 +60,19 @@ class NetworkManager : INetworkManager {
         return ServicePing.service(host)
                 .ping(url)
     }
+
+    private fun rateApiClient(hostType: ServiceExchangeApi.HostType): ServiceExchangeApi.IExchangeRate {
+        return when(hostType) {
+            ServiceExchangeApi.HostType.MAIN -> mainRateClient
+            else -> fallbackRateClient
+        }
+    }
 }
 
 object ServiceExchangeApi {
 
-    fun service(apiURL: String): IExchangeRate {
-        return APIClient.retrofit(apiURL)
-                .create(IExchangeRate::class.java)
+    enum class HostType {
+        MAIN, FALLBACK
     }
 
     interface IExchangeRate {
diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/RateManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/RateManager.kt
index b6f6a6be6..e23738121 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/RateManager.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/RateManager.kt
@@ -1,6 +1,5 @@
 package io.horizontalsystems.bankwallet.core.managers
 
-import io.horizontalsystems.bankwallet.core.IAppConfigProvider
 import io.horizontalsystems.bankwallet.core.INetworkManager
 import io.horizontalsystems.bankwallet.core.IRateStorage
 import io.horizontalsystems.bankwallet.entities.Rate
@@ -13,10 +12,7 @@ import retrofit2.HttpException
 import java.math.BigDecimal
 import java.net.SocketTimeoutException
 
-class RateManager(private val storage: IRateStorage, private val networkManager: INetworkManager, appConfigProvider: IAppConfigProvider) {
-
-    private val mainUrl = "https://${appConfigProvider.ipfsMainGateway}/ipns/${appConfigProvider.ipfsId}/"
-    private val fallbackUrl = "https://${appConfigProvider.ipfsFallbackGateway}/ipns/${appConfigProvider.ipfsId}/"
+class RateManager(private val storage: IRateStorage, private val networkManager: INetworkManager) {
 
     private var refreshDisposables: CompositeDisposable = CompositeDisposable()
 
@@ -25,18 +21,21 @@ class RateManager(private val storage: IRateStorage, private val networkManager:
 
         //mainUrl sometime returns expired rates, thus currently first request is done to fallbackUrl
         refreshDisposables.add(
-                networkManager.getLatestRateData(fallbackUrl, currencyCode)
-                        .onErrorResumeNext(networkManager.getLatestRateData(mainUrl, currencyCode))
+                networkManager.getLatestRateData(ServiceExchangeApi.HostType.FALLBACK, currencyCode)
+                        .onErrorResumeNext(networkManager.getLatestRateData(ServiceExchangeApi.HostType.MAIN, currencyCode))
                         .subscribeOn(Schedulers.io())
                         .observeOn(Schedulers.io())
-                        .subscribe{ latestRateData ->
+                        .subscribe ({ latestRateData ->
                             coinCodes.forEach { coinCode ->
                                 latestRateData.rates[coinCode]?.toBigDecimalOrNull()?.let {
                                     val rate = Rate(coinCode, latestRateData.currency, it, latestRateData.timestamp, true)
                                     storage.saveLatest(rate)
                                 }
                             }
+                        }, {
+                            //request failed
                         })
+        )
     }
 
     private fun getLatestRateFallbackFlowable(coinCode: CoinCode, currencyCode: String, timestamp: Long): Maybe<BigDecimal> {
@@ -59,17 +58,19 @@ class RateManager(private val storage: IRateStorage, private val networkManager:
         return storage.rateMaybe(coinCode, currencyCode, timestamp)
                 .map { it.value }
                 .switchIfEmpty(
-                        networkManager.getRateByHour(mainUrl, coinCode, currencyCode, timestamp)
+                        networkManager.getRateByHour(ServiceExchangeApi.HostType.MAIN, coinCode, currencyCode, timestamp)
                                 .onErrorResumeNext { t: Throwable ->
                                     when (t) {
-                                        is SocketTimeoutException -> networkManager.getRateByHour(fallbackUrl, coinCode, currencyCode, timestamp)
-                                                .onErrorResumeNext (networkManager.getRateByDay(fallbackUrl, coinCode, currencyCode, timestamp))
-                                        is HttpException -> networkManager.getRateByDay(mainUrl, coinCode, currencyCode, timestamp)
-                                                .onErrorResumeNext (networkManager.getRateByDay(fallbackUrl, coinCode, currencyCode, timestamp))
+                                        is SocketTimeoutException ->
+                                            networkManager.getRateByHour(ServiceExchangeApi.HostType.FALLBACK, coinCode, currencyCode, timestamp)
+                                                    .onErrorResumeNext(networkManager.getRateByDay(ServiceExchangeApi.HostType.FALLBACK, coinCode, currencyCode, timestamp))
+                                        is HttpException ->
+                                            networkManager.getRateByDay(ServiceExchangeApi.HostType.MAIN, coinCode, currencyCode, timestamp)
+                                                    .onErrorResumeNext(networkManager.getRateByDay(ServiceExchangeApi.HostType.FALLBACK, coinCode, currencyCode, timestamp))
                                         else -> throw t
                                     }
                                 }
-                                .doOnSuccess {rateFromNetwork ->
+                                .doOnSuccess { rateFromNetwork ->
                                     storage.save(Rate(coinCode, currencyCode, rateFromNetwork, timestamp, false))
                                 }
                                 .onErrorResumeNext(getLatestRateFallbackFlowable(coinCode, currencyCode, timestamp))
diff --git a/app/src/test/java/io/horizontalsystems/bankwallet/core/managers/RateManagerTest.kt b/app/src/test/java/io/horizontalsystems/bankwallet/core/managers/RateManagerTest.kt
index 511a83354..6f5396262 100644
--- a/app/src/test/java/io/horizontalsystems/bankwallet/core/managers/RateManagerTest.kt
+++ b/app/src/test/java/io/horizontalsystems/bankwallet/core/managers/RateManagerTest.kt
@@ -15,6 +15,8 @@ import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
+import retrofit2.HttpException
+import java.net.SocketTimeoutException
 
 class RateManagerTest {
 
@@ -22,7 +24,10 @@ class RateManagerTest {
 
     private val networkManager = mock(INetworkManager::class.java)
 
+    private val httpException = mock(HttpException::class.java)
     private val storage = mock(IRateStorage::class.java)
+    private val mainHost = ServiceExchangeApi.HostType.MAIN
+    private val fallbackHost = ServiceExchangeApi.HostType.FALLBACK
 
     @Before
     fun setup() {
@@ -37,7 +42,8 @@ class RateManagerTest {
         val currencyCode = "USD"
         val rates = hashMapOf("BTC" to "3981.05", "ETH" to "138.27")
 
-        whenever(networkManager.getLatestRateData(currencyCode)).thenReturn(Flowable.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(mainHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(fallbackHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
 
         rateManager.refreshLatestRates(coins, currencyCode)
 
@@ -52,7 +58,8 @@ class RateManagerTest {
         val currencyCode = "USD"
         val rates = hashMapOf("ETH" to "138.27")
 
-        whenever(networkManager.getLatestRateData(currencyCode)).thenReturn(Flowable.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(mainHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(fallbackHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
 
         rateManager.refreshLatestRates(coins, currencyCode)
 
@@ -66,7 +73,8 @@ class RateManagerTest {
         val currencyCode = "USD"
         val rates = hashMapOf("BTC" to "", "ETH" to "138.27")
 
-        whenever(networkManager.getLatestRateData(currencyCode)).thenReturn(Flowable.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(mainHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
+        whenever(networkManager.getLatestRateData(fallbackHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
 
         rateManager.refreshLatestRates(coins, currencyCode)
 
@@ -79,13 +87,30 @@ class RateManagerTest {
         val coins = listOf("BTC", "ETH")
         val currencyCode = "USD"
 
-        whenever(networkManager.getLatestRateData(currencyCode)).thenReturn(Flowable.error(Exception()))
+        whenever(networkManager.getLatestRateData(fallbackHost, currencyCode)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getLatestRateData(mainHost, currencyCode)).thenReturn(Maybe.error(Exception()))
 
         rateManager.refreshLatestRates(coins, currencyCode)
 
         verify(storage, never()).saveLatest(any())
     }
 
+    @Test
+    fun refreshRates_fromFallbackHost() {
+        val coins = listOf("BTC", "ETH")
+        val currencyCode = "USD"
+        val rates = hashMapOf("BTC" to "3981.05", "ETH" to "138.27")
+
+        whenever(networkManager.getLatestRateData(fallbackHost, currencyCode)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getLatestRateData(mainHost, currencyCode)).thenReturn(Maybe.just(LatestRateData(rates, "USD", 1000L)))
+
+        rateManager.refreshLatestRates(coins, currencyCode)
+
+        verify(storage).saveLatest(Rate(coins[0], currencyCode, 3981.05.toBigDecimal(), 1000, true))
+        verify(storage).saveLatest(Rate(coins[1], currencyCode, 138.27.toBigDecimal(), 1000, true))
+        verify(storage, atMost(2)).saveLatest(any())
+    }
+
     @Test
     fun rateValueObservable() {
 //        val coinCode = "BTC"
@@ -110,12 +135,12 @@ class RateManagerTest {
         val rateValueFromNetwork = 123.2300.toBigDecimal()
 
         whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
-        whenever(networkManager.getRate(coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
 
         rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
                 .test()
 
-        verify(networkManager).getRate(coinCode, currencyCode, timestamp)
+        verify(networkManager).getRateByHour(mainHost, coinCode, currencyCode, timestamp)
         verify(storage).save(Rate(coinCode, currencyCode, rateValueFromNetwork, timestamp, false))
     }
 
@@ -129,7 +154,7 @@ class RateManagerTest {
 
         whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
         whenever(storage.latestRateObservable(coinCode, currencyCode)).thenReturn(Flowable.just(latestRate))
-        whenever(networkManager.getRate(coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
         whenever(latestRate.expired).thenReturn(true)
         whenever(latestRate.value).thenReturn(rateValue)
 
@@ -148,7 +173,7 @@ class RateManagerTest {
 
         whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
         whenever(storage.latestRateObservable(coinCode, currencyCode)).thenReturn(Flowable.just(latestRate))
-        whenever(networkManager.getRate(coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
         whenever(latestRate.expired).thenReturn(false)
         whenever(latestRate.value).thenReturn(rateValue)
 
@@ -165,7 +190,7 @@ class RateManagerTest {
 
         whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
         whenever(storage.latestRateObservable(coinCode, currencyCode)).thenReturn(Flowable.just(latestRate))
-        whenever(networkManager.getRate(coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
         whenever(latestRate.expired).thenReturn(true)
 
         rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
@@ -181,11 +206,70 @@ class RateManagerTest {
 
         whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
         whenever(storage.latestRateObservable(coinCode, currencyCode)).thenReturn(Flowable.empty())
-        whenever(networkManager.getRate(coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
 
         rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
                 .test()
                 .assertNoValues()
     }
 
-}
\\ No newline at end of file
+    @Test
+    fun rateValueObservable_RateFromFallbackHost() {
+        val coinCode = "BTC"
+        val currencyCode = "USD"
+        val timestamp = System.currentTimeMillis()
+        val rateValueFromNetwork = 234.23.toBigDecimal()
+
+        whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(SocketTimeoutException()))
+        whenever(networkManager.getRateByHour(fallbackHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+        whenever(networkManager.getRateByDay(fallbackHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+
+        rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
+                .test()
+
+        verify(networkManager).getRateByHour(mainHost, coinCode, currencyCode, timestamp)
+        verify(networkManager).getRateByHour(fallbackHost, coinCode, currencyCode, timestamp)
+        verify(storage).save(Rate(coinCode, currencyCode, rateValueFromNetwork, timestamp, false))
+    }
+
+    @Test
+    fun rateValueObservable_RateByDayFromFallbackHost() {
+        val coinCode = "BTC"
+        val currencyCode = "USD"
+        val timestamp = System.currentTimeMillis()
+        val rateValueFromNetwork = 234.23.toBigDecimal()
+
+        whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(SocketTimeoutException()))
+        whenever(networkManager.getRateByHour(fallbackHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(Exception()))
+        whenever(networkManager.getRateByDay(fallbackHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+
+        rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
+                .test()
+
+        verify(networkManager).getRateByHour(mainHost, coinCode, currencyCode, timestamp)
+        verify(networkManager).getRateByHour(fallbackHost, coinCode, currencyCode, timestamp)
+        verify(storage).save(Rate(coinCode, currencyCode, rateValueFromNetwork, timestamp, false))
+    }
+
+    @Test
+    fun rateValueObservable_RateByDayFromMainHost() {
+        val coinCode = "BTC"
+        val currencyCode = "USD"
+        val timestamp = System.currentTimeMillis()
+        val rateValueFromNetwork = 234.23.toBigDecimal()
+
+        whenever(storage.rateMaybe(coinCode, currencyCode, timestamp)).thenReturn(Maybe.empty())
+        whenever(networkManager.getRateByHour(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.error(httpException))
+        whenever(networkManager.getRateByDay(mainHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+        whenever(networkManager.getRateByDay(fallbackHost, coinCode, currencyCode, timestamp)).thenReturn(Maybe.just(rateValueFromNetwork))
+
+        rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
+                .test()
+
+        verify(networkManager).getRateByDay(mainHost, coinCode, currencyCode, timestamp)
+        verify(storage).save(Rate(coinCode, currencyCode, rateValueFromNetwork, timestamp, false))
+    }
+
+}