Removed StoreRoom
This commit is contained in:
parent
4b27b64d0e
commit
e0cf396a10
12 changed files with 2 additions and 664 deletions
|
@ -33,7 +33,6 @@ android {
|
|||
exclude 'META-INF/rxjava.properties'
|
||||
}
|
||||
}
|
||||
def room_version = "1.1.0" // or, for latest rc, use "1.1.1-rc1"
|
||||
|
||||
dependencies {
|
||||
|
||||
|
@ -63,19 +62,9 @@ dependencies {
|
|||
implementation libraries.rxAndroid2
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
|
||||
implementation "android.arch.persistence.room:runtime:$room_version"
|
||||
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
|
||||
kapt "android.arch.persistence.room:compiler:$room_version"
|
||||
|
||||
// androidTestImplementation "android.arch.persistence.room:testing:$room_version"
|
||||
// optional - RxJava support for Room
|
||||
implementation "android.arch.persistence.room:rxjava2:$room_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0"
|
||||
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
|
||||
|
||||
|
||||
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
@ -27,35 +27,15 @@ class SampleApp : Application() {
|
|||
lateinit var persistedStore: Store<RedditData, BarCode>
|
||||
val moshi = Moshi.Builder().build()
|
||||
lateinit var persister: Persister<BufferedSource, BarCode>
|
||||
// lateinit var sampleRoomStore:SampleRoomStore
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
appContext = this
|
||||
// sampleRoomStore = SampleRoomStore(this)
|
||||
initPersister();
|
||||
nonPersistedStore = provideRedditStore();
|
||||
persistedStore = providePersistedRedditStore();
|
||||
//RoomSample()
|
||||
}
|
||||
|
||||
/*private fun RoomSample() {
|
||||
var foo = sampleRoomStore.store.get("")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace }
|
||||
|
||||
foo = Observable.timer(15, TimeUnit.SECONDS)
|
||||
.subscribe { makeFetchRequest() }
|
||||
}
|
||||
|
||||
private fun makeFetchRequest() {
|
||||
val bar = sampleRoomStore.store.fetch("")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace }
|
||||
}*/
|
||||
|
||||
private fun initPersister() {
|
||||
try {
|
||||
persister = newPersister()
|
||||
|
@ -104,7 +84,7 @@ class SampleApp : Application() {
|
|||
/**
|
||||
* Returns a "fetcher" which will retrieve new data from the network.
|
||||
*/
|
||||
private fun fetcher(barCode: BarCode): Deferred<ResponseBody> {
|
||||
private fun fetcher(barCode: BarCode): Deferred<ResponseBody> {
|
||||
return provideRetrofit().fetchSubredditForPersister(barCode.key, "10")
|
||||
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
//package com.nytimes.android.sample
|
||||
//
|
||||
//import android.arch.persistence.room.Dao
|
||||
//import android.arch.persistence.room.Database
|
||||
//import android.arch.persistence.room.Entity
|
||||
//import android.arch.persistence.room.Insert
|
||||
//import android.arch.persistence.room.PrimaryKey
|
||||
//import android.arch.persistence.room.Query
|
||||
//import android.arch.persistence.room.Room
|
||||
//import android.arch.persistence.room.RoomDatabase
|
||||
//import android.content.Context
|
||||
//import com.nytimes.android.external.store3.base.Fetcher
|
||||
//import com.nytimes.android.external.store3.base.impl.room.StoreRoom
|
||||
//import com.nytimes.android.external.store3.base.room.RoomPersister
|
||||
//import io.reactivex.Flowable
|
||||
//import io.reactivex.Observable
|
||||
//import io.reactivex.Single
|
||||
//
|
||||
//@Entity
|
||||
//data class User(
|
||||
// @PrimaryKey(autoGenerate = true)
|
||||
// var uid: Int = 0,
|
||||
// val name: String)
|
||||
//
|
||||
//@Dao
|
||||
//interface UserDao {
|
||||
// @Query("SELECT name FROM user")
|
||||
// fun loadAll(): Flowable<List<String>>
|
||||
//
|
||||
// @Insert
|
||||
// fun insertAll(user: User)
|
||||
//
|
||||
//}
|
||||
//
|
||||
//@Database(entities = arrayOf(User::class), version = 1)
|
||||
//abstract class AppDatabase : RoomDatabase() {
|
||||
// abstract fun userDao(): UserDao
|
||||
//}
|
||||
//
|
||||
//class SampleRoomStore(context: Context){
|
||||
// val db = Room.databaseBuilder(context, AppDatabase::class.java, "db").build()
|
||||
//
|
||||
// val fetcher = Fetcher<User, String> { Single.just(User(name = "Mike")) }
|
||||
// val persister = object : RoomPersister<User, List<String>, String> {
|
||||
//
|
||||
// override fun read(key: String): Observable<List<String>> {
|
||||
// return db.userDao().loadAll().toObservable()
|
||||
// }
|
||||
//
|
||||
// override fun write(key: String, user: User) {
|
||||
// db.userDao().insertAll(user)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// val store = StoreRoom.from(fetcher, persister)
|
||||
//}
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
|
@ -2,7 +2,6 @@ package com.nytimes.android.external.store3.base.impl
|
|||
|
||||
import com.nytimes.android.external.cache3.Cache
|
||||
import com.nytimes.android.external.cache3.CacheBuilder
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object CacheFactory {
|
||||
|
@ -15,16 +14,6 @@ object CacheFactory {
|
|||
return createBaseInFlighter(memoryPolicy)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <Key, Parsed> createRoomCache(memoryPolicy: MemoryPolicy): Cache<Key, Observable<Parsed>> {
|
||||
return createBaseCache(memoryPolicy)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <Key, Parsed> createRoomInflighter(memoryPolicy: MemoryPolicy): Cache<Key, Observable<Parsed>> {
|
||||
return createBaseInFlighter(memoryPolicy)
|
||||
}
|
||||
|
||||
private fun <Key, Value> createBaseInFlighter(memoryPolicy: MemoryPolicy?): Cache<Key, Value> {
|
||||
val expireAfterToSeconds = memoryPolicy?.expireAfterTimeUnit?.toSeconds(memoryPolicy.expireAfterWrite)
|
||||
?: StoreDefaults.getCacheTTLTimeUnit()
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.impl.room;
|
||||
|
||||
import com.nytimes.android.external.cache3.Cache;
|
||||
import com.nytimes.android.external.store3.annotations.Experimental;
|
||||
import com.nytimes.android.external.store3.base.impl.CacheFactory;
|
||||
import com.nytimes.android.external.store3.base.impl.MemoryPolicy;
|
||||
import com.nytimes.android.external.store3.base.impl.StalePolicy;
|
||||
import com.nytimes.android.external.store3.base.impl.StoreUtil;
|
||||
import com.nytimes.android.external.store3.base.room.RoomFetcher;
|
||||
import com.nytimes.android.external.store3.base.room.RoomPersister;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* Store to be used for loading an object from different data sources
|
||||
*
|
||||
* @param <Raw> data type before parsing, usually a String, Reader or BufferedSource
|
||||
* @param <Parsed> data type after parsing
|
||||
* <p>
|
||||
*/
|
||||
@Experimental
|
||||
class RealStoreRoom<Raw, Parsed, Key> extends StoreRoom<Parsed, Key> {
|
||||
private final RoomFetcher<Raw, Key> fetcher;
|
||||
private final RoomPersister<Raw, Parsed, Key> persister;
|
||||
private final Cache<Key, Observable<Parsed>> memCache;
|
||||
private final StalePolicy stalePolicy;
|
||||
private final Cache<Key, Observable<Parsed>> inFlightRequests;
|
||||
|
||||
|
||||
RealStoreRoom(RoomFetcher<Raw, Key> fetcher,
|
||||
RoomPersister<Raw, Parsed, Key> persister) {
|
||||
this(fetcher, persister, null, StalePolicy.UNSPECIFIED);
|
||||
}
|
||||
|
||||
RealStoreRoom(RoomFetcher<Raw, Key> fetcher,
|
||||
RoomPersister<Raw, Parsed, Key> persister,
|
||||
StalePolicy stalePolicy) {
|
||||
this(fetcher, persister, null, stalePolicy);
|
||||
}
|
||||
|
||||
RealStoreRoom(RoomFetcher<Raw, Key> fetcher,
|
||||
RoomPersister<Raw, Parsed, Key> persister,
|
||||
MemoryPolicy memoryPolicy,
|
||||
StalePolicy stalePolicy) {
|
||||
this.fetcher = fetcher;
|
||||
this.persister = persister;
|
||||
this.stalePolicy = stalePolicy;
|
||||
this.memCache = CacheFactory.createRoomCache(memoryPolicy);
|
||||
this.inFlightRequests = CacheFactory.createRoomInflighter(memoryPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return an observable from the first data source that is available
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public Observable<Parsed> get(@Nonnull final Key key) {
|
||||
return lazyCache(key).switchIfEmpty(fetch(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return data from memory
|
||||
*/
|
||||
private Observable<Parsed> lazyCache(@Nonnull final Key key) {
|
||||
return Observable.defer(() -> cache(key)).onErrorResumeNext(Observable.empty());
|
||||
}
|
||||
|
||||
Observable<Parsed> cache(@Nonnull final Key key) {
|
||||
try {
|
||||
return memCache.get(key, () -> disk(key));
|
||||
} catch (ExecutionException e) {
|
||||
return Observable.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Observable<Parsed> memory(@Nonnull Key key) {
|
||||
Observable<Parsed> cachedValue = memCache.getIfPresent(key);
|
||||
return cachedValue == null ? Observable.empty() : cachedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data from persister and update memory after. If an error occurs, emit an empty observable
|
||||
* so that the concat call in {@link #get(Key)} moves on to {@link #fetch(Key)}
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
@Nonnull
|
||||
public Observable<Parsed> disk(@Nonnull final Key key) {
|
||||
if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) {
|
||||
return Observable.empty();
|
||||
}
|
||||
return readDisk(key);
|
||||
}
|
||||
|
||||
Observable<Parsed> readDisk(@Nonnull final Key key) {
|
||||
return persister()
|
||||
.read(key)
|
||||
.doOnNext(this::guardAgainstEmptyCollection)
|
||||
.onErrorResumeNext(
|
||||
Observable.empty())
|
||||
.doOnNext(parsed -> {
|
||||
updateMemory(key, parsed);
|
||||
if (stalePolicy == StalePolicy.REFRESH_ON_STALE
|
||||
&& StoreUtil.persisterIsStale(key, persister)) {
|
||||
backfillCache(key);
|
||||
}
|
||||
}).cache();
|
||||
}
|
||||
|
||||
@SuppressWarnings("CheckReturnValue")
|
||||
void backfillCache(@Nonnull Key key) {
|
||||
fetch(key).subscribe(it -> {
|
||||
}, it -> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will check to see if there exists an in flight observable and return it before
|
||||
* going to network
|
||||
*
|
||||
* @return data from fresh and store it in memory and persister
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public Observable<Parsed> fetch(@Nonnull final Key key) {
|
||||
return Observable.defer(() -> fetchAndPersist(key));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* There should only be one fresh request in flight at any give time.
|
||||
* <p>
|
||||
* Return cached request in the form of a Behavior Subject which will emit to its subscribers
|
||||
* the last value it gets. Subject/Observable is cached in a {@link ConcurrentMap} to maintain
|
||||
* thread safety.
|
||||
*
|
||||
* @param key resource identifier
|
||||
* @return observable that emits a {@link Parsed} value
|
||||
*/
|
||||
@Nullable
|
||||
private Observable<Parsed> fetchAndPersist(@Nonnull final Key key) {
|
||||
try {
|
||||
return inFlightRequests.get(key, () -> response(key));
|
||||
} catch (ExecutionException e) {
|
||||
return Observable.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Observable<Parsed> response(@Nonnull final Key key) {
|
||||
return fetcher()
|
||||
.fetch(key)
|
||||
.concatMapEagerDelayError(it -> {
|
||||
persister().write(key, it);
|
||||
return readDisk(key);
|
||||
},true)
|
||||
.onErrorResumeNext(throwable -> {
|
||||
if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) {
|
||||
return readDisk(key).switchIfEmpty(Observable.error(throwable));
|
||||
}
|
||||
return Observable.error(throwable);
|
||||
})
|
||||
.doAfterTerminate(() -> inFlightRequests.invalidate(key))
|
||||
.cache();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only update memory after persister has been successfully updated
|
||||
*
|
||||
* @param key
|
||||
* @param data
|
||||
*/
|
||||
void updateMemory(@Nonnull final Key key, final Parsed data) {
|
||||
memCache.put(key, Observable.just(data));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
//need to create a clearable override to clear all
|
||||
// since room knows what table associated with data
|
||||
public void clear() {
|
||||
for (Key cachedKey : memCache.asMap().keySet()) {
|
||||
clear(cachedKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(@Nonnull Key key) {
|
||||
inFlightRequests.invalidate(key);
|
||||
memCache.invalidate(key);
|
||||
StoreUtil.clearPersister(persister(), key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return DiskDAO that stores and stores <Raw> data
|
||||
*/
|
||||
RoomPersister<Raw, Parsed, Key> persister() {
|
||||
return persister;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
RoomFetcher<Raw, Key> fetcher() {
|
||||
return fetcher;
|
||||
}
|
||||
|
||||
private void guardAgainstEmptyCollection(Parsed v) {
|
||||
if (v instanceof Collection && ((Collection) v).isEmpty()) {
|
||||
throw new IllegalStateException("empty result set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.impl.room;
|
||||
|
||||
import com.nytimes.android.external.store3.annotations.Experimental;
|
||||
import com.nytimes.android.external.store3.base.Fetcher;
|
||||
import com.nytimes.android.external.store3.base.impl.MemoryPolicy;
|
||||
import com.nytimes.android.external.store3.base.impl.StalePolicy;
|
||||
import com.nytimes.android.external.store3.base.impl.StoreBuilder;
|
||||
import com.nytimes.android.external.store3.base.room.RoomFetcher;
|
||||
import com.nytimes.android.external.store3.base.room.RoomPersister;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* a {@link StoreBuilder StoreBuilder}
|
||||
* will return an instance of a store
|
||||
* <p>
|
||||
* A {@link StoreRoom Store} can
|
||||
* {@link StoreRoom#get(V) Store.get() } cached data or
|
||||
* force a call to {@link StoreRoom#fetch(V) Store.fresh() }
|
||||
* (skipping cache)
|
||||
*/
|
||||
@Experimental
|
||||
public abstract class StoreRoom<T, V> {
|
||||
|
||||
/**
|
||||
* Return an Observable of T for request Barcode
|
||||
* Data will be returned from oldest non expired source
|
||||
* Sources are Memory Cache, Disk Cache, Inflight, Network Response
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract Observable<T> get(@Nonnull V key);
|
||||
|
||||
/**
|
||||
* Return an Observable of T for requested Barcode skipping Memory & Disk Cache
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract Observable<T> fetch(@Nonnull V key);
|
||||
|
||||
/**
|
||||
* purges all entries from memory and disk cache
|
||||
* Persister will only be cleared if they implements Clearable
|
||||
*/
|
||||
public abstract void clear();
|
||||
|
||||
/**
|
||||
* Purge a particular entry from memory and disk cache.
|
||||
* Persister will only be cleared if they implements Clearable
|
||||
*/
|
||||
public abstract void clear(@Nonnull V key);
|
||||
|
||||
|
||||
public static <Raw, Parsed, Key> StoreRoom<Parsed, Key> from
|
||||
(RoomFetcher<Raw, Key> fetcher, RoomPersister<Raw, Parsed, Key> persister) {
|
||||
return new RealStoreRoom<>(fetcher, persister);
|
||||
}
|
||||
|
||||
public static <Raw, Parsed, Key> StoreRoom<Parsed, Key> from(
|
||||
RoomFetcher<Raw, Key> fetcher,
|
||||
RoomPersister<Raw, Parsed, Key> persister,
|
||||
StalePolicy policy) {
|
||||
return new RealStoreRoom<>(fetcher, persister, policy);
|
||||
}
|
||||
|
||||
public static <Raw, Parsed, Key> StoreRoom<Parsed, Key> from
|
||||
(RoomFetcher<Raw, Key> fetcher, RoomPersister<Raw, Parsed, Key> persister,
|
||||
StalePolicy stalePolicy, MemoryPolicy memoryPolicy) {
|
||||
return new RealStoreRoom<>(fetcher, persister, memoryPolicy, stalePolicy);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.room;
|
||||
|
||||
import com.nytimes.android.external.store3.annotations.Experimental;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
@Experimental
|
||||
public interface RoomDiskRead<Raw, Key> {
|
||||
@Nonnull
|
||||
Observable<Raw> read(@Nonnull Key key);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.room;
|
||||
|
||||
import com.nytimes.android.external.store3.annotations.Experimental;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@Experimental
|
||||
public interface RoomDiskWrite<Raw, Key> {
|
||||
/**
|
||||
* @param key to use to get data from persister
|
||||
* If data is not available implementer needs to
|
||||
* either return Observable.empty or throw an exception
|
||||
*/
|
||||
@Nonnull
|
||||
void write(@Nonnull Key key, @Nonnull Raw raw);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.room;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for fetching new data for a Store
|
||||
*
|
||||
* @param <Raw> data type before parsing
|
||||
*/
|
||||
public interface RoomFetcher<Raw, Key> {
|
||||
|
||||
/**
|
||||
* @param key Container with Key and Type used as a request param
|
||||
* @return Observable that emits {@link Raw} data
|
||||
*/
|
||||
@Nonnull
|
||||
Observable<Raw> fetch(@Nonnull Key key);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.nytimes.android.external.store3.base.room;
|
||||
|
||||
import com.nytimes.android.external.store3.annotations.Experimental;
|
||||
import com.nytimes.android.external.store3.base.BasePersister;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* Interface for fetching data from persister
|
||||
* when implementing also think about implementing PathResolver to ease in creating primary keys
|
||||
*
|
||||
* @param <Raw> data type before parsing
|
||||
*/
|
||||
@Experimental
|
||||
public interface RoomPersister<Raw, Parsed, Key> extends
|
||||
RoomDiskRead<Parsed, Key>, RoomDiskWrite<Raw, Key>, BasePersister {
|
||||
|
||||
/**
|
||||
* @param key to use to get data from persister
|
||||
* If data is not available implementer needs to
|
||||
* either return Observable.empty or throw an exception
|
||||
*/
|
||||
@Override
|
||||
@Nonnull
|
||||
Observable<Parsed> read(@Nonnull final Key key);
|
||||
|
||||
/**
|
||||
* @param key to use to store data to persister
|
||||
* @param raw raw string to be stored
|
||||
*/
|
||||
@Override
|
||||
@Nonnull
|
||||
void write(@Nonnull final Key key, @Nonnull final Raw raw);
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package com.nytimes.android.external.store3.room
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nytimes.android.external.store3.base.Clearable
|
||||
import com.nytimes.android.external.store3.base.impl.BarCode
|
||||
import com.nytimes.android.external.store3.base.impl.StalePolicy
|
||||
import com.nytimes.android.external.store3.base.impl.room.StoreRoom
|
||||
import com.nytimes.android.external.store3.base.room.RoomPersister
|
||||
import io.reactivex.Observable
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ClearStoreRoomTest {
|
||||
private val persister: RoomClearingPersister = mock()
|
||||
private val networkCalls: AtomicInteger = AtomicInteger(0)
|
||||
private val store = StoreRoom.from({ Observable.fromCallable { networkCalls.incrementAndGet() } },
|
||||
persister,
|
||||
StalePolicy.UNSPECIFIED)
|
||||
|
||||
@Test
|
||||
fun testClearSingleBarCode() {
|
||||
// one request should produce one call
|
||||
val barcode = BarCode("type", "key")
|
||||
|
||||
`when`(persister.read(barcode))
|
||||
.thenReturn(Observable.empty()) //read from disk on get
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.empty()) //read from disk after clearing
|
||||
.thenReturn(Observable.just(1)) //read from disk after making additional network call
|
||||
|
||||
store.get(barcode).test().awaitTerminalEvent()
|
||||
assertThat(networkCalls.toInt()).isEqualTo(1)
|
||||
|
||||
// after clearing the memory another call should be made
|
||||
store.clear(barcode)
|
||||
store.get(barcode).test().awaitTerminalEvent()
|
||||
verify<RoomClearingPersister>(persister).clear(barcode)
|
||||
assertThat(networkCalls.toInt()).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearAllBarCodes() {
|
||||
val barcode1 = BarCode("type1", "key1")
|
||||
val barcode2 = BarCode("type2", "key2")
|
||||
|
||||
`when`(persister.read(barcode1))
|
||||
.thenReturn(Observable.empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)) //read from disk after making additional network call
|
||||
|
||||
`when`(persister.read(barcode2))
|
||||
.thenReturn(Observable.empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)) //read from disk after making additional network call
|
||||
|
||||
|
||||
// each request should produce one call
|
||||
store.get(barcode1).test().awaitTerminalEvent()
|
||||
store.get(barcode2).test().awaitTerminalEvent()
|
||||
assertThat(networkCalls.toInt()).isEqualTo(2)
|
||||
|
||||
store.clear()
|
||||
|
||||
// after everything is cleared each request should produce another 2 calls
|
||||
store.get(barcode1).test().awaitTerminalEvent()
|
||||
store.get(barcode2).test().awaitTerminalEvent()
|
||||
assertThat(networkCalls.toInt()).isEqualTo(4)
|
||||
}
|
||||
|
||||
//everything will be mocked
|
||||
internal open class RoomClearingPersister : RoomPersister<Int, Int, BarCode>, Clearable<BarCode> {
|
||||
override fun clear(key: BarCode) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
|
||||
override fun read(barCode: BarCode): Observable<Int> {
|
||||
throw RuntimeException()
|
||||
}
|
||||
|
||||
override fun write(barCode: BarCode, integer: Int) {
|
||||
//noop
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package com.nytimes.android.external.store3.room
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nytimes.android.external.store3.base.impl.BarCode
|
||||
import com.nytimes.android.external.store3.base.impl.StalePolicy
|
||||
import com.nytimes.android.external.store3.base.impl.room.StoreRoom
|
||||
import com.nytimes.android.external.store3.base.room.RoomFetcher
|
||||
import com.nytimes.android.external.store3.base.room.RoomPersister
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class StoreRoomTest {
|
||||
val counter = AtomicInteger(0)
|
||||
val fetcher: RoomFetcher<String, BarCode> = mock()
|
||||
val persister: RoomPersister<String, String, BarCode> = mock()
|
||||
private val barCode = BarCode("key", "value")
|
||||
|
||||
@Test
|
||||
fun testSimple() {
|
||||
|
||||
val simpleStore = StoreRoom.from(
|
||||
fetcher,
|
||||
persister,
|
||||
StalePolicy.UNSPECIFIED
|
||||
)
|
||||
|
||||
|
||||
`when`(fetcher.fetch(barCode))
|
||||
.thenReturn(Observable.just(NETWORK))
|
||||
|
||||
`when`(persister.read(barCode))
|
||||
.thenReturn(Observable.empty())
|
||||
.thenReturn(Observable.just(DISK))
|
||||
|
||||
|
||||
var value = simpleStore.get(barCode).blockingFirst()
|
||||
|
||||
assertThat(value).isEqualTo(DISK)
|
||||
value = simpleStore.get(barCode).blockingFirst()
|
||||
assertThat(value).isEqualTo(DISK)
|
||||
verify(fetcher, times(1)).fetch(barCode)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testDoubleTap() {
|
||||
val simpleStore = StoreRoom.from(
|
||||
fetcher,
|
||||
persister,
|
||||
StalePolicy.UNSPECIFIED
|
||||
)
|
||||
|
||||
val networkSingle = Single.create<String> { emitter ->
|
||||
if (counter.incrementAndGet() == 1) {
|
||||
emitter.onSuccess(NETWORK)
|
||||
} else {
|
||||
emitter.onError(RuntimeException("Yo Dawg your inflight is broken"))
|
||||
}
|
||||
}
|
||||
|
||||
`when`(fetcher.fetch(barCode))
|
||||
.thenReturn(networkSingle.toObservable())
|
||||
|
||||
`when`(persister.read(barCode))
|
||||
.thenReturn(Observable.empty())
|
||||
.thenReturn(Observable.just(DISK))
|
||||
|
||||
|
||||
val response = simpleStore.get(barCode)
|
||||
.zipWith(simpleStore.get(barCode), BiFunction<String, String, String> { s, s2 -> "hello" })
|
||||
.blockingFirst()
|
||||
assertThat(response).isEqualTo("hello")
|
||||
verify(fetcher, times(1)).fetch(barCode)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val DISK = "disk"
|
||||
private val NETWORK = "fresh"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue