diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index ec5ab65..6570e95 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.data.auth.impl import android.content.SharedPreferences +import androidx.annotation.VisibleForTesting import androidx.core.content.edit import gq.kirmanak.mealient.data.auth.AuthStorage import gq.kirmanak.mealient.di.AuthModule.Companion.ENCRYPTED @@ -53,8 +54,13 @@ class AuthStorageImpl @Inject constructor( } companion object { - private const val AUTH_HEADER_KEY = "authHeader" - private const val EMAIL_KEY = "email" - private const val PASSWORD_KEY = "password" + @VisibleForTesting + const val AUTH_HEADER_KEY = "authHeader" + + @VisibleForTesting + const val EMAIL_KEY = "email" + + @VisibleForTesting + const val PASSWORD_KEY = "password" } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt new file mode 100644 index 0000000..562dd64 --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt @@ -0,0 +1,108 @@ +package gq.kirmanak.mealient.data.auth.impl + +import com.google.common.truth.Truth.assertThat +import gq.kirmanak.mealient.data.auth.AuthDataSource +import gq.kirmanak.mealient.data.auth.AuthRepo +import gq.kirmanak.mealient.data.auth.AuthStorage +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME +import io.mockk.* +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AuthRepoImplTest { + + @MockK + lateinit var dataSource: AuthDataSource + + @MockK(relaxUnitFun = true) + lateinit var storage: AuthStorage + + lateinit var subject: AuthRepo + + @Before + fun setUp() { + MockKAnnotations.init(this) + subject = AuthRepoImpl(storage, dataSource) + } + + @Test + fun `when isAuthorizedFlow then reads from storage`() = runTest { + every { storage.authHeaderFlow } returns flowOf("", null, "header") + assertThat(subject.isAuthorizedFlow.toList()).isEqualTo(listOf(true, false, true)) + } + + @Test + fun `when authenticate successfully then saves to storage`() = runTest { + coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN + subject.authenticate(TEST_USERNAME, TEST_PASSWORD) + coVerifyAll { + storage.setAuthHeader(TEST_AUTH_HEADER) + storage.setEmail(TEST_USERNAME) + storage.setPassword(TEST_PASSWORD) + } + confirmVerified(storage) + } + + @Test + fun `when authenticate fails then does not change storage`() = runTest { + coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException() + runCatching { subject.authenticate("invalid", "") } + confirmVerified(storage) + } + + @Test + fun `when logout then removes email, password and header`() = runTest { + subject.logout() + coVerifyAll { + storage.setEmail(null) + storage.setPassword(null) + storage.setAuthHeader(null) + } + confirmVerified(storage) + } + + @Test + fun `when invalidate then does not authenticate without email`() = runTest { + coEvery { storage.getEmail() } returns null + coEvery { storage.getPassword() } returns TEST_PASSWORD + subject.invalidateAuthHeader() + confirmVerified(dataSource) + } + + @Test + fun `when invalidate then does not authenticate without password`() = runTest { + coEvery { storage.getEmail() } returns TEST_USERNAME + coEvery { storage.getPassword() } returns null + subject.invalidateAuthHeader() + confirmVerified(dataSource) + } + + @Test + fun `when invalidate with credentials then calls authenticate`() = runTest { + coEvery { storage.getEmail() } returns TEST_USERNAME + coEvery { storage.getPassword() } returns TEST_PASSWORD + coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN + subject.invalidateAuthHeader() + coVerifyAll { + dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) + } + } + + @Test + fun `when invalidate with credentials and auth fails then clears email`() = runTest { + coEvery { storage.getEmail() } returns "invalid" + coEvery { storage.getPassword() } returns "" + coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException() + subject.invalidateAuthHeader() + coVerify { storage.setEmail(null) } + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt new file mode 100644 index 0000000..b19f22a --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt @@ -0,0 +1,64 @@ +package gq.kirmanak.mealient.data.auth.impl + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import com.google.common.truth.Truth.assertThat +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.testing.HiltAndroidTest +import gq.kirmanak.mealient.data.auth.AuthStorage +import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.AUTH_HEADER_KEY +import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.EMAIL_KEY +import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.PASSWORD_KEY +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME +import gq.kirmanak.mealient.test.HiltRobolectricTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import javax.inject.Inject + +@OptIn(ExperimentalCoroutinesApi::class) +@HiltAndroidTest +class AuthStorageImplTest : HiltRobolectricTest() { + + @Inject + @ApplicationContext + lateinit var context: Context + + lateinit var subject: AuthStorage + + lateinit var sharedPreferences: SharedPreferences + + @Before + fun setUp() { + sharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE) + subject = AuthStorageImpl(sharedPreferences) + } + + @Test + fun `when authHeaderFlow is observed then sends value immediately`() = runTest { + sharedPreferences.edit(commit = true) { putString(AUTH_HEADER_KEY, TEST_AUTH_HEADER) } + assertThat(subject.authHeaderFlow.first()).isEqualTo(TEST_AUTH_HEADER) + } + + @Test + fun `when authHeader is observed then sends null if nothing saved`() = runTest { + assertThat(subject.authHeaderFlow.first()).isEqualTo(null) + } + + @Test + fun `when setEmail then edits shared preferences`() = runTest { + subject.setEmail(TEST_USERNAME) + assertThat(sharedPreferences.getString(EMAIL_KEY, null)).isEqualTo(TEST_USERNAME) + } + + @Test + fun `when getPassword then reads shared preferences`() = runTest { + sharedPreferences.edit(commit = true) { putString(PASSWORD_KEY, TEST_PASSWORD) } + assertThat(subject.getPassword()).isEqualTo(TEST_PASSWORD) + } +} \ No newline at end of file