From 488f020ab146f72ca96f10c45f579882745e4121 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 22:19:02 +0300 Subject: [PATCH] Add Hilt(Dagger) example unit tests for Networking --- .../showcase/core/login/LogoutUseCaseTest.kt | 2 +- network/build.gradle | 1 + .../showcase/network/TestNetworkComponent.kt | 44 +++++++ .../LoginRemoteSourceRefreshActionImplTest.kt | 98 ++++++++++++++ .../auth/hilt/LoginRemoteSourceTest.kt | 120 +++++++++++++++++ .../LoginRemoteSourceRefreshActionImplTest.kt | 3 +- .../auth/{ => koin}/LoginRemoteSourceTest.kt | 3 +- .../hilt/ContentRemoteSourceImplTest.kt | 121 ++++++++++++++++++ .../content/hilt/SessionExpirationTest.kt | 113 ++++++++++++++++ .../{ => koin}/ContentRemoteSourceImplTest.kt | 3 +- .../{ => koin}/SessionExpirationTest.kt | 3 +- 11 files changed, 506 insertions(+), 5 deletions(-) create mode 100644 network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt rename network/src/test/java/org/fnives/test/showcase/network/auth/{ => koin}/LoginRemoteSourceRefreshActionImplTest.kt (97%) rename network/src/test/java/org/fnives/test/showcase/network/auth/{ => koin}/LoginRemoteSourceTest.kt (97%) create mode 100644 network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt rename network/src/test/java/org/fnives/test/showcase/network/content/{ => koin}/ContentRemoteSourceImplTest.kt (97%) rename network/src/test/java/org/fnives/test/showcase/network/content/{ => koin}/SessionExpirationTest.kt (97%) diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt index 776f1ab..5e75644 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt @@ -27,7 +27,7 @@ internal class LogoutUseCaseTest : KoinTest { @BeforeEach fun setUp() { mockUserDataLocalStorage = mock() - sut = LogoutUseCase(mockUserDataLocalStorage) + sut = LogoutUseCase(mockUserDataLocalStorage, null) startKoin { modules( createCoreModule( diff --git a/network/build.gradle b/network/build.gradle index 7449ab7..ce7c233 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -30,4 +30,5 @@ dependencies { testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" + kaptTest "com.google.dagger:dagger-compiler:$hilt_version" } \ No newline at end of file diff --git a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt new file mode 100644 index 0000000..7614b21 --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt @@ -0,0 +1,44 @@ +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.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]) +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) +} \ No newline at end of file diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt new file mode 100644 index 0000000..1b9e1f1 --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt @@ -0,0 +1,98 @@ +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.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) + } + + @Test + fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session() = runBlocking { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) + val expected = ContentData.refreshSuccessResponse + + val actual = sut.refresh(ContentData.refreshSuccessResponse.refreshToken) + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly() = + 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()) + } + + @Test + fun GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.refresh(ContentData.refreshSuccessResponse.refreshToken) } + } + } + + @Test + fun GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } + } + } + + @Test + fun GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt new file mode 100644 index 0000000..94b848e --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt @@ -0,0 +1,120 @@ +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() + 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")) } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt index 418d667..cbf9ae7 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt @@ -1,7 +1,8 @@ -package org.fnives.test.showcase.network.auth +package org.fnives.test.showcase.network.auth.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt index 6d74777..cc1b582 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt @@ -1,8 +1,9 @@ -package org.fnives.test.showcase.network.auth +package org.fnives.test.showcase.network.auth.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.auth.LoginCredentials 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.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt new file mode 100644 index 0000000..c3fad8c --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt @@ -0,0 +1,121 @@ +package org.fnives.test.showcase.network.content.hilt + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.TestNetworkComponent +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl +import org.fnives.test.showcase.network.di.koin.createNetworkModules +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.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +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.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.fnives.test.showcase.network.DaggerTestNetworkComponent +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) + } + + @Test + fun GIVEN_successful_response_WHEN_getting_content_THEN_its_parsed_and_returned_correctly() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Success(false)) + val expected = ContentData.contentSuccess + + val actual = sut.get() + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_successful_response_WHEN_getting_content_THEN_the_request_is_setup_properly() = 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()) + } + + @Test + fun GIVEN_response_with_missing_Field_WHEN_getting_content_THEN_invalid_is_ignored_others_are_returned() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false)) + + val expected = ContentData.contentSuccessWithMissingFields + + val actual = sut.get() + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_error_response_WHEN_getting_content_THEN_network_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Error(false)) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.get() } + } + } + + @Test + fun GIVEN_unexpected_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false)) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.get() } + } + } + + @Test + fun GIVEN_malformed_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false)) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.get() } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt new file mode 100644 index 0000000..7c009fa --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt @@ -0,0 +1,113 @@ +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.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.api.extension.RegisterExtension +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +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() + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt index cff87d8..6edc095 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt @@ -1,7 +1,8 @@ -package org.fnives.test.showcase.network.content +package org.fnives.test.showcase.network.content.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt index 3450b85..5902153 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt @@ -1,8 +1,9 @@ -package org.fnives.test.showcase.network.content +package org.fnives.test.showcase.network.content.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.model.session.Session +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario