From 45f768208f53c7d1eb9cf1f901f66a420aa98fdf Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Mon, 24 Aug 2026 19:02:06 +0800 Subject: [PATCH] feat: route provider sessions to ABSDK and simulation --- android/app/build.gradle.kts | 12 +++ .../abox/manager/di/DebugSessionModule.kt | 27 ------- .../flagship/abox/manager/di/SessionModule.kt | 27 ++++++- .../abox/manager/di/ReleaseSessionModule.kt | 27 ------- .../gateway/LocalSessionGatewayImpl.kt | 38 --------- .../testing/fakes/SessionCoordinatorTest.kt | 77 +++++++++++++++++++ 6 files changed, 114 insertions(+), 94 deletions(-) delete mode 100644 android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugSessionModule.kt delete mode 100644 android/app/src/release/kotlin/com/flagship/abox/manager/di/ReleaseSessionModule.kt delete mode 100644 android/core/session/src/main/kotlin/com/flagship/abox/manager/session/gateway/LocalSessionGatewayImpl.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index cac7fec..5cc61b1 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -45,6 +45,15 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + excludes += "/META-INF/DEPENDENCIES" + excludes += "/META-INF/LICENSE*" + excludes += "/META-INF/NOTICE*" + } + } } kapt { @@ -55,6 +64,8 @@ dependencies { implementation(project(":domain")) implementation(project(":android:core:designsystem")) implementation(project(":android:core:session")) + implementation(project(":absdk-bridge")) + implementation(project(":runtime:simulation")) debugImplementation(project(":testing:fakes")) implementation(platform(libs.compose.bom)) @@ -73,6 +84,7 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.kotlinx.coroutines.test) + testImplementation(project(":testing:fakes")) androidTestImplementation(platform(libs.compose.bom)) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.test.runner) diff --git a/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugSessionModule.kt b/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugSessionModule.kt deleted file mode 100644 index ec992e9..0000000 --- a/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugSessionModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.flagship.abox.manager.di - -import com.flagship.abox.manager.domain.SessionGateway -import com.flagship.abox.manager.testing.fakes.FakeSessionGateway -import com.flagship.abox.manager.ui.provider.Availability -import com.flagship.abox.manager.ui.provider.ProviderAvailability -import com.flagship.abox.manager.ui.provider.UnavailableReason -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object DebugSessionModule { - @Provides - @Singleton - fun provideRemoteSessionGateway(fake: FakeSessionGateway): SessionGateway = fake - - @Provides - @Singleton - fun provideProviderAvailability(): ProviderAvailability = ProviderAvailability( - abox = Availability.Simulation, - compatible = Availability.Disabled(UnavailableReason.PROTOCOL_PENDING), - ) -} diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/di/SessionModule.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/di/SessionModule.kt index 2c123f8..a2425ba 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/di/SessionModule.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/di/SessionModule.kt @@ -3,6 +3,8 @@ package com.flagship.abox.manager.di import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences +import com.flagship.abox.manager.absdk.bridge.AbsdkBridge +import com.flagship.abox.manager.absdk.bridge.CleartextAbsdkTransport import com.flagship.abox.manager.domain.CredentialStore import com.flagship.abox.manager.domain.LocalSessionGateway import com.flagship.abox.manager.domain.ProviderProfileStore @@ -11,9 +13,12 @@ import com.flagship.abox.manager.domain.SessionGateway import com.flagship.abox.manager.session.credential.AndroidKeystoreCredentialCipher import com.flagship.abox.manager.session.credential.CredentialCipher import com.flagship.abox.manager.session.credential.KeystoreCredentialStore -import com.flagship.abox.manager.session.gateway.LocalSessionGatewayImpl import com.flagship.abox.manager.session.profile.ProviderProfileStoreImpl import com.flagship.abox.manager.session.profile.sessionDataStore +import com.flagship.abox.manager.simulation.SimulationSessionGateway +import com.flagship.abox.manager.ui.provider.Availability +import com.flagship.abox.manager.ui.provider.ProviderAvailability +import com.flagship.abox.manager.ui.provider.UnavailableReason import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -49,7 +54,25 @@ object SessionModule { @Provides @Singleton - fun provideLocalSessionGateway(): LocalSessionGateway = LocalSessionGatewayImpl() + @OptIn(CleartextAbsdkTransport::class) + fun provideAbsdkBridge(): AbsdkBridge = AbsdkBridge.create() + + @Provides + @Singleton + fun provideRemoteSessionGateway( + bridge: AbsdkBridge, + ): SessionGateway = bridge + + @Provides + @Singleton + fun provideLocalSessionGateway(): LocalSessionGateway = SimulationSessionGateway() + + @Provides + @Singleton + fun provideProviderAvailability(): ProviderAvailability = ProviderAvailability( + abox = Availability.Enabled, + compatible = Availability.Disabled(UnavailableReason.PROTOCOL_PENDING), + ) @Provides @Singleton diff --git a/android/app/src/release/kotlin/com/flagship/abox/manager/di/ReleaseSessionModule.kt b/android/app/src/release/kotlin/com/flagship/abox/manager/di/ReleaseSessionModule.kt deleted file mode 100644 index ff92f2a..0000000 --- a/android/app/src/release/kotlin/com/flagship/abox/manager/di/ReleaseSessionModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.flagship.abox.manager.di - -import com.flagship.abox.manager.domain.SessionGateway -import com.flagship.abox.manager.session.gateway.UnavailableRemoteSessionGateway -import com.flagship.abox.manager.ui.provider.Availability -import com.flagship.abox.manager.ui.provider.ProviderAvailability -import com.flagship.abox.manager.ui.provider.UnavailableReason -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object ReleaseSessionModule { - @Provides - @Singleton - fun provideRemoteSessionGateway(): SessionGateway = UnavailableRemoteSessionGateway() - - @Provides - @Singleton - fun provideProviderAvailability(): ProviderAvailability = ProviderAvailability( - abox = Availability.Disabled(UnavailableReason.TRANSPORT_SECURITY_PENDING), - compatible = Availability.Disabled(UnavailableReason.PROTOCOL_PENDING), - ) -} diff --git a/android/core/session/src/main/kotlin/com/flagship/abox/manager/session/gateway/LocalSessionGatewayImpl.kt b/android/core/session/src/main/kotlin/com/flagship/abox/manager/session/gateway/LocalSessionGatewayImpl.kt deleted file mode 100644 index 1fbfb01..0000000 --- a/android/core/session/src/main/kotlin/com/flagship/abox/manager/session/gateway/LocalSessionGatewayImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.flagship.abox.manager.session.gateway - -import com.flagship.abox.manager.domain.DomainResult -import com.flagship.abox.manager.domain.LocalSessionGateway -import com.flagship.abox.manager.domain.LoginCredentials -import com.flagship.abox.manager.domain.ProviderProfileId -import com.flagship.abox.manager.domain.Session -import com.flagship.abox.manager.domain.SessionValidity -import java.util.concurrent.ConcurrentHashMap -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -class LocalSessionGatewayImpl : LocalSessionGateway { - private val sessions = ConcurrentHashMap>() - - override fun observeSession(profileId: ProviderProfileId): Flow = flow(profileId) - - override suspend fun login( - profileId: ProviderProfileId, - credentials: LoginCredentials, - ): DomainResult = DomainResult.Success( - Session(profileId, credentials.username).also { flow(profileId).value = it }, - ) - - override suspend fun validateSession( - profileId: ProviderProfileId, - ): DomainResult = DomainResult.Success( - if (flow(profileId).value == null) SessionValidity.INVALID else SessionValidity.VALID, - ) - - override suspend fun logout(profileId: ProviderProfileId): DomainResult { - flow(profileId).value = null - return DomainResult.Success(Unit) - } - - private fun flow(profileId: ProviderProfileId): MutableStateFlow = - sessions.getOrPut(profileId) { MutableStateFlow(null) } -} diff --git a/testing/fakes/src/test/kotlin/com/flagship/abox/manager/testing/fakes/SessionCoordinatorTest.kt b/testing/fakes/src/test/kotlin/com/flagship/abox/manager/testing/fakes/SessionCoordinatorTest.kt index 89b3e94..29ea550 100644 --- a/testing/fakes/src/test/kotlin/com/flagship/abox/manager/testing/fakes/SessionCoordinatorTest.kt +++ b/testing/fakes/src/test/kotlin/com/flagship/abox/manager/testing/fakes/SessionCoordinatorTest.kt @@ -1,5 +1,6 @@ package com.flagship.abox.manager.testing.fakes +import com.flagship.abox.manager.domain.CredentialStore import com.flagship.abox.manager.domain.DomainError import com.flagship.abox.manager.domain.DomainResult import com.flagship.abox.manager.domain.LoginCredentials @@ -9,6 +10,8 @@ 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.SessionCoordinator +import com.flagship.abox.manager.domain.SessionGateway +import com.flagship.abox.manager.domain.Session import com.flagship.abox.manager.domain.Username import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -41,6 +44,37 @@ class SessionCoordinatorTest { assertNull(fixture.remote.observeSession(localProfile.id).first()) } + @Test + fun localSelectionCreatesSessionWithoutAboxOrCredentialAccess() = runTest { + val remote = CountingSessionGateway() + val credentialsStore = CountingCredentialStore() + val coordinator = SessionCoordinator( + FakeProviderProfileStore(), + credentialsStore, + remote, + FakeLocalSessionGateway(), + ) + + val result = coordinator.selectProvider(localProfile) + + assertTrue(result is DomainResult.Success) + assertEquals(0, remote.loginCalls) + assertEquals(0, credentialsStore.loadCalls + credentialsStore.saveCalls) + assertEquals(localProfile.id, coordinator.observeActiveSession().first()?.profileId) + } + + @Test + fun aboxAuthenticationFailureDoesNotCreateSession() = runTest { + val fixture = Fixture() + fixture.coordinator.selectProvider(remoteProfile) + fixture.remote.failLogin(remoteProfile.id, DomainError.Authentication()) + + val result = fixture.coordinator.login(remoteProfile.id, credentials, false, false) + + assertTrue(result is DomainResult.Failure) + assertNull(fixture.coordinator.observeActiveSession().first()) + } + @Test fun loginPersistsIndependentUsernameAndAutoLoginChoices() = runTest { val fixture = Fixture() @@ -126,4 +160,47 @@ class SessionCoordinatorTest { private val local = FakeLocalSessionGateway() val coordinator = SessionCoordinator(profiles, credentials, remote, local) } + + private class CountingSessionGateway( + private val delegate: FakeSessionGateway = FakeSessionGateway(), + ) : SessionGateway by delegate { + var loginCalls = 0 + private set + + override suspend fun login( + profileId: ProviderProfileId, + credentials: LoginCredentials, + ): DomainResult { + loginCalls++ + return delegate.login(profileId, credentials) + } + + fun failLogin(profileId: ProviderProfileId, error: DomainError) { + delegate.failLogin(profileId, error) + } + } + + private class CountingCredentialStore( + private val delegate: FakeCredentialStore = FakeCredentialStore(), + ) : CredentialStore by delegate { + var loadCalls = 0 + private set + var saveCalls = 0 + private set + + override suspend fun loadCredentials( + profileId: ProviderProfileId, + ): DomainResult { + loadCalls++ + return delegate.loadCredentials(profileId) + } + + override suspend fun saveCredentials( + profileId: ProviderProfileId, + credentials: LoginCredentials, + ): DomainResult { + saveCalls++ + return delegate.saveCredentials(profileId, credentials) + } + } }