Issue#49 Add core-network integration tests
This commit is contained in:
parent
a69fdce26c
commit
555ad6d05f
4 changed files with 286 additions and 29 deletions
|
|
@ -27,5 +27,7 @@ dependencies {
|
|||
testImplementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
testImplementation "app.cash.turbine:turbine:$turbine_version"
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$testing_junit5_version"
|
||||
|
||||
testImplementation project(':mockserver')
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package org.fnives.test.showcase.core.integration
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.fnives.test.showcase.core.di.koin.createCoreModule
|
||||
import org.fnives.test.showcase.core.integration.fake.FakeFavouriteContentLocalStorage
|
||||
import org.fnives.test.showcase.core.integration.fake.FakeUserDataLocalStorage
|
||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||
import org.fnives.test.showcase.core.login.LogoutUseCase
|
||||
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||
import org.fnives.test.showcase.model.auth.LoginStatus
|
||||
import org.fnives.test.showcase.model.network.BaseUrl
|
||||
import org.fnives.test.showcase.model.shared.Answer
|
||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
||||
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
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.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verifyZeroInteractions
|
||||
import java.util.stream.Stream
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class AuthIntegrationTest : KoinTest {
|
||||
|
||||
private lateinit var mockServerScenarioSetup: MockServerScenarioSetup
|
||||
private lateinit var fakeFavouriteContentLocalStorage: FakeFavouriteContentLocalStorage
|
||||
private lateinit var mockSessionExpirationListener: SessionExpirationListener
|
||||
private lateinit var fakeUserDataLocalStorage: FakeUserDataLocalStorage
|
||||
private val isUserLoggedInUseCase by inject<IsUserLoggedInUseCase>()
|
||||
private val loginUseCase by inject<LoginUseCase>()
|
||||
private val logoutUseCase by inject<LogoutUseCase>()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockSessionExpirationListener = mock()
|
||||
mockServerScenarioSetup = MockServerScenarioSetup()
|
||||
val url = mockServerScenarioSetup.start(false)
|
||||
fakeFavouriteContentLocalStorage = FakeFavouriteContentLocalStorage()
|
||||
fakeUserDataLocalStorage = FakeUserDataLocalStorage(null)
|
||||
|
||||
startKoin {
|
||||
modules(
|
||||
createCoreModule(
|
||||
baseUrl = BaseUrl(url),
|
||||
enableNetworkLogging = true,
|
||||
favouriteContentLocalStorageProvider = { fakeFavouriteContentLocalStorage },
|
||||
sessionExpirationListenerProvider = { mockSessionExpirationListener },
|
||||
userDataLocalStorageProvider = { fakeUserDataLocalStorage }
|
||||
).toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
mockServerScenarioSetup.stop()
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN no session saved WHEN checking if user is logged in THEN they are not")
|
||||
@Test
|
||||
fun withoutSessionTheUserIsNotLoggedIn() = runTest {
|
||||
fakeUserDataLocalStorage.session = null
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertFalse(actual, "User is expected to be not logged in")
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN no session WHEN user is logging in THEN they get session")
|
||||
@Test
|
||||
fun login() = runTest {
|
||||
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "usr", password = "sEc"), validateArguments = true)
|
||||
val expectedSession = ContentData.loginSuccessResponse
|
||||
|
||||
val answer = loginUseCase.invoke(LoginCredentials(username = "usr", password = "sEc"))
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertEquals(Answer.Success(LoginStatus.SUCCESS), answer)
|
||||
Assertions.assertTrue(actual, "User is expected to be logged in")
|
||||
Assertions.assertEquals(expectedSession, fakeUserDataLocalStorage.session)
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
@MethodSource("localInputErrorArguments")
|
||||
@ParameterizedTest(name = "GIVEN {0} credentials WHEN login called THEN error {1} is shown")
|
||||
fun localInputError(credentials: LoginCredentials, loginError: LoginStatus) = runTest {
|
||||
val answer = loginUseCase.invoke(credentials)
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertEquals(Answer.Success(loginError), answer)
|
||||
Assertions.assertFalse(actual, "User is expected to be not logged in")
|
||||
Assertions.assertEquals(null, fakeUserDataLocalStorage.session)
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN no session WHEN user is logging in THEN they get session")
|
||||
@Test
|
||||
fun loginInvalidCredentials() = runTest {
|
||||
mockServerScenarioSetup.setScenario(AuthScenario.InvalidCredentials(username = "usr", password = "sEc"), validateArguments = true)
|
||||
|
||||
val answer = loginUseCase.invoke(LoginCredentials(username = "usr", password = "sEc"))
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertEquals(Answer.Success(LoginStatus.INVALID_CREDENTIALS), answer)
|
||||
Assertions.assertFalse(actual, "User is expected to be not logged in")
|
||||
Assertions.assertEquals(null, fakeUserDataLocalStorage.session)
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
@MethodSource("networkErrorArguments")
|
||||
@ParameterizedTest(name = "GIVEN {0} network response WHEN login called THEN error is shown")
|
||||
fun networkInputError(authScenario: AuthScenario) = runTest {
|
||||
mockServerScenarioSetup.setScenario(authScenario, validateArguments = true)
|
||||
val credentials = LoginCredentials(username = authScenario.username, password = authScenario.password)
|
||||
val answer = loginUseCase.invoke(credentials)
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertTrue(answer is Answer.Error, "Answer is expected to be an Error")
|
||||
Assertions.assertFalse(actual, "User is expected to be not logged in")
|
||||
Assertions.assertEquals(null, fakeUserDataLocalStorage.session)
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN logged in user WHEN user is login out THEN they no longer have a session and content is cleared")
|
||||
@Test
|
||||
fun logout() = runTest {
|
||||
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "usr", password = "sEc"), validateArguments = true)
|
||||
loginUseCase.invoke(LoginCredentials(username = "usr", password = "sEc"))
|
||||
|
||||
logoutUseCase.invoke()
|
||||
val actual = isUserLoggedInUseCase.invoke()
|
||||
|
||||
Assertions.assertEquals(false, actual, "User is expected to be logged out")
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun localInputErrorArguments() = Stream.of(
|
||||
Arguments.of(LoginCredentials("", "password"), LoginStatus.INVALID_USERNAME),
|
||||
Arguments.of(LoginCredentials("username", ""), LoginStatus.INVALID_PASSWORD)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun networkErrorArguments() = Stream.of(
|
||||
Arguments.of(AuthScenario.GenericError(username = "a", password = "b")),
|
||||
Arguments.of(AuthScenario.UnexpectedJsonAsSuccessResponse(username = "a", password = "b")),
|
||||
Arguments.of(AuthScenario.MalformedJsonAsSuccessResponse(username = "a", password = "b")),
|
||||
Arguments.of(AuthScenario.MissingFieldJson(username = "a", password = "b"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -37,9 +37,6 @@ import org.koin.core.context.stopKoin
|
|||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -335,30 +332,4 @@ class ContentIntegrationTest : KoinTest {
|
|||
val expectedSession = Session(accessToken = "refreshed-access", refreshToken = "refreshed-refresh")
|
||||
Assertions.assertEquals(expectedSession, fakeUserDataLocalStorage.session)
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN session expiration and failing token-refresh response WHEN observing THEN session expiration is attached")
|
||||
@Test
|
||||
fun sessionExpiration() = runTest {
|
||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error)
|
||||
.setScenario(
|
||||
ContentScenario.Unauthorized(usingRefreshedToken = false)
|
||||
.then(ContentScenario.Success(usingRefreshedToken = true))
|
||||
)
|
||||
|
||||
val actual = async {
|
||||
getAllContentUseCase.get()
|
||||
.take(2)
|
||||
.toList()
|
||||
}
|
||||
|
||||
val actualValues = actual.await()
|
||||
Assertions.assertEquals(Resource.Loading<List<FavouriteContent>>(), actualValues[0])
|
||||
Assertions.assertTrue(actualValues[1] is Resource.Error, "Resource is Error")
|
||||
Assertions.assertTrue((actualValues[1] as Resource.Error).error is NetworkException, "Resource is Network Error")
|
||||
|
||||
verify(mockSessionExpirationListener, times(1)).onSessionExpired()
|
||||
verifyNoMoreInteractions(mockSessionExpirationListener)
|
||||
|
||||
Assertions.assertEquals(null, fakeUserDataLocalStorage.session)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
package org.fnives.test.showcase.core.integration
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
||||
import org.fnives.test.showcase.core.content.GetAllContentUseCase
|
||||
import org.fnives.test.showcase.core.di.koin.createCoreModule
|
||||
import org.fnives.test.showcase.core.integration.fake.FakeFavouriteContentLocalStorage
|
||||
import org.fnives.test.showcase.core.integration.fake.FakeUserDataLocalStorage
|
||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||
import org.fnives.test.showcase.core.session.SessionExpirationListener
|
||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||
import org.fnives.test.showcase.model.network.BaseUrl
|
||||
import org.fnives.test.showcase.model.shared.Resource
|
||||
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.content.ContentScenario
|
||||
import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario
|
||||
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
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.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SessionExpirationIntegrationTest : KoinTest {
|
||||
|
||||
private lateinit var mockServerScenarioSetup: MockServerScenarioSetup
|
||||
private lateinit var fakeFavouriteContentLocalStorage: FakeFavouriteContentLocalStorage
|
||||
private lateinit var mockSessionExpirationListener: SessionExpirationListener
|
||||
private lateinit var fakeUserDataLocalStorage: FakeUserDataLocalStorage
|
||||
private val isUserLoggedInUseCase by inject<IsUserLoggedInUseCase>()
|
||||
private val getAllContentUseCase by inject<GetAllContentUseCase>()
|
||||
private val loginUseCase by inject<LoginUseCase>()
|
||||
private val fetchContentUseCase by inject<FetchContentUseCase>()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockSessionExpirationListener = mock()
|
||||
mockServerScenarioSetup = MockServerScenarioSetup()
|
||||
val url = mockServerScenarioSetup.start(false)
|
||||
fakeFavouriteContentLocalStorage = FakeFavouriteContentLocalStorage()
|
||||
fakeUserDataLocalStorage = FakeUserDataLocalStorage(null)
|
||||
|
||||
startKoin {
|
||||
modules(
|
||||
createCoreModule(
|
||||
baseUrl = BaseUrl(url),
|
||||
enableNetworkLogging = true,
|
||||
favouriteContentLocalStorageProvider = { fakeFavouriteContentLocalStorage },
|
||||
sessionExpirationListenerProvider = { mockSessionExpirationListener },
|
||||
userDataLocalStorageProvider = { fakeUserDataLocalStorage }
|
||||
).toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
mockServerScenarioSetup.stop()
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN logged in user WHEN fetching but expired THEN user is logged out")
|
||||
@Test
|
||||
fun sessionResultsInErrorAndClearsContent() = runTest {
|
||||
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"), validateArguments = true)
|
||||
loginUseCase.invoke(LoginCredentials(username = "a", password = "b"))
|
||||
Assertions.assertTrue(isUserLoggedInUseCase.invoke())
|
||||
verifyZeroInteractions(mockSessionExpirationListener)
|
||||
|
||||
mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(usingRefreshedToken = false))
|
||||
.setScenario(RefreshTokenScenario.Error)
|
||||
|
||||
getAllContentUseCase.get().take(2).toList() // getting session expiration
|
||||
|
||||
verify(mockSessionExpirationListener, times(1)).onSessionExpired()
|
||||
verifyNoMoreInteractions(mockSessionExpirationListener)
|
||||
Assertions.assertFalse(isUserLoggedInUseCase.invoke(), "User is expected to be logged out")
|
||||
}
|
||||
|
||||
@DisplayName("GIVEN session expiration and failing token-refresh response WHEN requiring data THEN error is returned and data is cleared")
|
||||
@Test
|
||||
fun sessionExpirationResultsInLogout() = runTest {
|
||||
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "", password = ""), validateArguments = true)
|
||||
loginUseCase.invoke(LoginCredentials(username = "", password = ""))
|
||||
|
||||
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error)
|
||||
.setScenario(
|
||||
ContentScenario.Success(usingRefreshedToken = true)
|
||||
.then(ContentScenario.Unauthorized(usingRefreshedToken = false))
|
||||
.then(ContentScenario.Success(usingRefreshedToken = true))
|
||||
)
|
||||
|
||||
getAllContentUseCase.get().take(2).toList() // cachedData
|
||||
|
||||
fetchContentUseCase.invoke()
|
||||
val unauthorizedData = getAllContentUseCase.get().take(2).last()
|
||||
|
||||
Assertions.assertTrue(unauthorizedData is Resource.Error, "Resource is Error")
|
||||
Assertions.assertTrue((unauthorizedData as Resource.Error).error is NetworkException, "Resource is Network Error")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue