Merge pull request #42 from fknives/issue#31-move-hilt-to-separate-branch
Issue#31 move hilt to separate branch
This commit is contained in:
commit
4b985bec25
86 changed files with 87 additions and 1884 deletions
|
|
@ -2,8 +2,6 @@ plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
// hilt specific
|
|
||||||
id 'dagger.hilt.android.plugin'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -33,19 +31,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flavorDimensions 'di'
|
flavorDimensions 'di'
|
||||||
productFlavors {
|
|
||||||
hilt {
|
|
||||||
dimension 'di'
|
|
||||||
resValue "string", "app_name", "Hilt Test-ShowCase"
|
|
||||||
applicationId "org.fnives.test.showcase.hilt"
|
|
||||||
testInstrumentationRunner "org.fnives.test.showcase.testutils.configuration.HiltTestRunner"
|
|
||||||
}
|
|
||||||
koin {
|
|
||||||
dimension 'di'
|
|
||||||
resValue "string", "app_name", "Koin Test-ShowCase"
|
|
||||||
applicationId "org.fnives.test.showcase.koin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
|
|
@ -56,29 +41,11 @@ android {
|
||||||
java.srcDirs += "src/sharedTest/java"
|
java.srcDirs += "src/sharedTest/java"
|
||||||
assets.srcDirs += files("$projectDir/schemas".toString())
|
assets.srcDirs += files("$projectDir/schemas".toString())
|
||||||
}
|
}
|
||||||
androidTestHilt {
|
|
||||||
java.srcDirs += "src/sharedTestHilt/java"
|
|
||||||
assets.srcDirs += files("$projectDir/schemas".toString())
|
|
||||||
}
|
|
||||||
androidTestKoin {
|
|
||||||
java.srcDirs += "src/sharedTestKoin/java"
|
|
||||||
assets.srcDirs += files("$projectDir/schemas".toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
java.srcDirs += "src/sharedTest/java"
|
java.srcDirs += "src/sharedTest/java"
|
||||||
java.srcDirs += "src/robolectricTest/java"
|
java.srcDirs += "src/robolectricTest/java"
|
||||||
resources.srcDirs += files("$projectDir/schemas".toString())
|
resources.srcDirs += files("$projectDir/schemas".toString())
|
||||||
}
|
}
|
||||||
testHilt {
|
|
||||||
java.srcDirs += "src/sharedTestHilt/java"
|
|
||||||
java.srcDirs += "src/robolectricTestHilt/java"
|
|
||||||
resources.srcDirs += "src/robolectricTestHilt/resources"
|
|
||||||
}
|
|
||||||
testKoin {
|
|
||||||
java.srcDirs += "src/sharedTestKoin/java"
|
|
||||||
java.srcDirs += "src/robolectricTestKoin/java"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed for androidTest
|
// needed for androidTest
|
||||||
|
|
@ -90,17 +57,10 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hilt {
|
|
||||||
enableAggregatingTask = true
|
|
||||||
enableExperimentalClasspathAggregation = true
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
// making sure the :mockserver is assembled after :clean when running tests
|
// making sure the :mockserver is assembled after :clean when running tests
|
||||||
testKoinDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
testDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
||||||
testKoinReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
testReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
||||||
testHiltDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
|
||||||
testHiltReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -113,13 +73,7 @@ dependencies {
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
|
||||||
|
|
||||||
// Koin
|
implementation "io.insert-koin:koin-android:$koin_version"
|
||||||
koinImplementation "io.insert-koin:koin-android:$koin_version"
|
|
||||||
|
|
||||||
// Hilt
|
|
||||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
|
||||||
kaptHilt "com.google.dagger:hilt-compiler:$hilt_version"
|
|
||||||
hiltImplementation "androidx.activity:activity-ktx:$activity_ktx_version"
|
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$androidx_room_version"
|
implementation "androidx.room:room-runtime:$androidx_room_version"
|
||||||
kapt "androidx.room:room-compiler:$androidx_room_version"
|
kapt "androidx.room:room-compiler:$androidx_room_version"
|
||||||
|
|
@ -151,8 +105,6 @@ dependencies {
|
||||||
testImplementation project(':mockserver')
|
testImplementation project(':mockserver')
|
||||||
testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
||||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
||||||
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
|
|
||||||
kaptTest "com.google.dagger:hilt-compiler:$hilt_version"
|
|
||||||
|
|
||||||
androidTestImplementation "androidx.room:room-testing:$androidx_room_version"
|
androidTestImplementation "androidx.room:room-testing:$androidx_room_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||||
|
|
@ -167,9 +119,6 @@ dependencies {
|
||||||
androidTestImplementation project(':mockserver')
|
androidTestImplementation project(':mockserver')
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
||||||
androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
||||||
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
|
|
||||||
kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version"
|
|
||||||
androidTestImplementation project(":network") // hilt needs it
|
|
||||||
|
|
||||||
implementation "io.reactivex.rxjava3:rxjava:3.1.3"
|
implementation "io.reactivex.rxjava3:rxjava:3.1.3"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
|
||||||
|
|
||||||
object AndroidTestServerTypeConfiguration : ServerTypeConfiguration {
|
|
||||||
override val useHttps: Boolean get() = true
|
|
||||||
|
|
||||||
override val url: String get() = "${MockServerScenarioSetup.HTTPS_BASE_URL}:${MockServerScenarioSetup.PORT}/"
|
|
||||||
|
|
||||||
override fun invoke(mockServerScenarioSetup: MockServerScenarioSetup) {
|
|
||||||
val handshakeCertificates = mockServerScenarioSetup.clientCertificates ?: return
|
|
||||||
HttpsConfigurationModule.handshakeCertificates = handshakeCertificates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.test.runner.AndroidJUnitRunner
|
|
||||||
import dagger.hilt.android.testing.HiltTestApplication
|
|
||||||
|
|
||||||
class HiltTestRunner : AndroidJUnitRunner() {
|
|
||||||
|
|
||||||
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application =
|
|
||||||
super.newApplication(cl, HiltTestApplication::class.java.name, context)
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import dagger.hilt.testing.TestInstallIn
|
|
||||||
import okhttp3.tls.HandshakeCertificates
|
|
||||||
import org.fnives.test.showcase.hilt.SessionLessQualifier
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@TestInstallIn(
|
|
||||||
components = [SingletonComponent::class],
|
|
||||||
replaces = [BindsBaseOkHttpClient::class]
|
|
||||||
)
|
|
||||||
object HttpsConfigurationModule {
|
|
||||||
|
|
||||||
lateinit var handshakeCertificates: HandshakeCertificates
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@SessionLessQualifier
|
|
||||||
fun bindsBaseOkHttpClient(enableLogging: Boolean) =
|
|
||||||
HiltNetworkModule.provideSessionLessOkHttpClient(enableLogging)
|
|
||||||
.newBuilder()
|
|
||||||
.sslSocketFactory(
|
|
||||||
handshakeCertificates.sslSocketFactory(),
|
|
||||||
handshakeCertificates.trustManager
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.fnives.test.showcase">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.splash.HiltSplashActivity"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".ui.home.HiltMainActivity" />
|
|
||||||
<activity android:name=".ui.auth.HiltAuthActivity" />
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package org.fnives.test.showcase
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
|
||||||
|
|
||||||
@HiltAndroidApp
|
|
||||||
class TestShowcaseApplication : Application()
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package org.fnives.test.showcase.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
|
||||||
import org.fnives.test.showcase.session.SessionExpirationListenerImpl
|
|
||||||
import org.fnives.test.showcase.storage.SharedPreferencesManagerImpl
|
|
||||||
import org.fnives.test.showcase.storage.database.DatabaseInitialization
|
|
||||||
import org.fnives.test.showcase.storage.favourite.FavouriteContentLocalStorageImpl
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
@Module
|
|
||||||
object AppModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideBaseUrl(): String = BaseUrlProvider.get().baseUrl
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun enableLogging(): Boolean = true
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideFavouriteDao(@ApplicationContext context: Context) =
|
|
||||||
DatabaseInitialization.create(context).favouriteDao
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideSharedPreferencesManagerImpl(@ApplicationContext context: Context) =
|
|
||||||
SharedPreferencesManagerImpl.create(context)
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideUserDataLocalStorage(
|
|
||||||
sharedPreferencesManagerImpl: SharedPreferencesManagerImpl
|
|
||||||
): UserDataLocalStorage = sharedPreferencesManagerImpl
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideFavouriteContentLocalStorage(
|
|
||||||
favouriteContentLocalStorageImpl: FavouriteContentLocalStorageImpl
|
|
||||||
): FavouriteContentLocalStorage = favouriteContentLocalStorageImpl
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun bindSessionExpirationListener(
|
|
||||||
sessionExpirationListenerImpl: SessionExpirationListenerImpl
|
|
||||||
): SessionExpirationListener = sessionExpirationListenerImpl
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.fnives.test.showcase.ui.auth.HiltAuthActivity
|
|
||||||
import org.fnives.test.showcase.ui.home.HiltMainActivity
|
|
||||||
|
|
||||||
object IntentCoordinator {
|
|
||||||
|
|
||||||
fun mainActivitygetStartIntent(context: Context) =
|
|
||||||
HiltMainActivity.getStartIntent(context)
|
|
||||||
|
|
||||||
fun authActivitygetStartIntent(context: Context) =
|
|
||||||
HiltAuthActivity.getStartIntent(context)
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelStoreOwner
|
|
||||||
import androidx.activity.viewModels as androidxViewModel
|
|
||||||
|
|
||||||
inline fun <reified T : ViewModel> ViewModelStoreOwner.viewModels(): Lazy<T> =
|
|
||||||
when (this) {
|
|
||||||
is ComponentActivity -> androidxViewModel()
|
|
||||||
else -> throw IllegalStateException("Only supports activity viewModel for now")
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.auth
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class HiltAuthActivity : AuthActivity() {
|
|
||||||
companion object {
|
|
||||||
fun getStartIntent(context: Context): Intent = Intent(context, HiltAuthActivity::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.home
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class HiltMainActivity : MainActivity() {
|
|
||||||
companion object {
|
|
||||||
fun getStartIntent(context: Context): Intent = Intent(context, HiltMainActivity::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.splash
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
|
|
||||||
@SuppressLint("CustomSplashScreen")
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class HiltSplashActivity : SplashActivity()
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="org.fnives.test.showcase">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.splash.SplashActivity"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".ui.home.MainActivity" />
|
|
||||||
<activity android:name=".ui.auth.AuthActivity" />
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -13,6 +13,18 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.TestShowCase"
|
android:theme="@style/Theme.TestShowCase"
|
||||||
tools:ignore="AllowBackup"/>
|
tools:ignore="AllowBackup">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.splash.SplashActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".ui.home.MainActivity" />
|
||||||
|
<activity android:name=".ui.auth.AuthActivity" />
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -4,15 +4,10 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
||||||
import org.fnives.test.showcase.ui.IntentCoordinator
|
import org.fnives.test.showcase.ui.IntentCoordinator
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class SessionExpirationListenerImpl @Inject constructor(
|
class SessionExpirationListenerImpl(private val context: Context) : SessionExpirationListener {
|
||||||
@ApplicationContext
|
|
||||||
private val context: Context
|
|
||||||
) : SessionExpirationListener {
|
|
||||||
|
|
||||||
override fun onSessionExpired() {
|
override fun onSessionExpired() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
import org.fnives.test.showcase.model.content.ContentId
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class FavouriteContentLocalStorageImpl @Inject constructor(
|
class FavouriteContentLocalStorageImpl(
|
||||||
private val favouriteDao: FavouriteDao
|
private val favouriteDao: FavouriteDao
|
||||||
) : FavouriteContentLocalStorage {
|
) : FavouriteContentLocalStorage {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,14 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||||
import org.fnives.test.showcase.model.auth.LoginStatus
|
import org.fnives.test.showcase.model.auth.LoginStatus
|
||||||
import org.fnives.test.showcase.model.shared.Answer
|
import org.fnives.test.showcase.model.shared.Answer
|
||||||
import org.fnives.test.showcase.ui.shared.Event
|
import org.fnives.test.showcase.ui.shared.Event
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
class AuthViewModel(private val loginUseCase: LoginUseCase) : ViewModel() {
|
||||||
class AuthViewModel @Inject constructor(private val loginUseCase: LoginUseCase) : ViewModel() {
|
|
||||||
|
|
||||||
private val _username = MutableLiveData<String>()
|
private val _username = MutableLiveData<String>()
|
||||||
val username: LiveData<String> = _username
|
val username: LiveData<String> = _username
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
|
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
|
||||||
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
||||||
|
|
@ -18,10 +16,8 @@ import org.fnives.test.showcase.model.content.ContentId
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
import org.fnives.test.showcase.model.shared.Resource
|
import org.fnives.test.showcase.model.shared.Resource
|
||||||
import org.fnives.test.showcase.ui.shared.Event
|
import org.fnives.test.showcase.ui.shared.Event
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
class MainViewModel(
|
||||||
class MainViewModel @Inject constructor(
|
|
||||||
private val getAllContentUseCase: GetAllContentUseCase,
|
private val getAllContentUseCase: GetAllContentUseCase,
|
||||||
private val logoutUseCase: LogoutUseCase,
|
private val logoutUseCase: LogoutUseCase,
|
||||||
private val fetchContentUseCase: FetchContentUseCase,
|
private val fetchContentUseCase: FetchContentUseCase,
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,12 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||||
import org.fnives.test.showcase.ui.shared.Event
|
import org.fnives.test.showcase.ui.shared.Event
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
class SplashViewModel(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() {
|
||||||
class SplashViewModel @Inject constructor(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() {
|
|
||||||
|
|
||||||
private val _navigateTo = MutableLiveData<Event<NavigateTo>>()
|
private val _navigateTo = MutableLiveData<Event<NavigateTo>>()
|
||||||
val navigateTo: LiveData<Event<NavigateTo>> = _navigateTo
|
val navigateTo: LiveData<Event<NavigateTo>> = _navigateTo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="app_name">Test ShowCase</string>
|
||||||
<string name="login">Login</string>
|
<string name="login">Login</string>
|
||||||
<string name="username">Username</string>
|
<string name="username">Username</string>
|
||||||
<string name="password">Password</string>
|
<string name="password">Password</string>
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package org.fnives.test.showcase.favourite
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.take
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
|
||||||
import kotlinx.coroutines.test.TestDispatcher
|
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
|
||||||
import org.fnives.test.showcase.storage.database.DatabaseInitialization
|
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@HiltAndroidTest
|
|
||||||
internal class FavouriteContentLocalStorageImplTest {
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var sut: FavouriteContentLocalStorage
|
|
||||||
private lateinit var testDispatcher: TestDispatcher
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
testDispatcher = StandardTestDispatcher(TestCoroutineScheduler())
|
|
||||||
DatabaseInitialization.dispatcher = testDispatcher
|
|
||||||
hiltRule.inject()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN content_id WHEN added to Favourite THEN it can be read out */
|
|
||||||
@Test
|
|
||||||
fun addingContentIdToFavouriteCanBeLaterReadOut() = runTest(testDispatcher) {
|
|
||||||
val expected = listOf(ContentId("a"))
|
|
||||||
|
|
||||||
sut.markAsFavourite(ContentId("a"))
|
|
||||||
val actual = sut.observeFavourites().first()
|
|
||||||
|
|
||||||
Assert.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN content_id added WHEN removed to Favourite THEN it no longer can be read out */
|
|
||||||
@Test
|
|
||||||
fun contentIdAddedThenRemovedCanNoLongerBeReadOut() = runTest(testDispatcher) {
|
|
||||||
val expected = listOf<ContentId>()
|
|
||||||
sut.markAsFavourite(ContentId("b"))
|
|
||||||
|
|
||||||
sut.deleteAsFavourite(ContentId("b"))
|
|
||||||
val actual = sut.observeFavourites().first()
|
|
||||||
|
|
||||||
Assert.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN empty database WHILE observing content WHEN favourite added THEN change is emitted */
|
|
||||||
@Test
|
|
||||||
fun addingFavouriteUpdatesExistingObservers() = runTest(testDispatcher) {
|
|
||||||
val expected = listOf(listOf(), listOf(ContentId("a")))
|
|
||||||
|
|
||||||
val actual = async(coroutineContext) {
|
|
||||||
sut.observeFavourites().take(2).toList()
|
|
||||||
}
|
|
||||||
advanceUntilIdle()
|
|
||||||
|
|
||||||
sut.markAsFavourite(ContentId("a"))
|
|
||||||
advanceUntilIdle()
|
|
||||||
|
|
||||||
Assert.assertEquals(expected, actual.getCompleted())
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN non empty database WHILE observing content WHEN favourite removed THEN change is emitted */
|
|
||||||
@Test
|
|
||||||
fun removingFavouriteUpdatesExistingObservers() = runTest(testDispatcher) {
|
|
||||||
val expected = listOf(listOf(ContentId("a")), listOf())
|
|
||||||
sut.markAsFavourite(ContentId("a"))
|
|
||||||
|
|
||||||
val actual = async(coroutineContext) {
|
|
||||||
sut.observeFavourites().take(2).toList()
|
|
||||||
}
|
|
||||||
advanceUntilIdle()
|
|
||||||
|
|
||||||
sut.deleteAsFavourite(ContentId("a"))
|
|
||||||
advanceUntilIdle()
|
|
||||||
|
|
||||||
Assert.assertEquals(expected, actual.getCompleted())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
sdk=28
|
|
||||||
shadows=org.fnives.test.showcase.testutils.shadow.ShadowSnackbar
|
|
||||||
instrumentedPackages=androidx.loader.content
|
|
||||||
application=dagger.hilt.android.testing.HiltTestApplication
|
|
||||||
|
|
@ -5,8 +5,9 @@ import androidx.test.core.app.ActivityScenario
|
||||||
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.testutils.configuration.MainDispatcherTestRule
|
import org.fnives.test.showcase.testutils.configuration.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.ui.ActivityClassHolder
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.fnives.test.showcase.ui.home.HomeRobot
|
import org.fnives.test.showcase.ui.home.HomeRobot
|
||||||
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
import org.fnives.test.showcase.ui.login.LoginRobot
|
import org.fnives.test.showcase.ui.login.LoginRobot
|
||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ object SetupAuthenticationState : KoinTest {
|
||||||
password = "b"
|
password = "b"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val activityScenario = ActivityScenario.launch(ActivityClassHolder.authActivity().java)
|
val activityScenario = ActivityScenario.launch(AuthActivity::class.java)
|
||||||
activityScenario.moveToState(Lifecycle.State.RESUMED)
|
activityScenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
val loginRobot = LoginRobot()
|
val loginRobot = LoginRobot()
|
||||||
loginRobot.setupIntentResults()
|
loginRobot.setupIntentResults()
|
||||||
|
|
@ -39,7 +40,7 @@ object SetupAuthenticationState : KoinTest {
|
||||||
fun setupLogout(
|
fun setupLogout(
|
||||||
mainDispatcherTestRule: MainDispatcherTestRule
|
mainDispatcherTestRule: MainDispatcherTestRule
|
||||||
) {
|
) {
|
||||||
val activityScenario = ActivityScenario.launch(ActivityClassHolder.mainActivity().java)
|
val activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||||
activityScenario.moveToState(Lifecycle.State.RESUMED)
|
activityScenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
val homeRobot = HomeRobot()
|
val homeRobot = HomeRobot()
|
||||||
homeRobot
|
homeRobot
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,14 @@ import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState
|
||||||
import org.fnives.test.showcase.testutils.viewactions.PullToRefresh
|
import org.fnives.test.showcase.testutils.viewactions.PullToRefresh
|
||||||
import org.fnives.test.showcase.testutils.viewactions.WithDrawable
|
import org.fnives.test.showcase.testutils.viewactions.WithDrawable
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
||||||
import org.fnives.test.showcase.ui.ActivityClassHolder
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.hamcrest.Matchers.allOf
|
import org.hamcrest.Matchers.allOf
|
||||||
|
|
||||||
class HomeRobot : Robot {
|
class HomeRobot : Robot {
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
Intents.init()
|
Intents.init()
|
||||||
Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
Intents.intending(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
.respondWith(Instrumentation.ActivityResult(0, null))
|
.respondWith(Instrumentation.ActivityResult(0, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,11 +42,11 @@ class HomeRobot : Robot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNavigatedToAuth() = apply {
|
fun assertNavigatedToAuth() = apply {
|
||||||
Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
Intents.intended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertDidNotNavigateToAuth() = apply {
|
fun assertDidNotNavigateToAuth() = apply {
|
||||||
notIntended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
notIntended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickSignOut() = apply {
|
fun clickSignOut() = apply {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import org.fnives.test.showcase.testutils.configuration.SpecificTestConfiguratio
|
||||||
import org.fnives.test.showcase.testutils.configuration.TestConfigurationsFactory
|
import org.fnives.test.showcase.testutils.configuration.TestConfigurationsFactory
|
||||||
import org.fnives.test.showcase.testutils.robot.Robot
|
import org.fnives.test.showcase.testutils.robot.Robot
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
||||||
import org.fnives.test.showcase.ui.ActivityClassHolder
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
import org.hamcrest.core.IsNot.not
|
import org.hamcrest.core.IsNot.not
|
||||||
|
|
||||||
class LoginRobot(
|
class LoginRobot(
|
||||||
|
|
@ -41,7 +41,7 @@ class LoginRobot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupIntentResults() {
|
fun setupIntentResults() {
|
||||||
intending(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
intending(hasComponent(MainActivity::class.java.canonicalName))
|
||||||
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()))
|
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,10 +95,10 @@ class LoginRobot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNavigatedToHome() = apply {
|
fun assertNavigatedToHome() = apply {
|
||||||
intended(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
intended(hasComponent(MainActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNotNavigatedToHome() = apply {
|
fun assertNotNavigatedToHome() = apply {
|
||||||
notIntended(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
notIntended(hasComponent(MainActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,16 @@ import org.fnives.test.showcase.testutils.configuration.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.robot.Robot
|
import org.fnives.test.showcase.testutils.robot.Robot
|
||||||
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState
|
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
||||||
import org.fnives.test.showcase.ui.ActivityClassHolder
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
|
|
||||||
class SplashRobot : Robot {
|
class SplashRobot : Robot {
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
Intents.init()
|
Intents.init()
|
||||||
Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
Intents.intending(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName))
|
||||||
.respondWith(Instrumentation.ActivityResult(0, null))
|
.respondWith(Instrumentation.ActivityResult(0, null))
|
||||||
Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
Intents.intending(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
.respondWith(Instrumentation.ActivityResult(0, null))
|
.respondWith(Instrumentation.ActivityResult(0, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,18 +43,18 @@ class SplashRobot : Robot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertHomeIsStarted() = apply {
|
fun assertHomeIsStarted() = apply {
|
||||||
Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertHomeIsNotStarted() = apply {
|
fun assertHomeIsNotStarted() = apply {
|
||||||
notIntended(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName))
|
notIntended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertAuthIsStarted() = apply {
|
fun assertAuthIsStarted() = apply {
|
||||||
Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
Intents.intended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertAuthIsNotStarted() = apply {
|
fun assertAuthIsNotStarted() = apply {
|
||||||
notIntended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName))
|
notIntended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
|
||||||
|
|
||||||
import androidx.annotation.CheckResult
|
|
||||||
import androidx.test.espresso.IdlingResource
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.fnives.test.showcase.hilt.SessionLessQualifier
|
|
||||||
import org.fnives.test.showcase.hilt.SessionQualifier
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class NetworkSynchronization @Inject constructor(
|
|
||||||
@SessionQualifier
|
|
||||||
private val sessionOkhttpClient: OkHttpClient,
|
|
||||||
@SessionLessQualifier
|
|
||||||
private val sessionlessOkhttpClient: OkHttpClient
|
|
||||||
) {
|
|
||||||
|
|
||||||
@CheckResult
|
|
||||||
fun registerNetworkingSynchronization(): Disposable {
|
|
||||||
val idlingResources = OkHttpClientTypes.values()
|
|
||||||
.map { it to getOkHttpClient(it) }
|
|
||||||
.associateBy { it.second.dispatcher }
|
|
||||||
.values
|
|
||||||
.map { (key, client) -> client.asIdlingResource(key.qualifier) }
|
|
||||||
.map(::IdlingResourceDisposable)
|
|
||||||
|
|
||||||
return CompositeDisposable(idlingResources)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getOkHttpClient(type: OkHttpClientTypes): OkHttpClient =
|
|
||||||
when (type) {
|
|
||||||
OkHttpClientTypes.SESSION -> sessionOkhttpClient
|
|
||||||
OkHttpClientTypes.SESSIONLESS -> sessionlessOkhttpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun OkHttpClient.asIdlingResource(name: String): IdlingResource =
|
|
||||||
OkHttp3IdlingResource.create(name, this)
|
|
||||||
|
|
||||||
enum class OkHttpClientTypes(val qualifier: String) {
|
|
||||||
SESSION("SESSION-NETWORKING"), SESSIONLESS("SESSIONLESS-NETWORKING")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui
|
|
||||||
|
|
||||||
import org.fnives.test.showcase.ui.auth.HiltAuthActivity
|
|
||||||
import org.fnives.test.showcase.ui.home.HiltMainActivity
|
|
||||||
import org.fnives.test.showcase.ui.splash.HiltSplashActivity
|
|
||||||
|
|
||||||
object ActivityClassHolder {
|
|
||||||
|
|
||||||
fun authActivity() = HiltAuthActivity::class
|
|
||||||
|
|
||||||
fun mainActivity() = HiltMainActivity::class
|
|
||||||
|
|
||||||
fun splashActivity() = HiltSplashActivity::class
|
|
||||||
}
|
|
||||||
|
|
@ -1,251 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.home
|
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.test.core.app.ActivityScenario
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule
|
|
||||||
import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory
|
|
||||||
import org.fnives.test.showcase.testutils.idling.Disposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.NetworkSynchronization
|
|
||||||
import org.fnives.test.showcase.testutils.idling.loopMainThreadFor
|
|
||||||
import org.fnives.test.showcase.testutils.idling.loopMainThreadUntilIdleWithIdlingResources
|
|
||||||
import org.fnives.test.showcase.testutils.robot.RobotTestRule
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@HiltAndroidTest
|
|
||||||
class MainActivityTest {
|
|
||||||
|
|
||||||
private lateinit var activityScenario: ActivityScenario<HiltMainActivity>
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val snackbarVerificationTestRule = SpecificTestConfigurationsFactory.createSnackbarVerification()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val robotRule = RobotTestRule(HomeRobot())
|
|
||||||
private val homeRobot get() = robotRule.robot
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var networkSynchronization: NetworkSynchronization
|
|
||||||
|
|
||||||
private lateinit var disposable: Disposable
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
SpecificTestConfigurationsFactory.createServerTypeConfiguration()
|
|
||||||
.invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
|
|
||||||
hiltRule.inject()
|
|
||||||
disposable = networkSynchronization.registerNetworkingSynchronization()
|
|
||||||
homeRobot.setupLogin(mainDispatcherTestRule, mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
activityScenario.moveToState(Lifecycle.State.DESTROYED)
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN initialized MainActivity WHEN signout is clicked THEN user is signed out */
|
|
||||||
@Test
|
|
||||||
fun signOutClickedResultsInNavigation() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Error(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.clickSignOut()
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleOrActivityIsDestroyed()
|
|
||||||
|
|
||||||
homeRobot.assertNavigatedToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN success response WHEN data is returned THEN it is shown on the ui */
|
|
||||||
@Test
|
|
||||||
fun successfulDataLoadingShowsTheElementsOnTheUI() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Success(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
ContentData.contentSuccess.forEachIndexed { index, content ->
|
|
||||||
homeRobot.assertContainsItem(index, FavouriteContent(content, false))
|
|
||||||
}
|
|
||||||
homeRobot.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN success response WHEN item is clicked THEN ui is updated */
|
|
||||||
@Test
|
|
||||||
fun clickingOnListElementUpdatesTheElementsFavouriteState() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Success(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
homeRobot.clickOnContentItem(0, ContentData.contentSuccess.first())
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), true)
|
|
||||||
homeRobot.assertContainsItem(0, expectedItem)
|
|
||||||
.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN success response WHEN item is clicked THEN ui is updated even if activity is recreated */
|
|
||||||
@Test
|
|
||||||
fun elementFavouritedIsKeptEvenIfActivityIsRecreated() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Success(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
homeRobot.clickOnContentItem(0, ContentData.contentSuccess.first())
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), true)
|
|
||||||
|
|
||||||
activityScenario.moveToState(Lifecycle.State.DESTROYED)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.assertContainsItem(0, expectedItem)
|
|
||||||
.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN success response WHEN item is clicked then clicked again THEN ui is updated */
|
|
||||||
@Test
|
|
||||||
fun clickingAnElementMultipleTimesProperlyUpdatesIt() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Success(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
homeRobot.clickOnContentItem(0, ContentData.contentSuccess.first())
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
homeRobot.clickOnContentItem(0, ContentData.contentSuccess.first())
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), false)
|
|
||||||
homeRobot.assertContainsItem(0, expectedItem)
|
|
||||||
.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN error response WHEN loaded THEN error is Shown */
|
|
||||||
@Test
|
|
||||||
fun networkErrorResultsInUIErrorStateShown() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Error(false))
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.assertContainsNoItems()
|
|
||||||
.assertContainsError()
|
|
||||||
.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN error response then success WHEN retried THEN success is shown */
|
|
||||||
@Test
|
|
||||||
fun retryingFromErrorStateAndSucceedingShowsTheData() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(
|
|
||||||
ContentScenario.Error(false)
|
|
||||||
.then(ContentScenario.Success(false))
|
|
||||||
)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.swipeRefresh()
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loopMainThreadFor(2000L)
|
|
||||||
|
|
||||||
ContentData.contentSuccess.forEachIndexed { index, content ->
|
|
||||||
homeRobot.assertContainsItem(index, FavouriteContent(content, false))
|
|
||||||
}
|
|
||||||
homeRobot.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN success then error WHEN retried THEN error is shown */
|
|
||||||
@Test
|
|
||||||
fun errorIsShownIfTheDataIsFetchedAndErrorIsReceived() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(
|
|
||||||
ContentScenario.Success(false)
|
|
||||||
.then(ContentScenario.Error(false))
|
|
||||||
)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.swipeRefresh()
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loopMainThreadUntilIdleWithIdlingResources()
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(1000L)
|
|
||||||
loopMainThreadFor(1000)
|
|
||||||
|
|
||||||
homeRobot
|
|
||||||
.assertContainsError()
|
|
||||||
.assertContainsNoItems()
|
|
||||||
.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN unauthenticated then success WHEN loaded THEN success is shown */
|
|
||||||
@Test
|
|
||||||
fun authenticationIsHandledWithASingleLoading() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(
|
|
||||||
ContentScenario.Unauthorized(false)
|
|
||||||
.then(ContentScenario.Success(true))
|
|
||||||
)
|
|
||||||
.setScenario(RefreshTokenScenario.Success)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
ContentData.contentSuccess.forEachIndexed { index, content ->
|
|
||||||
homeRobot.assertContainsItem(index, FavouriteContent(content, false))
|
|
||||||
}
|
|
||||||
homeRobot.assertDidNotNavigateToAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN unauthenticated then error WHEN loaded THEN navigated to auth */
|
|
||||||
@Test
|
|
||||||
fun sessionExpirationResultsInNavigation() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
|
||||||
.setScenario(ContentScenario.Unauthorized(false))
|
|
||||||
.setScenario(RefreshTokenScenario.Error)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltMainActivity::class.java)
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
|
|
||||||
homeRobot.assertNavigatedToAuth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.login
|
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.test.core.app.ActivityScenario
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import org.fnives.test.showcase.R
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule
|
|
||||||
import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory
|
|
||||||
import org.fnives.test.showcase.testutils.idling.Disposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.NetworkSynchronization
|
|
||||||
import org.fnives.test.showcase.testutils.robot.RobotTestRule
|
|
||||||
import org.fnives.test.showcase.ui.auth.HiltAuthActivity
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@HiltAndroidTest
|
|
||||||
class AuthActivityTest {
|
|
||||||
|
|
||||||
private lateinit var activityScenario: ActivityScenario<HiltAuthActivity>
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val snackbarVerificationTestRule = SpecificTestConfigurationsFactory.createSnackbarVerification()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val robotRule = RobotTestRule(LoginRobot())
|
|
||||||
private val loginRobot get() = robotRule.robot
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var networkSynchronization: NetworkSynchronization
|
|
||||||
|
|
||||||
private lateinit var disposable: Disposable
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
SpecificTestConfigurationsFactory.createServerTypeConfiguration()
|
|
||||||
.invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
hiltRule.inject()
|
|
||||||
disposable = networkSynchronization.registerNetworkingSynchronization()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
activityScenario.moveToState(Lifecycle.State.DESTROYED)
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */
|
|
||||||
@Test
|
|
||||||
fun properLoginResultsInNavigationToHome() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario(
|
|
||||||
AuthScenario.Success(
|
|
||||||
password = "alma",
|
|
||||||
username = "banan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java)
|
|
||||||
loginRobot
|
|
||||||
.setPassword("alma")
|
|
||||||
.setUsername("banan")
|
|
||||||
.assertPassword("alma")
|
|
||||||
.assertUsername("banan")
|
|
||||||
.clickOnLogin()
|
|
||||||
.assertLoadingBeforeRequests()
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleOrActivityIsDestroyed()
|
|
||||||
loginRobot.assertNavigatedToHome()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN empty password and username WHEN signIn THEN error password is shown */
|
|
||||||
@Test
|
|
||||||
fun emptyPasswordShowsProperErrorMessage() {
|
|
||||||
activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java)
|
|
||||||
loginRobot
|
|
||||||
.setUsername("banan")
|
|
||||||
.assertUsername("banan")
|
|
||||||
.clickOnLogin()
|
|
||||||
.assertLoadingBeforeRequests()
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loginRobot.assertErrorIsShown(R.string.password_is_invalid)
|
|
||||||
.assertNotNavigatedToHome()
|
|
||||||
.assertNotLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN password and empty username WHEN signIn THEN error username is shown */
|
|
||||||
@Test
|
|
||||||
fun emptyUserNameShowsProperErrorMessage() {
|
|
||||||
activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java)
|
|
||||||
loginRobot
|
|
||||||
.setPassword("banan")
|
|
||||||
.assertPassword("banan")
|
|
||||||
.clickOnLogin()
|
|
||||||
.assertLoadingBeforeRequests()
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loginRobot.assertErrorIsShown(R.string.username_is_invalid)
|
|
||||||
.assertNotNavigatedToHome()
|
|
||||||
.assertNotLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */
|
|
||||||
@Test
|
|
||||||
fun invalidCredentialsGivenShowsProperErrorMessage() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario(
|
|
||||||
AuthScenario.InvalidCredentials(username = "alma", password = "banan")
|
|
||||||
)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java)
|
|
||||||
loginRobot
|
|
||||||
.setUsername("alma")
|
|
||||||
.setPassword("banan")
|
|
||||||
.assertUsername("alma")
|
|
||||||
.assertPassword("banan")
|
|
||||||
.clickOnLogin()
|
|
||||||
.assertLoadingBeforeRequests()
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loginRobot.assertErrorIsShown(R.string.credentials_invalid)
|
|
||||||
.assertNotNavigatedToHome()
|
|
||||||
.assertNotLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */
|
|
||||||
@Test
|
|
||||||
fun networkErrorShowsProperErrorMessage() {
|
|
||||||
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario(
|
|
||||||
AuthScenario.GenericError(username = "alma", password = "banan")
|
|
||||||
)
|
|
||||||
activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java)
|
|
||||||
loginRobot
|
|
||||||
.setUsername("alma")
|
|
||||||
.setPassword("banan")
|
|
||||||
.assertUsername("alma")
|
|
||||||
.assertPassword("banan")
|
|
||||||
.clickOnLogin()
|
|
||||||
.assertLoadingBeforeRequests()
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
|
||||||
loginRobot.assertErrorIsShown(R.string.something_went_wrong)
|
|
||||||
.assertNotNavigatedToHome()
|
|
||||||
.assertNotLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui.splash
|
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.test.core.app.ActivityScenario
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule
|
|
||||||
import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory
|
|
||||||
import org.fnives.test.showcase.testutils.idling.Disposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.NetworkSynchronization
|
|
||||||
import org.fnives.test.showcase.testutils.robot.RobotTestRule
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.koin.test.KoinTest
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@HiltAndroidTest
|
|
||||||
class SplashActivityTest : KoinTest {
|
|
||||||
|
|
||||||
private var activityScenario: ActivityScenario<HiltSplashActivity>? = null
|
|
||||||
|
|
||||||
private val splashRobot: SplashRobot get() = robotTestRule.robot
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val robotTestRule = RobotTestRule(SplashRobot())
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule()
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var networkSynchronization: NetworkSynchronization
|
|
||||||
|
|
||||||
var disposable: Disposable? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
SpecificTestConfigurationsFactory.createServerTypeConfiguration()
|
|
||||||
.invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
hiltRule.inject()
|
|
||||||
disposable = networkSynchronization.registerNetworkingSynchronization()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
activityScenario?.moveToState(Lifecycle.State.DESTROYED)
|
|
||||||
disposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN loggedInState WHEN opened THEN MainActivity is started */
|
|
||||||
@Test
|
|
||||||
fun loggedInStateNavigatesToHome() {
|
|
||||||
splashRobot.setupLoggedInState(mainDispatcherTestRule, mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(501)
|
|
||||||
|
|
||||||
splashRobot.assertHomeIsStarted()
|
|
||||||
.assertAuthIsNotStarted()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN loggedOffState WHEN opened THEN AuthActivity is started */
|
|
||||||
@Test
|
|
||||||
fun loggedOutStatesNavigatesToAuthentication() {
|
|
||||||
splashRobot.setupLoggedOutState(mainDispatcherTestRule)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(501)
|
|
||||||
|
|
||||||
splashRobot.assertAuthIsStarted()
|
|
||||||
.assertHomeIsNotStarted()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loggedOutStatesNotEnoughTime() {
|
|
||||||
splashRobot.setupLoggedOutState(mainDispatcherTestRule)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(10)
|
|
||||||
|
|
||||||
splashRobot.assertAuthIsNotStarted()
|
|
||||||
.assertHomeIsNotStarted()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GIVEN loggedInState and not enough time WHEN opened THEN no activity is started */
|
|
||||||
@Test
|
|
||||||
fun loggedInStatesNotEnoughTime() {
|
|
||||||
splashRobot.setupLoggedInState(mainDispatcherTestRule, mockServerScenarioSetupTestRule.mockServerScenarioSetup)
|
|
||||||
|
|
||||||
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
|
|
||||||
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(10)
|
|
||||||
|
|
||||||
splashRobot.assertHomeIsNotStarted()
|
|
||||||
.assertAuthIsNotStarted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package org.fnives.test.showcase.ui
|
|
||||||
|
|
||||||
import org.fnives.test.showcase.ui.auth.AuthActivity
|
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
|
||||||
import org.fnives.test.showcase.ui.splash.SplashActivity
|
|
||||||
|
|
||||||
object ActivityClassHolder {
|
|
||||||
|
|
||||||
fun authActivity() = AuthActivity::class
|
|
||||||
|
|
||||||
fun mainActivity() = MainActivity::class
|
|
||||||
|
|
||||||
fun splashActivity() = SplashActivity::class
|
|
||||||
}
|
|
||||||
17
build.gradle
17
build.gradle
|
|
@ -2,14 +2,12 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.6.10"
|
ext.kotlin_version = "1.6.10"
|
||||||
ext.detekt_version = "1.19.0"
|
ext.detekt_version = "1.19.0"
|
||||||
ext.hilt_version = "2.40.5"
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
|
||||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1"
|
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1"
|
||||||
|
|
@ -24,15 +22,6 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven {
|
|
||||||
url "https://maven.pkg.github.com/fknives/ReloadableHiltModule"
|
|
||||||
credentials {
|
|
||||||
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
|
|
||||||
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
|
|
||||||
}
|
|
||||||
// how to get token
|
|
||||||
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,17 +29,17 @@ task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
task unitTests(dependsOn: ["app:testKoinDebugUnitTest", "app:testHiltDebugUnitTest", "core:test", "network:test"]){
|
task unitTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]){
|
||||||
group = 'Tests'
|
group = 'Tests'
|
||||||
description = 'Run all unit tests'
|
description = 'Run all unit tests'
|
||||||
}
|
}
|
||||||
|
|
||||||
task robolectricTests(dependsOn: ["app:testKoinDebugUnitTest", "app:testHiltDebugUnitTest"]){
|
task robolectricTests(dependsOn: ["app:testDebugUnitTest"]){
|
||||||
group = 'Tests'
|
group = 'Tests'
|
||||||
description = 'Run all robolectric tests'
|
description = 'Run all robolectric tests'
|
||||||
}
|
}
|
||||||
|
|
||||||
task androidTests(dependsOn: ["app:connectedKoinDebugAndroidTest", "app:connectedHiltDebugAndroidTest"]){
|
task androidTests(dependsOn: ["app:connectedDebugAndroidTest"]){
|
||||||
group = 'Tests'
|
group = 'Tests'
|
||||||
description = 'Run all Android tests'
|
description = 'Run all Android tests'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,6 @@ java {
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
|
||||||
kotlinOptions {
|
|
||||||
freeCompilerArgs += ['-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
correctErrorTypes = true
|
correctErrorTypes = true
|
||||||
}
|
}
|
||||||
|
|
@ -25,18 +19,11 @@ dependencies {
|
||||||
api project(":model")
|
api project(":model")
|
||||||
implementation project(":network")
|
implementation project(":network")
|
||||||
|
|
||||||
// hilt
|
|
||||||
implementation "com.google.dagger:hilt-core:$hilt_version"
|
|
||||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
|
||||||
implementation "org.fnives.library.reloadable.module:annotation:$reloadable_module_version"
|
|
||||||
kapt "org.fnives.library.reloadable.module:annotation-processor:$reloadable_module_version"
|
|
||||||
|
|
||||||
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
kaptTest "com.google.dagger:dagger-compiler:$hilt_version"
|
|
||||||
testImplementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
testImplementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||||
testImplementation "app.cash.turbine:turbine:$turbine_version"
|
testImplementation "app.cash.turbine:turbine:$turbine_version"
|
||||||
}
|
}
|
||||||
|
|
@ -2,9 +2,8 @@ package org.fnives.test.showcase.core.content
|
||||||
|
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
import org.fnives.test.showcase.model.content.ContentId
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AddContentToFavouriteUseCase @Inject internal constructor(
|
class AddContentToFavouriteUseCase internal constructor(
|
||||||
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import org.fnives.test.showcase.core.di.hilt.LoggedInModuleInject
|
|
||||||
import org.fnives.test.showcase.core.shared.Optional
|
import org.fnives.test.showcase.core.shared.Optional
|
||||||
import org.fnives.test.showcase.core.shared.mapIntoResource
|
import org.fnives.test.showcase.core.shared.mapIntoResource
|
||||||
import org.fnives.test.showcase.core.shared.wrapIntoAnswer
|
import org.fnives.test.showcase.core.shared.wrapIntoAnswer
|
||||||
|
|
@ -15,7 +14,7 @@ import org.fnives.test.showcase.model.content.Content
|
||||||
import org.fnives.test.showcase.model.shared.Resource
|
import org.fnives.test.showcase.model.shared.Resource
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSource
|
import org.fnives.test.showcase.network.content.ContentRemoteSource
|
||||||
|
|
||||||
internal class ContentRepository @LoggedInModuleInject constructor(
|
internal class ContentRepository(
|
||||||
private val contentRemoteSource: ContentRemoteSource
|
private val contentRemoteSource: ContentRemoteSource
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package org.fnives.test.showcase.core.content
|
package org.fnives.test.showcase.core.content
|
||||||
|
|
||||||
import javax.inject.Inject
|
class FetchContentUseCase internal constructor(private val contentRepository: ContentRepository) {
|
||||||
|
|
||||||
class FetchContentUseCase @Inject internal constructor(private val contentRepository: ContentRepository) {
|
|
||||||
|
|
||||||
fun invoke() = contentRepository.fetch()
|
fun invoke() = contentRepository.fetch()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import org.fnives.test.showcase.model.content.Content
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
import org.fnives.test.showcase.model.content.ContentId
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
import org.fnives.test.showcase.model.shared.Resource
|
import org.fnives.test.showcase.model.shared.Resource
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetAllContentUseCase @Inject internal constructor(
|
class GetAllContentUseCase internal constructor(
|
||||||
private val contentRepository: ContentRepository,
|
private val contentRepository: ContentRepository,
|
||||||
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ package org.fnives.test.showcase.core.content
|
||||||
|
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
import org.fnives.test.showcase.model.content.ContentId
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RemoveContentFromFavouritesUseCase @Inject internal constructor(
|
class RemoveContentFromFavouritesUseCase internal constructor(
|
||||||
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
private val favouriteContentLocalStorage: FavouriteContentLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
||||||
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.scope.Scope
|
import org.koin.core.scope.Scope
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
@ -42,7 +42,7 @@ fun repositoryModule() = module {
|
||||||
|
|
||||||
fun useCaseModule() = module {
|
fun useCaseModule() = module {
|
||||||
factory { LoginUseCase(get(), get()) }
|
factory { LoginUseCase(get(), get()) }
|
||||||
factory { LogoutUseCase(get(), null) }
|
factory { LogoutUseCase(get()) }
|
||||||
factory { GetAllContentUseCase(get(), get()) }
|
factory { GetAllContentUseCase(get(), get()) }
|
||||||
factory { AddContentToFavouriteUseCase(get()) }
|
factory { AddContentToFavouriteUseCase(get()) }
|
||||||
factory { RemoveContentFromFavouritesUseCase(get()) }
|
factory { RemoveContentFromFavouritesUseCase(get()) }
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package org.fnives.test.showcase.core.di.hilt
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import org.fnives.test.showcase.core.login.LogoutUseCase
|
|
||||||
import org.fnives.test.showcase.core.session.SessionExpirationAdapter
|
|
||||||
import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
@Module
|
|
||||||
object CoreModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun bindNetworkSessionLocalStorageAdapter(
|
|
||||||
networkSessionLocalStorageAdapter: NetworkSessionLocalStorageAdapter
|
|
||||||
): NetworkSessionLocalStorage = networkSessionLocalStorageAdapter
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun bindNetworkSessionExpirationListener(
|
|
||||||
sessionExpirationAdapter: SessionExpirationAdapter
|
|
||||||
): NetworkSessionExpirationListener = sessionExpirationAdapter
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideLogoutUseCase(
|
|
||||||
storage: UserDataLocalStorage,
|
|
||||||
reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule
|
|
||||||
): LogoutUseCase = LogoutUseCase(storage, reloadLoggedInModuleInjectModule)
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package org.fnives.test.showcase.core.di.hilt
|
|
||||||
|
|
||||||
import org.fnives.library.reloadable.module.annotation.ReloadableModule
|
|
||||||
|
|
||||||
@ReloadableModule
|
|
||||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
annotation class LoggedInModuleInject
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package org.fnives.test.showcase.core.login
|
package org.fnives.test.showcase.core.login
|
||||||
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class IsUserLoggedInUseCase @Inject constructor(
|
class IsUserLoggedInUseCase(
|
||||||
private val userDataLocalStorage: UserDataLocalStorage
|
private val userDataLocalStorage: UserDataLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import org.fnives.test.showcase.model.auth.LoginStatus
|
||||||
import org.fnives.test.showcase.model.shared.Answer
|
import org.fnives.test.showcase.model.shared.Answer
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSource
|
import org.fnives.test.showcase.network.auth.LoginRemoteSource
|
||||||
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class LoginUseCase @Inject internal constructor(
|
class LoginUseCase internal constructor(
|
||||||
private val loginRemoteSource: LoginRemoteSource,
|
private val loginRemoteSource: LoginRemoteSource,
|
||||||
private val userDataLocalStorage: UserDataLocalStorage
|
private val userDataLocalStorage: UserDataLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
package org.fnives.test.showcase.core.login
|
package org.fnives.test.showcase.core.login
|
||||||
|
|
||||||
import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule
|
|
||||||
import org.fnives.test.showcase.core.di.koin.repositoryModule
|
import org.fnives.test.showcase.core.di.koin.repositoryModule
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
||||||
import org.koin.core.context.loadKoinModules
|
import org.koin.core.context.loadKoinModules
|
||||||
import org.koin.mp.KoinPlatformTools
|
|
||||||
|
|
||||||
class LogoutUseCase(
|
class LogoutUseCase(
|
||||||
private val storage: UserDataLocalStorage,
|
private val storage: UserDataLocalStorage
|
||||||
private val reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule?
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun invoke() {
|
suspend fun invoke() {
|
||||||
if (KoinPlatformTools.defaultContext().getOrNull() == null) {
|
loadKoinModules(repositoryModule())
|
||||||
reloadLoggedInModuleInjectModule?.reload()
|
|
||||||
} else {
|
|
||||||
loadKoinModules(repositoryModule())
|
|
||||||
}
|
|
||||||
storage.session = null
|
storage.session = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package org.fnives.test.showcase.core.session
|
package org.fnives.test.showcase.core.session
|
||||||
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class SessionExpirationAdapter @Inject constructor(
|
internal class SessionExpirationAdapter(
|
||||||
private val sessionExpirationListener: SessionExpirationListener
|
private val sessionExpirationListener: SessionExpirationListener
|
||||||
) : NetworkSessionExpirationListener {
|
) : NetworkSessionExpirationListener {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ package org.fnives.test.showcase.core.storage
|
||||||
|
|
||||||
import org.fnives.test.showcase.model.session.Session
|
import org.fnives.test.showcase.model.session.Session
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class NetworkSessionLocalStorageAdapter @Inject constructor(
|
internal class NetworkSessionLocalStorageAdapter(
|
||||||
private val userDataLocalStorage: UserDataLocalStorage
|
private val userDataLocalStorage: UserDataLocalStorage
|
||||||
) : NetworkSessionLocalStorage {
|
) : NetworkSessionLocalStorage {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package org.fnives.test.showcase.core.login.koin
|
package org.fnives.test.showcase.core.login
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.fnives.test.showcase.core.content.ContentRepository
|
import org.fnives.test.showcase.core.content.ContentRepository
|
||||||
import org.fnives.test.showcase.core.di.koin.createCoreModule
|
import org.fnives.test.showcase.core.di.koin.createCoreModule
|
||||||
import org.fnives.test.showcase.core.login.LogoutUseCase
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
|
@ -31,7 +30,7 @@ internal class LogoutUseCaseTest : KoinTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockUserDataLocalStorage = mock()
|
mockUserDataLocalStorage = mock()
|
||||||
sut = LogoutUseCase(mockUserDataLocalStorage, null)
|
sut = LogoutUseCase(mockUserDataLocalStorage)
|
||||||
startKoin {
|
startKoin {
|
||||||
modules(
|
modules(
|
||||||
createCoreModule(
|
createCoreModule(
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package org.fnives.test.showcase.core.login.hilt
|
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.fnives.test.showcase.core.content.ContentRepository
|
|
||||||
import org.fnives.test.showcase.core.login.LogoutUseCase
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.DisplayName
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.mockito.kotlin.times
|
|
||||||
import org.mockito.kotlin.verify
|
|
||||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
|
||||||
import org.mockito.kotlin.verifyZeroInteractions
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
internal class LogoutUseCaseTest {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var sut: LogoutUseCase
|
|
||||||
private lateinit var mockUserDataLocalStorage: UserDataLocalStorage
|
|
||||||
private lateinit var testCoreComponent: TestCoreComponent
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var contentRepository: ContentRepository
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
mockUserDataLocalStorage = mock()
|
|
||||||
testCoreComponent = DaggerTestCoreComponent.builder()
|
|
||||||
.setBaseUrl("https://a.b.com")
|
|
||||||
.setEnableLogging(true)
|
|
||||||
.setSessionExpirationListener(mock())
|
|
||||||
.setUserDataLocalStorage(mockUserDataLocalStorage)
|
|
||||||
.build()
|
|
||||||
testCoreComponent.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("WHEN no call THEN storage is not interacted")
|
|
||||||
@Test
|
|
||||||
fun initializedDoesntAffectStorage() {
|
|
||||||
verifyZeroInteractions(mockUserDataLocalStorage)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("WHEN logout invoked THEN storage is cleared")
|
|
||||||
@Test
|
|
||||||
fun logoutResultsInStorageCleaning() = runTest {
|
|
||||||
val repositoryBefore = contentRepository
|
|
||||||
|
|
||||||
sut.invoke()
|
|
||||||
|
|
||||||
testCoreComponent.inject(this@LogoutUseCaseTest)
|
|
||||||
val repositoryAfter = contentRepository
|
|
||||||
verify(mockUserDataLocalStorage, times(1)).session = null
|
|
||||||
verifyNoMoreInteractions(mockUserDataLocalStorage)
|
|
||||||
Assertions.assertNotSame(repositoryBefore, repositoryAfter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package org.fnives.test.showcase.core.login.hilt
|
|
||||||
|
|
||||||
import dagger.BindsInstance
|
|
||||||
import dagger.Component
|
|
||||||
import org.fnives.test.showcase.core.di.hilt.CoreModule
|
|
||||||
import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModuleImpl
|
|
||||||
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
|
||||||
import org.fnives.test.showcase.core.storage.UserDataLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class, BindsBaseOkHttpClient::class])
|
|
||||||
internal interface TestCoreComponent {
|
|
||||||
|
|
||||||
@Component.Builder
|
|
||||||
interface Builder {
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setBaseUrl(baseUrl: String): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setEnableLogging(enableLogging: Boolean): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setSessionExpirationListener(listener: SessionExpirationListener): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setUserDataLocalStorage(storage: UserDataLocalStorage): Builder
|
|
||||||
|
|
||||||
fun build(): TestCoreComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inject(logoutUseCaseTest: LogoutUseCaseTest)
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,13 @@ subprojects { module ->
|
||||||
showStandardStreams true
|
showStandardStreams true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
module.tasks.configureEach { task ->
|
||||||
|
if (task.taskIdentity.type.toString() == "class org.jetbrains.kotlin.gradle.tasks.KotlinCompile") {
|
||||||
|
task.kotlinOptions {
|
||||||
|
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.withId("com.android.application") {
|
plugins.withId("com.android.application") {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ project.ext {
|
||||||
retrofit_version = "2.9.0"
|
retrofit_version = "2.9.0"
|
||||||
okhttp_version = "4.9.1"
|
okhttp_version = "4.9.1"
|
||||||
moshi_version = "1.13.0"
|
moshi_version = "1.13.0"
|
||||||
reloadable_module_version = "0.1.0"
|
|
||||||
|
|
||||||
testing_androidx_code_version = "1.4.0"
|
testing_androidx_code_version = "1.4.0"
|
||||||
testing_androidx_junit_version = "1.1.3"
|
testing_androidx_junit_version = "1.1.3"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package org.fnives.test.showcase.hilt
|
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
annotation class SessionLessQualifier
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package org.fnives.test.showcase.hilt
|
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
annotation class SessionQualifier
|
|
||||||
|
|
@ -21,9 +21,6 @@ dependencies {
|
||||||
// koin
|
// koin
|
||||||
api "io.insert-koin:koin-core:$koin_version"
|
api "io.insert-koin:koin-core:$koin_version"
|
||||||
|
|
||||||
// hilt
|
|
||||||
implementation "com.google.dagger:hilt-core:$hilt_version"
|
|
||||||
|
|
||||||
api project(":model")
|
api project(":model")
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||||
|
|
@ -33,6 +30,4 @@ dependencies {
|
||||||
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
||||||
testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version"
|
testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version"
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
|
||||||
kaptTest "com.google.dagger:dagger-compiler:$hilt_version"
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,9 +7,8 @@ import org.fnives.test.showcase.network.shared.ExceptionWrapper
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class LoginErrorConverter @Inject constructor() {
|
internal class LoginErrorConverter {
|
||||||
|
|
||||||
@Throws(ParsingException::class)
|
@Throws(ParsingException::class)
|
||||||
suspend fun invoke(request: suspend () -> Response<LoginResponse>): LoginStatusResponses =
|
suspend fun invoke(request: suspend () -> Response<LoginResponse>): LoginStatusResponses =
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
||||||
import org.fnives.test.showcase.network.shared.ExceptionWrapper
|
import org.fnives.test.showcase.network.shared.ExceptionWrapper
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class LoginRemoteSourceImpl @Inject constructor(
|
internal class LoginRemoteSourceImpl(
|
||||||
private val loginService: LoginService,
|
private val loginService: LoginService,
|
||||||
private val loginErrorConverter: LoginErrorConverter
|
private val loginErrorConverter: LoginErrorConverter
|
||||||
) : LoginRemoteSource {
|
) : LoginRemoteSource {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import org.fnives.test.showcase.model.content.Content
|
||||||
import org.fnives.test.showcase.model.content.ContentId
|
import org.fnives.test.showcase.model.content.ContentId
|
||||||
import org.fnives.test.showcase.model.content.ImageUrl
|
import org.fnives.test.showcase.model.content.ImageUrl
|
||||||
import org.fnives.test.showcase.network.shared.ExceptionWrapper
|
import org.fnives.test.showcase.network.shared.ExceptionWrapper
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class ContentRemoteSourceImpl @Inject constructor(
|
internal class ContentRemoteSourceImpl(
|
||||||
private val contentService: ContentService
|
private val contentService: ContentService
|
||||||
) : ContentRemoteSource {
|
) : ContentRemoteSource {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.network.di.koin
|
package org.fnives.test.showcase.network.di
|
||||||
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
|
|
@ -9,7 +9,6 @@ import org.fnives.test.showcase.network.auth.LoginService
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSource
|
import org.fnives.test.showcase.network.content.ContentRemoteSource
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
||||||
import org.fnives.test.showcase.network.content.ContentService
|
import org.fnives.test.showcase.network.content.ContentService
|
||||||
import org.fnives.test.showcase.network.di.setupLogging
|
|
||||||
import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor
|
import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor
|
||||||
import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils
|
import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.di.hilt
|
|
||||||
|
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.fnives.test.showcase.hilt.SessionLessQualifier
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
@Module
|
|
||||||
abstract class BindsBaseOkHttpClient {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@SessionLessQualifier
|
|
||||||
abstract fun bindsSessionLess(okHttpClient: OkHttpClient): OkHttpClient
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.di.hilt
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.fnives.test.showcase.hilt.SessionLessQualifier
|
|
||||||
import org.fnives.test.showcase.hilt.SessionQualifier
|
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSource
|
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl
|
|
||||||
import org.fnives.test.showcase.network.auth.LoginService
|
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSource
|
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
|
||||||
import org.fnives.test.showcase.network.content.ContentService
|
|
||||||
import org.fnives.test.showcase.network.di.setupLogging
|
|
||||||
import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor
|
|
||||||
import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils
|
|
||||||
import org.fnives.test.showcase.network.session.SessionAuthenticator
|
|
||||||
import org.fnives.test.showcase.network.shared.PlatformInterceptor
|
|
||||||
import retrofit2.Converter
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
@Module
|
|
||||||
object HiltNetworkModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideConverterFactory(): Converter.Factory = MoshiConverterFactory.create()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideSessionLessOkHttpClient(enableLogging: Boolean) =
|
|
||||||
OkHttpClient.Builder()
|
|
||||||
.addInterceptor(PlatformInterceptor())
|
|
||||||
.setupLogging(enableLogging)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@SessionLessQualifier
|
|
||||||
fun provideSessionLessRetrofit(
|
|
||||||
baseUrl: String,
|
|
||||||
converterFactory: Converter.Factory,
|
|
||||||
@SessionLessQualifier okHttpClient: OkHttpClient
|
|
||||||
) = Retrofit.Builder()
|
|
||||||
.baseUrl(baseUrl)
|
|
||||||
.addConverterFactory(converterFactory)
|
|
||||||
.client(okHttpClient)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@SessionQualifier
|
|
||||||
internal fun provideSessionOkHttpClient(
|
|
||||||
@SessionLessQualifier okHttpClient: OkHttpClient,
|
|
||||||
sessionAuthenticator: SessionAuthenticator,
|
|
||||||
authenticationHeaderUtils: AuthenticationHeaderUtils
|
|
||||||
) =
|
|
||||||
okHttpClient
|
|
||||||
.newBuilder()
|
|
||||||
.authenticator(sessionAuthenticator)
|
|
||||||
.addInterceptor(AuthenticationHeaderInterceptor(authenticationHeaderUtils))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@SessionQualifier
|
|
||||||
fun provideSessionRetrofit(
|
|
||||||
@SessionLessQualifier retrofit: Retrofit,
|
|
||||||
@SessionQualifier okHttpClient: OkHttpClient
|
|
||||||
) = retrofit.newBuilder()
|
|
||||||
.client(okHttpClient)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun bindContentRemoteSource(
|
|
||||||
contentRemoteSourceImpl: ContentRemoteSourceImpl
|
|
||||||
): ContentRemoteSource = contentRemoteSourceImpl
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun bindLoginRemoteSource(
|
|
||||||
loginRemoteSource: LoginRemoteSourceImpl
|
|
||||||
): LoginRemoteSource = loginRemoteSource
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun provideLoginService(@SessionLessQualifier retrofit: Retrofit): LoginService =
|
|
||||||
retrofit.create(LoginService::class.java)
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
internal fun provideContentService(@SessionQualifier retrofit: Retrofit): ContentService =
|
|
||||||
retrofit.create(ContentService::class.java)
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package org.fnives.test.showcase.network.session
|
package org.fnives.test.showcase.network.session
|
||||||
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class AuthenticationHeaderUtils @Inject constructor(
|
internal class AuthenticationHeaderUtils(
|
||||||
private val networkSessionLocalStorage: NetworkSessionLocalStorage
|
private val networkSessionLocalStorage: NetworkSessionLocalStorage
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.Route
|
import okhttp3.Route
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl
|
import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class SessionAuthenticator @Inject constructor(
|
internal class SessionAuthenticator(
|
||||||
private val networkSessionLocalStorage: NetworkSessionLocalStorage,
|
private val networkSessionLocalStorage: NetworkSessionLocalStorage,
|
||||||
private val loginRemoteSource: LoginRemoteSourceImpl,
|
private val loginRemoteSource: LoginRemoteSourceImpl,
|
||||||
private val authenticationHeaderUtils: AuthenticationHeaderUtils,
|
private val authenticationHeaderUtils: AuthenticationHeaderUtils,
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package org.fnives.test.showcase.network
|
|
||||||
|
|
||||||
import dagger.BindsInstance
|
|
||||||
import dagger.Component
|
|
||||||
import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceRefreshActionImplTest
|
|
||||||
import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceTest
|
|
||||||
import org.fnives.test.showcase.network.content.hilt.ContentRemoteSourceImplTest
|
|
||||||
import org.fnives.test.showcase.network.content.hilt.SessionExpirationTest
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient
|
|
||||||
import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Component(modules = [HiltNetworkModule::class, BindsBaseOkHttpClient::class])
|
|
||||||
interface TestNetworkComponent {
|
|
||||||
|
|
||||||
@Component.Builder
|
|
||||||
interface Builder {
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setBaseUrl(baseUrl: String): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setEnableLogging(enableLogging: Boolean): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setNetworkSessionLocalStorage(storage: NetworkSessionLocalStorage): Builder
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
fun setNetworkSessionExpirationListener(listener: NetworkSessionExpirationListener): Builder
|
|
||||||
|
|
||||||
fun build(): TestNetworkComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inject(contentRemoteSourceImplTest: ContentRemoteSourceImplTest)
|
|
||||||
|
|
||||||
fun inject(sessionExpirationTest: SessionExpirationTest)
|
|
||||||
|
|
||||||
fun inject(loginRemoteSourceRefreshActionImplTest: LoginRemoteSourceRefreshActionImplTest)
|
|
||||||
|
|
||||||
fun inject(loginRemoteSourceTest: LoginRemoteSourceTest)
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package org.fnives.test.showcase.network.auth
|
package org.fnives.test.showcase.network.auth
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okhttp3.internal.http.RealResponseBody
|
import okhttp3.internal.http.RealResponseBody
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import org.fnives.test.showcase.model.session.Session
|
import org.fnives.test.showcase.model.session.Session
|
||||||
|
|
@ -18,6 +19,7 @@ import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class LoginErrorConverterTest {
|
class LoginErrorConverterTest {
|
||||||
|
|
||||||
private lateinit var sut: LoginErrorConverter
|
private lateinit var sut: LoginErrorConverter
|
||||||
|
|
@ -49,7 +51,7 @@ class LoginErrorConverterTest {
|
||||||
|
|
||||||
@DisplayName("GIVEN 400 error response WHEN parsing login error THEN invalid credentials is returned")
|
@DisplayName("GIVEN 400 error response WHEN parsing login error THEN invalid credentials is returned")
|
||||||
@Test
|
@Test
|
||||||
fun code400ResponseResultsInInvalidCredentials() = runBlockingTest {
|
fun code400ResponseResultsInInvalidCredentials() = runTest {
|
||||||
val expected = LoginStatusResponses.InvalidCredentials
|
val expected = LoginStatusResponses.InvalidCredentials
|
||||||
|
|
||||||
val actual = sut.invoke {
|
val actual = sut.invoke {
|
||||||
|
|
@ -62,7 +64,7 @@ class LoginErrorConverterTest {
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN parsing login error THEN successful response is returned")
|
@DisplayName("GIVEN successful response WHEN parsing login error THEN successful response is returned")
|
||||||
@Test
|
@Test
|
||||||
fun successResponseResultsInSessionResponse() = runBlockingTest {
|
fun successResponseResultsInSessionResponse() = runTest {
|
||||||
val loginResponse = LoginResponse("a", "r")
|
val loginResponse = LoginResponse("a", "r")
|
||||||
val expectedSession = Session(accessToken = loginResponse.accessToken, refreshToken = loginResponse.refreshToken)
|
val expectedSession = Session(accessToken = loginResponse.accessToken, refreshToken = loginResponse.refreshToken)
|
||||||
val expected = LoginStatusResponses.Success(expectedSession)
|
val expected = LoginStatusResponses.Success(expectedSession)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package org.fnives.test.showcase.network.auth.koin
|
package org.fnives.test.showcase.network.auth
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
import org.fnives.test.showcase.network.mockserver.ContentData
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package org.fnives.test.showcase.network.auth.koin
|
package org.fnives.test.showcase.network.auth
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSource
|
|
||||||
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
import org.fnives.test.showcase.network.mockserver.ContentData
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson
|
import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.auth.hilt
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.fnives.test.showcase.network.DaggerTestNetworkComponent
|
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.DisplayName
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
class LoginRemoteSourceRefreshActionImplTest {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
internal lateinit var sut: LoginRemoteSourceImpl
|
|
||||||
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
|
|
||||||
private val mockServerScenarioSetup
|
|
||||||
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
mockNetworkSessionLocalStorage = mock()
|
|
||||||
DaggerTestNetworkComponent.builder()
|
|
||||||
.setBaseUrl(mockServerScenarioSetupExtensions.url)
|
|
||||||
.setEnableLogging(true)
|
|
||||||
.setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage)
|
|
||||||
.setNetworkSessionExpirationListener(mock())
|
|
||||||
.build()
|
|
||||||
.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN refresh request is fired THEN session is returned")
|
|
||||||
@Test
|
|
||||||
fun successResponseResultsInSession() = runBlocking {
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success)
|
|
||||||
val expected = ContentData.refreshSuccessResponse
|
|
||||||
|
|
||||||
val actual = sut.refresh(ContentData.refreshSuccessResponse.refreshToken)
|
|
||||||
|
|
||||||
Assertions.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN refresh request is fired THEN the request is setup properly")
|
|
||||||
@Test
|
|
||||||
fun refreshRequestIsSetupProperly() = runBlocking {
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false)
|
|
||||||
|
|
||||||
sut.refresh(ContentData.refreshSuccessResponse.refreshToken)
|
|
||||||
val request = mockServerScenarioSetup.takeRequest()
|
|
||||||
|
|
||||||
Assertions.assertEquals("PUT", request.method)
|
|
||||||
Assertions.assertEquals("Android", request.getHeader("Platform"))
|
|
||||||
Assertions.assertEquals(null, request.getHeader("Authorization"))
|
|
||||||
Assertions.assertEquals(
|
|
||||||
"/login/${ContentData.refreshSuccessResponse.refreshToken}",
|
|
||||||
request.path
|
|
||||||
)
|
|
||||||
Assertions.assertEquals("", request.body.readUtf8())
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN internal error response WHEN refresh request is fired THEN network exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun generalErrorResponseResultsInNetworkException() {
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error)
|
|
||||||
|
|
||||||
Assertions.assertThrows(NetworkException::class.java) {
|
|
||||||
runBlocking { sut.refresh(ContentData.refreshSuccessResponse.refreshToken) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN invalid json response WHEN refresh request is fired THEN network exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun jsonErrorResponseResultsInParsingException() {
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse)
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN malformed json response WHEN refresh request is fired THEN parsing exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun malformedJsonErrorResponseResultsInParsingException() {
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson)
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.auth.hilt
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
|
||||||
import org.fnives.test.showcase.network.DaggerTestNetworkComponent
|
|
||||||
import org.fnives.test.showcase.network.auth.LoginRemoteSource
|
|
||||||
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.DisplayName
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.skyscreamer.jsonassert.JSONAssert
|
|
||||||
import org.skyscreamer.jsonassert.JSONCompareMode
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
class LoginRemoteSourceTest {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
internal lateinit var sut: LoginRemoteSource
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
|
|
||||||
private val mockServerScenarioSetup
|
|
||||||
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
val mockNetworkSessionLocalStorage = mock<NetworkSessionLocalStorage>()
|
|
||||||
DaggerTestNetworkComponent.builder()
|
|
||||||
.setBaseUrl(mockServerScenarioSetupExtensions.url)
|
|
||||||
.setEnableLogging(true)
|
|
||||||
.setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage)
|
|
||||||
.setNetworkSessionExpirationListener(mock())
|
|
||||||
.build()
|
|
||||||
.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned")
|
|
||||||
@Test
|
|
||||||
fun successResponseIsParsedProperly() = runBlocking {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b"))
|
|
||||||
val expected = LoginStatusResponses.Success(ContentData.loginSuccessResponse)
|
|
||||||
|
|
||||||
val actual = sut.login(LoginCredentials("a", "b"))
|
|
||||||
|
|
||||||
Assertions.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly")
|
|
||||||
@Test
|
|
||||||
fun requestProperlySetup() = runBlocking {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b"), false)
|
|
||||||
|
|
||||||
sut.login(LoginCredentials("a", "b"))
|
|
||||||
val request = mockServerScenarioSetup.takeRequest()
|
|
||||||
|
|
||||||
Assertions.assertEquals("POST", request.method)
|
|
||||||
Assertions.assertEquals("Android", request.getHeader("Platform"))
|
|
||||||
Assertions.assertEquals(null, request.getHeader("Authorization"))
|
|
||||||
Assertions.assertEquals("/login", request.path)
|
|
||||||
val loginRequest = createExpectedLoginRequestJson("a", "b")
|
|
||||||
JSONAssert.assertEquals(
|
|
||||||
loginRequest,
|
|
||||||
request.body.readUtf8(),
|
|
||||||
JSONCompareMode.NON_EXTENSIBLE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned")
|
|
||||||
@Test
|
|
||||||
fun badRequestMeansInvalidCredentials() = runBlocking {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.InvalidCredentials("a", "b"))
|
|
||||||
val expected = LoginStatusResponses.InvalidCredentials
|
|
||||||
|
|
||||||
val actual = sut.login(LoginCredentials("a", "b"))
|
|
||||||
|
|
||||||
Assertions.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun genericErrorMeansNetworkError() {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.GenericError("a", "b"))
|
|
||||||
|
|
||||||
Assertions.assertThrows(NetworkException::class.java) {
|
|
||||||
runBlocking { sut.login(LoginCredentials("a", "b")) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun invalidJsonMeansParsingException() {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.UnexpectedJsonAsSuccessResponse("a", "b"))
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.login(LoginCredentials("a", "b")) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown")
|
|
||||||
@Test
|
|
||||||
fun malformedJsonMeansParsingException() {
|
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.MalformedJsonAsSuccessResponse("a", "b"))
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.login(LoginCredentials("a", "b")) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@ package org.fnives.test.showcase.network.content
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package org.fnives.test.showcase.network.content.koin
|
package org.fnives.test.showcase.network.content
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
import org.fnives.test.showcase.network.mockserver.ContentData
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package org.fnives.test.showcase.network.content.koin
|
package org.fnives.test.showcase.network.content
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
import org.fnives.test.showcase.model.network.BaseUrl
|
||||||
import org.fnives.test.showcase.model.session.Session
|
import org.fnives.test.showcase.model.session.Session
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
import org.fnives.test.showcase.network.di.createNetworkModules
|
||||||
import org.fnives.test.showcase.network.di.koin.createNetworkModules
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
import org.fnives.test.showcase.network.mockserver.ContentData
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.content.hilt
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.fnives.test.showcase.network.DaggerTestNetworkComponent
|
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.DisplayName
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension
|
|
||||||
import org.koin.test.inject
|
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.mockito.kotlin.whenever
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
class ContentRemoteSourceImplTest {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
internal lateinit var sut: ContentRemoteSourceImpl
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
|
|
||||||
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
|
|
||||||
private val mockServerScenarioSetup
|
|
||||||
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
mockNetworkSessionLocalStorage = mock()
|
|
||||||
DaggerTestNetworkComponent.builder()
|
|
||||||
.setBaseUrl(mockServerScenarioSetupExtensions.url)
|
|
||||||
.setEnableLogging(true)
|
|
||||||
.setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage)
|
|
||||||
.setNetworkSessionExpirationListener(mock())
|
|
||||||
.build()
|
|
||||||
.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN getting content THEN its parsed and returned correctly")
|
|
||||||
@Test
|
|
||||||
fun successResponseParsing() = runBlocking {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.Success(false))
|
|
||||||
val expected = ContentData.contentSuccess
|
|
||||||
|
|
||||||
val actual = sut.get()
|
|
||||||
|
|
||||||
Assertions.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN successful response WHEN getting content THEN the request is setup properly")
|
|
||||||
@Test
|
|
||||||
fun successResponseRequestIsCorrect() = runBlocking {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.Success(false), false)
|
|
||||||
|
|
||||||
sut.get()
|
|
||||||
val request = mockServerScenarioSetup.takeRequest()
|
|
||||||
|
|
||||||
Assertions.assertEquals("GET", request.method)
|
|
||||||
Assertions.assertEquals("Android", request.getHeader("Platform"))
|
|
||||||
Assertions.assertEquals(ContentData.loginSuccessResponse.accessToken, request.getHeader("Authorization"))
|
|
||||||
Assertions.assertEquals("/content", request.path)
|
|
||||||
Assertions.assertEquals("", request.body.readUtf8())
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN response with missing Field WHEN getting content THEN invalid is ignored others are returned")
|
|
||||||
@Test
|
|
||||||
fun dataMissingFieldIsIgnored() = runBlocking {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false))
|
|
||||||
|
|
||||||
val expected = ContentData.contentSuccessWithMissingFields
|
|
||||||
|
|
||||||
val actual = sut.get()
|
|
||||||
|
|
||||||
Assertions.assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN error response WHEN getting content THEN network request is thrown")
|
|
||||||
@Test
|
|
||||||
fun errorResponseResultsInNetworkException() {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.Error(false))
|
|
||||||
|
|
||||||
Assertions.assertThrows(NetworkException::class.java) {
|
|
||||||
runBlocking { sut.get() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN unexpected json response WHEN getting content THEN parsing request is thrown")
|
|
||||||
@Test
|
|
||||||
fun unexpectedJSONResultsInParsingException() {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false))
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.get() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN malformed json response WHEN getting content THEN parsing request is thrown")
|
|
||||||
@Test
|
|
||||||
fun malformedJSONResultsInParsingException() {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false))
|
|
||||||
|
|
||||||
Assertions.assertThrows(ParsingException::class.java) {
|
|
||||||
runBlocking { sut.get() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
package org.fnives.test.showcase.network.content.hilt
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.fnives.test.showcase.model.session.Session
|
|
||||||
import org.fnives.test.showcase.network.DaggerTestNetworkComponent
|
|
||||||
import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl
|
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario
|
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
|
|
||||||
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
|
|
||||||
import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions
|
|
||||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.DisplayName
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension
|
|
||||||
import org.koin.test.inject
|
|
||||||
import org.mockito.kotlin.anyOrNull
|
|
||||||
import org.mockito.kotlin.doAnswer
|
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.mockito.kotlin.times
|
|
||||||
import org.mockito.kotlin.verify
|
|
||||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
|
||||||
import org.mockito.kotlin.verifyZeroInteractions
|
|
||||||
import org.mockito.kotlin.whenever
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
class SessionExpirationTest {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
internal lateinit var sut: ContentRemoteSourceImpl
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
@JvmField
|
|
||||||
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
|
|
||||||
private val mockServerScenarioSetup
|
|
||||||
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
|
|
||||||
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
|
|
||||||
private lateinit var mockNetworkSessionExpirationListener: NetworkSessionExpirationListener
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
mockNetworkSessionLocalStorage = mock()
|
|
||||||
mockNetworkSessionExpirationListener = mock()
|
|
||||||
DaggerTestNetworkComponent.builder()
|
|
||||||
.setBaseUrl(mockServerScenarioSetupExtensions.url)
|
|
||||||
.setEnableLogging(true)
|
|
||||||
.setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage)
|
|
||||||
.setNetworkSessionExpirationListener(mockNetworkSessionExpirationListener)
|
|
||||||
.build()
|
|
||||||
.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN 401 THEN refresh token ok response WHEN content requested THE tokens are refreshed and request retried with new tokens")
|
|
||||||
@Test
|
|
||||||
fun successRefreshResultsInRequestRetry() = runBlocking {
|
|
||||||
var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse
|
|
||||||
mockServerScenarioSetup.setScenario(
|
|
||||||
ContentScenario.Unauthorized(false)
|
|
||||||
.then(ContentScenario.Success(true)),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false)
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock }
|
|
||||||
doAnswer { sessionToReturnByMock = it.arguments[0] as Session? }
|
|
||||||
.whenever(mockNetworkSessionLocalStorage).session = anyOrNull()
|
|
||||||
|
|
||||||
sut.get()
|
|
||||||
|
|
||||||
mockServerScenarioSetup.takeRequest()
|
|
||||||
val refreshRequest = mockServerScenarioSetup.takeRequest()
|
|
||||||
val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest()
|
|
||||||
|
|
||||||
Assertions.assertEquals("PUT", refreshRequest.method)
|
|
||||||
Assertions.assertEquals(
|
|
||||||
"/login/${ContentData.loginSuccessResponse.refreshToken}",
|
|
||||||
refreshRequest.path
|
|
||||||
)
|
|
||||||
Assertions.assertEquals(null, refreshRequest.getHeader("Authorization"))
|
|
||||||
Assertions.assertEquals("Android", refreshRequest.getHeader("Platform"))
|
|
||||||
Assertions.assertEquals("", refreshRequest.body.readUtf8())
|
|
||||||
Assertions.assertEquals(
|
|
||||||
ContentData.refreshSuccessResponse.accessToken,
|
|
||||||
retryAfterTokenRefreshRequest.getHeader("Authorization")
|
|
||||||
)
|
|
||||||
verify(mockNetworkSessionLocalStorage, times(1)).session =
|
|
||||||
ContentData.refreshSuccessResponse
|
|
||||||
verifyZeroInteractions(mockNetworkSessionExpirationListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called")
|
|
||||||
@Test
|
|
||||||
fun failingRefreshResultsInSessionExpiration() = runBlocking {
|
|
||||||
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
|
|
||||||
mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(false))
|
|
||||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error)
|
|
||||||
|
|
||||||
Assertions.assertThrows(NetworkException::class.java) {
|
|
||||||
runBlocking { sut.get() }
|
|
||||||
}
|
|
||||||
verify(mockNetworkSessionLocalStorage, times(3)).session
|
|
||||||
verify(mockNetworkSessionLocalStorage, times(1)).session = null
|
|
||||||
verifyNoMoreInteractions(mockNetworkSessionLocalStorage)
|
|
||||||
verify(mockNetworkSessionExpirationListener, times(1)).onSessionExpired()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue