Replace Shared Preferences with Data Store
This commit is contained in:
@@ -120,9 +120,6 @@ dependencies {
|
|||||||
// https://github.com/Kotlin/kotlinx.serialization/releases
|
// https://github.com/Kotlin/kotlinx.serialization/releases
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/preference
|
|
||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
|
||||||
|
|
||||||
// https://github.com/JakeWharton/timber/releases
|
// https://github.com/JakeWharton/timber/releases
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||||
|
|
||||||
@@ -145,6 +142,12 @@ dependencies {
|
|||||||
// https://github.com/square/picasso/releases
|
// https://github.com/square/picasso/releases
|
||||||
implementation "com.squareup.picasso:picasso:2.8"
|
implementation "com.squareup.picasso:picasso:2.8"
|
||||||
|
|
||||||
|
// https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases
|
||||||
|
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6"
|
||||||
|
|
||||||
|
// https://developer.android.com/topic/libraries/architecture/datastore
|
||||||
|
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||||
|
|
||||||
// https://github.com/junit-team/junit4/releases
|
// https://github.com/junit-team/junit4/releases
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
|
|
||||||
@@ -163,9 +166,6 @@ dependencies {
|
|||||||
// https://mockk.io/
|
// https://mockk.io/
|
||||||
testImplementation "io.mockk:mockk:1.12.3"
|
testImplementation "io.mockk:mockk:1.12.3"
|
||||||
|
|
||||||
// https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases
|
|
||||||
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6"
|
|
||||||
|
|
||||||
// https://github.com/facebook/flipper/releases
|
// https://github.com/facebook/flipper/releases
|
||||||
def flipper_version = "0.140.0"
|
def flipper_version = "0.140.0"
|
||||||
debugImplementation "com.facebook.flipper:flipper:$flipper_version"
|
debugImplementation "com.facebook.flipper:flipper:$flipper_version"
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ interface AuthRepo {
|
|||||||
|
|
||||||
fun authenticationStatuses(): Flow<Boolean>
|
fun authenticationStatuses(): Flow<Boolean>
|
||||||
|
|
||||||
fun logout()
|
suspend fun logout()
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AuthStorage {
|
interface AuthStorage {
|
||||||
fun storeAuthData(authHeader: String, baseUrl: String)
|
suspend fun storeAuthData(authHeader: String, baseUrl: String)
|
||||||
|
|
||||||
suspend fun getBaseUrl(): String?
|
suspend fun getBaseUrl(): String?
|
||||||
|
|
||||||
@@ -11,5 +11,5 @@ interface AuthStorage {
|
|||||||
|
|
||||||
fun authHeaderObservable(): Flow<String?>
|
fun authHeaderObservable(): Flow<String?>
|
||||||
|
|
||||||
fun clearAuthData()
|
suspend fun clearAuthData()
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
return storage.authHeaderObservable().map { it != null }
|
return storage.authHeaderObservable().map { it != null }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override suspend fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
storage.clearAuthData()
|
storage.clearAuthData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,48 @@
|
|||||||
package gq.kirmanak.mealient.data.auth.impl
|
package gq.kirmanak.mealient.data.auth.impl
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.core.content.edit
|
|
||||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||||
import gq.kirmanak.mealient.extensions.changesFlow
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import gq.kirmanak.mealient.extensions.getStringOrNull
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val AUTH_HEADER_KEY = "AUTH_TOKEN"
|
|
||||||
private const val BASE_URL_KEY = "BASE_URL"
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AuthStorageImpl @Inject constructor(
|
class AuthStorageImpl @Inject constructor(
|
||||||
private val sharedPreferences: SharedPreferences
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : AuthStorage {
|
) : AuthStorage {
|
||||||
|
|
||||||
override fun storeAuthData(authHeader: String, baseUrl: String) {
|
private val authHeaderKey by preferencesStorage::authHeaderKey
|
||||||
|
private val baseUrlKey by preferencesStorage::baseUrlKey
|
||||||
|
|
||||||
|
override suspend fun storeAuthData(authHeader: String, baseUrl: String) {
|
||||||
Timber.v("storeAuthData() called with: authHeader = $authHeader, baseUrl = $baseUrl")
|
Timber.v("storeAuthData() called with: authHeader = $authHeader, baseUrl = $baseUrl")
|
||||||
sharedPreferences.edit {
|
preferencesStorage.storeValues(
|
||||||
putString(AUTH_HEADER_KEY, authHeader)
|
Pair(authHeaderKey, authHeader),
|
||||||
putString(BASE_URL_KEY, baseUrl)
|
Pair(baseUrlKey, baseUrl),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getBaseUrl(): String? {
|
override suspend fun getBaseUrl(): String? {
|
||||||
val baseUrl = sharedPreferences.getStringOrNull(BASE_URL_KEY)
|
val baseUrl = preferencesStorage.getValue(baseUrlKey)
|
||||||
Timber.d("getBaseUrl: base url is $baseUrl")
|
Timber.d("getBaseUrl: base url is $baseUrl")
|
||||||
return baseUrl
|
return baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAuthHeader(): String? {
|
override suspend fun getAuthHeader(): String? {
|
||||||
Timber.v("getAuthHeader() called")
|
Timber.v("getAuthHeader() called")
|
||||||
val token = sharedPreferences.getStringOrNull(AUTH_HEADER_KEY)
|
val token = preferencesStorage.getValue(authHeaderKey)
|
||||||
Timber.d("getAuthHeader: header is \"$token\"")
|
Timber.d("getAuthHeader: header is \"$token\"")
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun authHeaderObservable(): Flow<String?> {
|
override fun authHeaderObservable(): Flow<String?> {
|
||||||
Timber.v("authHeaderObservable() called")
|
Timber.v("authHeaderObservable() called")
|
||||||
return sharedPreferences.changesFlow().map { it.first.getStringOrNull(AUTH_HEADER_KEY) }
|
return preferencesStorage.valueUpdates(authHeaderKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearAuthData() {
|
override suspend fun clearAuthData() {
|
||||||
Timber.v("clearAuthData() called")
|
Timber.v("clearAuthData() called")
|
||||||
sharedPreferences.edit {
|
preferencesStorage.removeValues(authHeaderKey, baseUrlKey)
|
||||||
remove(AUTH_HEADER_KEY)
|
|
||||||
remove(BASE_URL_KEY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package gq.kirmanak.mealient.data.disclaimer
|
|||||||
interface DisclaimerStorage {
|
interface DisclaimerStorage {
|
||||||
suspend fun isDisclaimerAccepted(): Boolean
|
suspend fun isDisclaimerAccepted(): Boolean
|
||||||
|
|
||||||
fun acceptDisclaimer()
|
suspend fun acceptDisclaimer()
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,26 @@
|
|||||||
package gq.kirmanak.mealient.data.disclaimer
|
package gq.kirmanak.mealient.data.disclaimer
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import gq.kirmanak.mealient.extensions.getBooleanOrFalse
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val IS_DISCLAIMER_ACCEPTED_KEY = "IS_DISCLAIMER_ACCEPTED"
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class DisclaimerStorageImpl @Inject constructor(
|
class DisclaimerStorageImpl @Inject constructor(
|
||||||
private val sharedPreferences: SharedPreferences
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : DisclaimerStorage {
|
) : DisclaimerStorage {
|
||||||
|
|
||||||
|
private val isDisclaimerAcceptedKey by preferencesStorage::isDisclaimerAcceptedKey
|
||||||
|
|
||||||
override suspend fun isDisclaimerAccepted(): Boolean {
|
override suspend fun isDisclaimerAccepted(): Boolean {
|
||||||
Timber.v("isDisclaimerAccepted() called")
|
Timber.v("isDisclaimerAccepted() called")
|
||||||
val isAccepted = sharedPreferences.getBooleanOrFalse(IS_DISCLAIMER_ACCEPTED_KEY)
|
val isAccepted = preferencesStorage.getValue(isDisclaimerAcceptedKey) ?: false
|
||||||
Timber.v("isDisclaimerAccepted() returned: $isAccepted")
|
Timber.v("isDisclaimerAccepted() returned: $isAccepted")
|
||||||
return isAccepted
|
return isAccepted
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptDisclaimer() {
|
override suspend fun acceptDisclaimer() {
|
||||||
Timber.v("acceptDisclaimer() called")
|
Timber.v("acceptDisclaimer() called")
|
||||||
sharedPreferences.edit()
|
preferencesStorage.storeValues(Pair(isDisclaimerAcceptedKey, true))
|
||||||
.putBoolean(IS_DISCLAIMER_ACCEPTED_KEY, true)
|
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package gq.kirmanak.mealient.data.storage
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface PreferencesStorage {
|
||||||
|
|
||||||
|
val baseUrlKey: Preferences.Key<String>
|
||||||
|
|
||||||
|
val authHeaderKey: Preferences.Key<String>
|
||||||
|
|
||||||
|
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||||
|
|
||||||
|
suspend fun <T> getValue(key: Preferences.Key<T>): T?
|
||||||
|
|
||||||
|
suspend fun <T> requireValue(key: Preferences.Key<T>): T
|
||||||
|
|
||||||
|
suspend fun <T> storeValues(vararg pairs: Pair<Preferences.Key<T>, T>)
|
||||||
|
|
||||||
|
fun <T> valueUpdates(key: Preferences.Key<T>): Flow<T?>
|
||||||
|
|
||||||
|
suspend fun <T> removeValues(vararg keys: Preferences.Key<T>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package gq.kirmanak.mealient.data.storage
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class PreferencesStorageImpl @Inject constructor(
|
||||||
|
private val dataStore: DataStore<Preferences>
|
||||||
|
) : PreferencesStorage {
|
||||||
|
|
||||||
|
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
||||||
|
|
||||||
|
override val authHeaderKey = stringPreferencesKey("authHeader")
|
||||||
|
|
||||||
|
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
||||||
|
|
||||||
|
override suspend fun <T> getValue(key: Preferences.Key<T>): T? {
|
||||||
|
val value = dataStore.data.first()[key]
|
||||||
|
Timber.v("getValue() returned: $value for $key")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> requireValue(key: Preferences.Key<T>): T =
|
||||||
|
checkNotNull(getValue(key)) { "Value at $key is null when it was required" }
|
||||||
|
|
||||||
|
override suspend fun <T> storeValues(vararg pairs: Pair<Preferences.Key<T>, T>) {
|
||||||
|
Timber.v("storeValues() called with: pairs = ${pairs.contentToString()}")
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
pairs.forEach { preferences += it.toPreferencesPair() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T> valueUpdates(key: Preferences.Key<T>): Flow<T?> {
|
||||||
|
Timber.v("valueUpdates() called with: key = $key")
|
||||||
|
return dataStore.data
|
||||||
|
.map { it[key] }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.onEach { Timber.d("valueUpdates: new value at $key is $it") }
|
||||||
|
.onCompletion { Timber.i(it, "valueUpdates: finished") }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> removeValues(vararg keys: Preferences.Key<T>) {
|
||||||
|
Timber.v("removeValues() called with: key = ${keys.contentToString()}")
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
keys.forEach { preferences -= it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Pair<Preferences.Key<T>, T>.toPreferencesPair(): Preferences.Pair<T> {
|
||||||
|
val (key, value) = this
|
||||||
|
return key to value
|
||||||
|
}
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
package gq.kirmanak.mealient.di
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.data.AppDb
|
import gq.kirmanak.mealient.data.AppDb
|
||||||
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
|
import gq.kirmanak.mealient.data.storage.PreferencesStorageImpl
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object AppModule {
|
interface AppModule {
|
||||||
|
companion object {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun createDb(@ApplicationContext context: Context): AppDb =
|
fun createDb(@ApplicationContext context: Context): AppDb =
|
||||||
@@ -22,6 +29,11 @@ object AppModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
|
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("settings") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindPreferencesStorage(preferencesStorage: PreferencesStorageImpl): PreferencesStorage
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.channels.onClosed
|
|
||||||
import kotlinx.coroutines.channels.onFailure
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
suspend fun SharedPreferences.getStringOrNull(key: String) =
|
|
||||||
withContext(Dispatchers.IO) { getString(key, null) }
|
|
||||||
|
|
||||||
suspend fun SharedPreferences.getBooleanOrFalse(key: String) =
|
|
||||||
withContext(Dispatchers.IO) { getBoolean(key, false) }
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun SharedPreferences.changesFlow(): Flow<Pair<SharedPreferences, String?>> = callbackFlow {
|
|
||||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
|
|
||||||
Timber.v("watchChanges: listener called with key $key")
|
|
||||||
trySend(prefs to key)
|
|
||||||
.onFailure { Timber.e(it, "watchChanges: can't send preference change, key $key") }
|
|
||||||
.onClosed { Timber.e(it, "watchChanges: flow has been closed") }
|
|
||||||
}
|
|
||||||
Timber.v("watchChanges: registering listener")
|
|
||||||
registerOnSharedPreferenceChangeListener(listener)
|
|
||||||
send(this@changesFlow to null)
|
|
||||||
awaitClose {
|
|
||||||
Timber.v("watchChanges: flow has been closed")
|
|
||||||
unregisterOnSharedPreferenceChangeListener(listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -38,7 +38,9 @@ class AuthenticationViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
|
viewModelScope.launch {
|
||||||
authRepo.logout()
|
authRepo.logout()
|
||||||
viewModelScope.launch { recipeRepo.clearLocalData() }
|
recipeRepo.clearLocalData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,11 @@ class DisclaimerViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun acceptDisclaimer() {
|
fun acceptDisclaimer() {
|
||||||
Timber.v("acceptDisclaimer() called")
|
Timber.v("acceptDisclaimer() called")
|
||||||
|
viewModelScope.launch {
|
||||||
disclaimerStorage.acceptDisclaimer()
|
disclaimerStorage.acceptDisclaimer()
|
||||||
_isAccepted.value = true
|
_isAccepted.value = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun startCountDown() {
|
fun startCountDown() {
|
||||||
Timber.v("startCountDown() called")
|
Timber.v("startCountDown() called")
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
|
|||||||
import gq.kirmanak.mealient.test.RobolectricTest
|
import gq.kirmanak.mealient.test.RobolectricTest
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@@ -102,12 +102,12 @@ class AuthRepoImplTest : RobolectricTest() {
|
|||||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
||||||
} returns TEST_TOKEN
|
} returns TEST_TOKEN
|
||||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
|
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
|
||||||
verify { storage.storeAuthData(TEST_AUTH_HEADER, TEST_BASE_URL) }
|
coVerify { storage.storeAuthData(TEST_AUTH_HEADER, TEST_BASE_URL) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when logout then clearAuthData is called`() = runTest {
|
fun `when logout then clearAuthData is called`() = runTest {
|
||||||
subject.logout()
|
subject.logout()
|
||||||
verify { storage.clearAuthData() }
|
coVerify { storage.clearAuthData() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package gq.kirmanak.mealient.data.storage
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import gq.kirmanak.mealient.test.HiltRobolectricTest
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@HiltAndroidTest
|
||||||
|
class PreferencesStorageImplTest : HiltRobolectricTest() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var subject: PreferencesStorage
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when getValue without writes then null`() = runTest {
|
||||||
|
assertThat(subject.getValue(subject.authHeaderKey)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException::class)
|
||||||
|
fun `when requireValue without writes then throws IllegalStateException`() = runTest {
|
||||||
|
subject.requireValue(subject.authHeaderKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when getValue after write then returns value`() = runTest {
|
||||||
|
subject.storeValues(Pair(subject.authHeaderKey, "test"))
|
||||||
|
assertThat(subject.getValue(subject.authHeaderKey)).isEqualTo("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when storeValue then valueUpdates emits`() = runTest {
|
||||||
|
subject.storeValues(Pair(subject.authHeaderKey, "test"))
|
||||||
|
assertThat(subject.valueUpdates(subject.authHeaderKey).first()).isEqualTo("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when remove value then getValue returns null`() = runTest {
|
||||||
|
subject.storeValues(Pair(subject.authHeaderKey, "test"))
|
||||||
|
subject.removeValues(subject.authHeaderKey)
|
||||||
|
assertThat(subject.getValue(subject.authHeaderKey)).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -10,27 +10,27 @@ class RoomTypeConvertersTest {
|
|||||||
fun `when localDateTimeToTimestamp then correctly converts`() {
|
fun `when localDateTimeToTimestamp then correctly converts`() {
|
||||||
val input = LocalDateTime.parse("2021-11-13T15:56:33")
|
val input = LocalDateTime.parse("2021-11-13T15:56:33")
|
||||||
val actual = RoomTypeConverters.localDateTimeToTimestamp(input)
|
val actual = RoomTypeConverters.localDateTimeToTimestamp(input)
|
||||||
Truth.assertThat(actual).isEqualTo(1636818993000)
|
assertThat(actual).isEqualTo(1636818993000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when timestampToLocalDateTime then correctly converts`() {
|
fun `when timestampToLocalDateTime then correctly converts`() {
|
||||||
val expected = LocalDateTime.parse("2021-11-13T15:58:38")
|
val expected = LocalDateTime.parse("2021-11-13T15:58:38")
|
||||||
val actual = RoomTypeConverters.timestampToLocalDateTime(1636819118000)
|
val actual = RoomTypeConverters.timestampToLocalDateTime(1636819118000)
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
assertThat(actual).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when localDateToTimeStamp then correctly converts`() {
|
fun `when localDateToTimeStamp then correctly converts`() {
|
||||||
val input = LocalDate.parse("2021-11-13")
|
val input = LocalDate.parse("2021-11-13")
|
||||||
val actual = RoomTypeConverters.localDateToTimeStamp(input)
|
val actual = RoomTypeConverters.localDateToTimeStamp(input)
|
||||||
Truth.assertThat(actual).isEqualTo(1636761600000)
|
assertThat(actual).isEqualTo(1636761600000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when timestampToLocalDate then correctly converts`() {
|
fun `when timestampToLocalDate then correctly converts`() {
|
||||||
val expected = LocalDate.parse("2021-11-13")
|
val expected = LocalDate.parse("2021-11-13")
|
||||||
val actual = RoomTypeConverters.timestampToLocalDate(1636761600000)
|
val actual = RoomTypeConverters.timestampToLocalDate(1636761600000)
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
assertThat(actual).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user