feat: add provider login flow #4
@@ -1,9 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:name=".ABoxManagerApplication"
|
||||
android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.ABoxManager">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.flagship.abox.manager.ui.session
|
||||
|
||||
import com.flagship.abox.manager.domain.DomainError
|
||||
import com.flagship.abox.manager.domain.LoginCredentials
|
||||
import com.flagship.abox.manager.domain.Password
|
||||
import com.flagship.abox.manager.domain.ProviderProfile
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import com.flagship.abox.manager.domain.ProviderType
|
||||
import com.flagship.abox.manager.domain.Session
|
||||
import com.flagship.abox.manager.domain.SessionCoordinator
|
||||
import com.flagship.abox.manager.domain.SessionGateway
|
||||
import com.flagship.abox.manager.domain.Username
|
||||
import com.flagship.abox.manager.testing.MainDispatcherRule
|
||||
import com.flagship.abox.manager.testing.fakes.FakeCredentialStore
|
||||
import com.flagship.abox.manager.testing.fakes.FakeLocalSessionGateway
|
||||
import com.flagship.abox.manager.testing.fakes.FakeProviderProfileStore
|
||||
import com.flagship.abox.manager.testing.fakes.FakeSessionGateway
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Regression tests for real ABox login gating: the authenticated UI may appear only when the
|
||||
* active provider profile owns a successful session, and a failed ABox login stays on Login.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class AppSessionViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private val profiles = FakeProviderProfileStore()
|
||||
private val remote = FakeSessionGateway()
|
||||
private val local = FakeLocalSessionGateway()
|
||||
private val credentials = LoginCredentials(
|
||||
username = Username("user"),
|
||||
password = Password("password"),
|
||||
)
|
||||
|
||||
private fun newCoordinator(remoteGateway: SessionGateway = remote) = SessionCoordinator(
|
||||
profileStore = profiles,
|
||||
credentialStore = FakeCredentialStore(),
|
||||
remoteGateway = remoteGateway,
|
||||
localGateway = local,
|
||||
)
|
||||
|
||||
private suspend fun saveAndActivateAboxProfile(id: ProviderProfileId) {
|
||||
profiles.saveProfile(ProviderProfile(id, ProviderType.ABOX, id.value))
|
||||
profiles.setActiveProfile(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun failedAboxLoginLeavesUiOnLogin() = runTest {
|
||||
saveAndActivateAboxProfile(aboxProfileId)
|
||||
remote.failLogin(aboxProfileId, DomainError.Authentication())
|
||||
val coordinator = newCoordinator()
|
||||
val viewModel = AppSessionViewModel(coordinator, profiles)
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
advanceUntilIdle()
|
||||
|
||||
coordinator.login(
|
||||
profileId = aboxProfileId,
|
||||
credentials = credentials,
|
||||
rememberUsername = false,
|
||||
autoLogin = false,
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(viewModel.uiState.value is AppSessionUiState.Login)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sessionForDifferentProfileCannotAuthenticateActiveProfile() = runTest {
|
||||
profiles.saveProfile(ProviderProfile(otherProfileId, ProviderType.ABOX, otherProfileId.value))
|
||||
remote.login(otherProfileId, credentials)
|
||||
saveAndActivateAboxProfile(aboxProfileId)
|
||||
|
||||
// The gateway exposes the other profile's session no matter which profile is observed.
|
||||
val crossProfileGateway = object : SessionGateway by remote {
|
||||
override fun observeSession(profileId: ProviderProfileId): Flow<Session?> =
|
||||
remote.observeSession(otherProfileId)
|
||||
}
|
||||
val viewModel = AppSessionViewModel(newCoordinator(crossProfileGateway), profiles)
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(viewModel.uiState.value is AppSessionUiState.Login)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun successfulAboxLoginAuthenticatesActiveProfile() = runTest {
|
||||
saveAndActivateAboxProfile(aboxProfileId)
|
||||
val coordinator = newCoordinator()
|
||||
val viewModel = AppSessionViewModel(coordinator, profiles)
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
advanceUntilIdle()
|
||||
|
||||
coordinator.login(
|
||||
profileId = aboxProfileId,
|
||||
credentials = credentials,
|
||||
rememberUsername = false,
|
||||
autoLogin = false,
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
val state = viewModel.uiState.value
|
||||
assertTrue(state is AppSessionUiState.Authenticated)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val aboxProfileId = ProviderProfileId("abox-default")
|
||||
val otherProfileId = ProviderProfileId("abox-other")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user