fix: require real ABSDK success before authentication

This commit is contained in:
2026-08-24 21:50:58 +08:00
parent 2ecca00371
commit 6535aeef1a
2 changed files with 128 additions and 0 deletions
+2
View File
@@ -1,9 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".ABoxManagerApplication" android:name=".ABoxManagerApplication"
android:allowBackup="false" android:allowBackup="false"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.ABoxManager"> android:theme="@style/Theme.ABoxManager">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
@@ -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")
}
}