feat: separate session and credential actions

This commit is contained in:
2026-08-26 12:57:44 +08:00
parent 283f56a3b6
commit c1c21551fb
6 changed files with 397 additions and 13 deletions
@@ -12,6 +12,7 @@ import com.flagship.abox.manager.domain.ProviderType
import com.flagship.abox.manager.domain.SessionCoordinator
import com.flagship.abox.manager.domain.SessionGateway
import com.flagship.abox.manager.domain.Session
import com.flagship.abox.manager.domain.SessionValidity
import com.flagship.abox.manager.domain.Username
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
@@ -133,6 +134,101 @@ class SessionCoordinatorTest {
)
}
@Test
fun endActiveSessionKeepsAboxCredentials() = runTest {
val fixture = Fixture()
fixture.coordinator.selectProvider(remoteProfile)
fixture.coordinator.login(
remoteProfile.id,
credentials,
rememberUsername = true,
autoLogin = true,
)
val result = fixture.coordinator.endActiveSession()
assertTrue(result is DomainResult.Success)
assertEquals(remoteProfile, fixture.profiles.activeProfile())
assertNull(fixture.remote.observeSession(remoteProfile.id).first())
assertEquals(
credentials,
(fixture.credentials.loadCredentials(remoteProfile.id) as DomainResult.Success).value,
)
assertTrue(fixture.profiles.loginPreferences(remoteProfile.id).autoLoginEnabled)
}
@Test
fun forgetSavedCredentialsDisablesAutoLoginAndKeepsSessionStateExplicit() = runTest {
val fixture = Fixture()
fixture.coordinator.selectProvider(remoteProfile)
fixture.coordinator.login(
remoteProfile.id,
credentials,
rememberUsername = true,
autoLogin = true,
)
val result = fixture.coordinator.forgetSavedCredentials()
assertTrue(result is DomainResult.Success)
assertEquals(remoteProfile, fixture.profiles.activeProfile())
assertEquals(
remoteProfile.id,
fixture.remote.observeSession(remoteProfile.id).first()?.profileId,
)
assertNull(
(fixture.credentials.loadCredentials(remoteProfile.id) as DomainResult.Success).value,
)
val preferences = fixture.profiles.loginPreferences(remoteProfile.id)
assertEquals(credentials.username, preferences.rememberedUsername)
assertTrue(!preferences.autoLoginEnabled)
}
@Test
fun switchProviderClearsActiveProfileAfterGatewayLogout() = runTest {
val profiles = FakeProviderProfileStore()
val remote = RecordingSessionGateway(profiles)
val coordinator = SessionCoordinator(
profileStore = profiles,
credentialStore = FakeCredentialStore(),
remoteGateway = remote,
localGateway = FakeLocalSessionGateway(),
)
coordinator.selectProvider(remoteProfile)
remote.login(remoteProfile.id, credentials)
val result = coordinator.switchProvider()
assertTrue(result is DomainResult.Success)
assertEquals(remoteProfile.id, remote.activeProfileAtLogout)
assertNull(profiles.activeProfile())
}
@Test
fun validateActiveSessionDoesNotRelogin() = runTest {
val remote = CountingSessionGateway()
val credentialsStore = CountingCredentialStore()
val profiles = FakeProviderProfileStore()
val coordinator = SessionCoordinator(
profileStore = profiles,
credentialStore = credentialsStore,
remoteGateway = remote,
localGateway = FakeLocalSessionGateway(),
)
profiles.saveProfile(remoteProfile)
profiles.setActiveProfile(remoteProfile.id)
remote.setValidationResult(
remoteProfile.id,
DomainResult.Success(SessionValidity.INVALID),
)
val result = coordinator.validateActiveSession()
assertEquals(DomainResult.Success(SessionValidity.INVALID), result)
assertEquals(0, remote.loginCalls)
assertEquals(0, credentialsStore.loadCalls)
}
@Test
fun authenticationFailureDuringRecoveryClearsSavedCredential() = runTest {
val fixture = Fixture()
@@ -189,6 +285,18 @@ class SessionCoordinatorTest {
assertTrue(!fixture.profiles.loginPreferences(remoteProfile.id).autoLoginEnabled)
}
@Test
fun endActiveSessionClearsLocalActiveProfile() = runTest {
val fixture = Fixture()
fixture.coordinator.selectProvider(localProfile)
val result = fixture.coordinator.endActiveSession()
assertTrue(result is DomainResult.Success)
assertNull(fixture.profiles.activeProfile())
assertNull(fixture.local.observeSession(localProfile.id).first())
}
private class Fixture {
val profiles = FakeProviderProfileStore()
val credentials = FakeCredentialStore()
@@ -197,6 +305,19 @@ class SessionCoordinatorTest {
val coordinator = SessionCoordinator(profiles, credentials, remote, local)
}
private class RecordingSessionGateway(
private val profileStore: FakeProviderProfileStore,
private val delegate: FakeSessionGateway = FakeSessionGateway(),
) : SessionGateway by delegate {
var activeProfileAtLogout: ProviderProfileId? = null
private set
override suspend fun logout(profileId: ProviderProfileId): DomainResult<Unit> {
activeProfileAtLogout = profileStore.activeProfile()?.id
return delegate.logout(profileId)
}
}
private class CountingSessionGateway(
private val delegate: FakeSessionGateway = FakeSessionGateway(),
) : SessionGateway by delegate {
@@ -214,6 +335,13 @@ class SessionCoordinatorTest {
fun failLogin(profileId: ProviderProfileId, error: DomainError) {
delegate.failLogin(profileId, error)
}
fun setValidationResult(
profileId: ProviderProfileId,
result: DomainResult<SessionValidity>,
) {
delegate.setValidationResult(profileId, result)
}
}
private class CountingCredentialStore(