From 9e5a172ca7a47f68ae513f929e23d93ceffc81ee Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Tue, 24 Mar 2026 08:57:33 -0300 Subject: [PATCH 1/5] Move FetchAuthSession to a usecase --- .../auth/cognito/AWSCognitoAuthPlugin.kt | 7 +- .../auth/cognito/KotlinAuthFacadeInternal.kt | 40 -- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 143 ------ .../cognito/usecases/AuthUseCaseFactory.kt | 2 +- .../usecases/FetchAuthSessionUseCase.kt | 139 +++++- .../auth/cognito/AWSCognitoAuthPluginTest.kt | 9 +- .../cognito/RealAWSCognitoAuthPluginTest.kt | 161 ------- .../usecases/FetchAuthSessionUseCaseTest.kt | 415 ++++++++++++++++++ 8 files changed, 550 insertions(+), 366 deletions(-) delete mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt delete mode 100644 aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt create mode 100644 aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt index 79e45b8201..11bf8b3ff0 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt @@ -92,9 +92,6 @@ class AWSCognitoAuthPlugin : AuthPlugin() { internal lateinit var useCaseFactory: AuthUseCaseFactory private val pluginScope = CoroutineScope(Job() + Dispatchers.Default) - private val queueFacade: KotlinAuthFacadeInternal by lazy { - KotlinAuthFacadeInternal(realPlugin) - } private val queueChannel = Channel(capacity = Channel.UNLIMITED).apply { pluginScope.launch { @@ -301,10 +298,10 @@ class AWSCognitoAuthPlugin : AuthPlugin() { options: AuthFetchSessionOptions, onSuccess: Consumer, onError: Consumer - ) = enqueue(onSuccess, onError) { queueFacade.fetchAuthSession(options) } + ) = enqueue(onSuccess, onError) { useCaseFactory.fetchAuthSession().execute(options) } override fun fetchAuthSession(onSuccess: Consumer, onError: Consumer) = - enqueue(onSuccess, onError) { queueFacade.fetchAuthSession() } + enqueue(onSuccess, onError) { useCaseFactory.fetchAuthSession().execute() } override fun rememberDevice(onSuccess: Action, onError: Consumer) = enqueue(onSuccess, onError) { useCaseFactory.rememberDevice().execute() } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt deleted file mode 100644 index 19a679fbbf..0000000000 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.auth.cognito - -import com.amplifyframework.auth.AuthSession -import com.amplifyframework.auth.options.AuthFetchSessionOptions -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuthPlugin) { - - suspend fun fetchAuthSession(): AuthSession = suspendCoroutine { continuation -> - delegate.fetchAuthSession( - { continuation.resume(it) }, - { continuation.resumeWithException(it) } - ) - } - - suspend fun fetchAuthSession(options: AuthFetchSessionOptions): AuthSession = suspendCoroutine { continuation -> - delegate.fetchAuthSession( - options, - { continuation.resume(it) }, - { continuation.resumeWithException(it) } - ) - } -} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index cb1e6904c6..750a82aeff 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -19,30 +19,10 @@ import androidx.annotation.WorkerThread import com.amplifyframework.AmplifyException import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.auth.AWSCognitoAuthMetadataType -import com.amplifyframework.auth.AuthChannelEventName -import com.amplifyframework.auth.AuthException -import com.amplifyframework.auth.AuthSession -import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException -import com.amplifyframework.auth.exceptions.ConfigurationException -import com.amplifyframework.auth.exceptions.InvalidStateException -import com.amplifyframework.auth.exceptions.NotAuthorizedException -import com.amplifyframework.auth.exceptions.ServiceException -import com.amplifyframework.auth.exceptions.SessionExpiredException -import com.amplifyframework.auth.exceptions.SignedOutException -import com.amplifyframework.auth.exceptions.UnknownException -import com.amplifyframework.auth.options.AuthFetchSessionOptions -import com.amplifyframework.core.Amplify -import com.amplifyframework.core.Consumer -import com.amplifyframework.hub.HubChannel -import com.amplifyframework.hub.HubEvent import com.amplifyframework.logging.Logger import com.amplifyframework.statemachine.StateChangeListenerToken -import com.amplifyframework.statemachine.codegen.data.AmplifyCredential -import com.amplifyframework.statemachine.codegen.errors.SessionError import com.amplifyframework.statemachine.codegen.events.AuthEvent -import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent import com.amplifyframework.statemachine.codegen.states.AuthState -import com.amplifyframework.statemachine.codegen.states.AuthorizationState import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.collect @@ -96,125 +76,6 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.state.takeWhile { it !is AuthState.Configured && it !is AuthState.Error }.collect() } - fun fetchAuthSession(onSuccess: Consumer, onError: Consumer) { - fetchAuthSession(AuthFetchSessionOptions.defaults(), onSuccess, onError) - } - - fun fetchAuthSession( - options: AuthFetchSessionOptions, - onSuccess: Consumer, - onError: Consumer - ) { - val forceRefresh = options.forceRefresh - authStateMachine.getCurrentState { authState -> - when (val authZState = authState.authZState) { - is AuthorizationState.Configured -> { - authStateMachine.send(AuthorizationEvent(AuthorizationEvent.EventType.FetchUnAuthSession)) - _fetchAuthSession(onSuccess) - } - is AuthorizationState.SessionEstablished -> { - val credential = authZState.amplifyCredential - if (!credential.isValid() || forceRefresh) { - if (credential is AmplifyCredential.IdentityPoolFederated) { - authStateMachine.send( - AuthorizationEvent( - AuthorizationEvent.EventType.StartFederationToIdentityPool( - credential.federatedToken, - credential.identityId, - credential - ) - ) - ) - } else { - authStateMachine.send( - AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(credential)) - ) - } - _fetchAuthSession(onSuccess) - } else { - onSuccess.accept(credential.getCognitoSession()) - } - } - is AuthorizationState.Error -> { - val error = authZState.exception - if (error is SessionError) { - val amplifyCredential = error.amplifyCredential - if (amplifyCredential is AmplifyCredential.IdentityPoolFederated) { - authStateMachine.send( - AuthorizationEvent( - AuthorizationEvent.EventType.StartFederationToIdentityPool( - amplifyCredential.federatedToken, - amplifyCredential.identityId, - amplifyCredential - ) - ) - ) - } else { - authStateMachine.send( - AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(amplifyCredential)) - ) - } - _fetchAuthSession(onSuccess) - } else { - onError.accept(InvalidStateException()) - } - } - else -> onError.accept(InvalidStateException()) - } - } - } - - private fun _fetchAuthSession(onSuccess: Consumer) { - val token = StateChangeListenerToken() - authStateMachine.listen( - token, - { authState -> - when (val authZState = authState.authZState) { - is AuthorizationState.SessionEstablished -> { - authStateMachine.cancel(token) - onSuccess.accept(authZState.amplifyCredential.getCognitoSession()) - } - is AuthorizationState.Error -> { - authStateMachine.cancel(token) - when (val error = authZState.exception) { - is SessionError -> { - when (val innerException = error.exception) { - is SignedOutException -> { - onSuccess.accept(error.amplifyCredential.getCognitoSession(innerException)) - } - is SessionExpiredException -> { - onSuccess.accept(error.amplifyCredential.getCognitoSession(innerException)) - sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) - } - is ServiceException -> { - onSuccess.accept(error.amplifyCredential.getCognitoSession(innerException)) - } - is NotAuthorizedException -> { - onSuccess.accept(error.amplifyCredential.getCognitoSession(innerException)) - } - else -> { - val errorResult = UnknownException("Fetch auth session failed.", innerException) - onSuccess.accept(error.amplifyCredential.getCognitoSession(errorResult)) - } - } - } - is ConfigurationException -> { - val errorResult = InvalidAccountTypeException(error) - onSuccess.accept(AmplifyCredential.Empty.getCognitoSession(errorResult)) - } - else -> { - val errorResult = UnknownException("Fetch auth session failed.", error) - onSuccess.accept(AmplifyCredential.Empty.getCognitoSession(errorResult)) - } - } - } - else -> Unit - } - }, - null - ) - } - private fun addAuthStateChangeListener() { authStateMachine.listen( StateChangeListenerToken(), @@ -240,8 +101,4 @@ internal class RealAWSCognitoAuthPlugin( } ) } - - private fun sendHubEvent(eventName: String) { - Amplify.Hub.publish(HubChannel.AUTH, HubEvent.create(eventName)) - } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt index 8f72ab13b4..6652f56be7 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt @@ -27,7 +27,7 @@ internal class AuthUseCaseFactory( private val stateMachine: AuthStateMachine ) { - fun fetchAuthSession() = FetchAuthSessionUseCase(plugin) + fun fetchAuthSession() = FetchAuthSessionUseCase(stateMachine) fun associateWebAuthnCredential() = AssociateWebAuthnCredentialUseCase( client = authEnvironment.requireIdentityProviderClient(), diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt index 1eb9d96faa..991531d5ba 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt @@ -15,23 +15,136 @@ package com.amplifyframework.auth.cognito.usecases +import com.amplifyframework.auth.AuthChannelEventName import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import com.amplifyframework.auth.cognito.RealAWSCognitoAuthPlugin -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.getCognitoSession +import com.amplifyframework.auth.cognito.isValid +import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException +import com.amplifyframework.auth.exceptions.ConfigurationException +import com.amplifyframework.auth.exceptions.InvalidStateException +import com.amplifyframework.auth.exceptions.NotAuthorizedException +import com.amplifyframework.auth.exceptions.ServiceException +import com.amplifyframework.auth.exceptions.SessionExpiredException +import com.amplifyframework.auth.exceptions.SignedOutException +import com.amplifyframework.auth.exceptions.UnknownException +import com.amplifyframework.auth.options.AuthFetchSessionOptions +import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter +import com.amplifyframework.statemachine.codegen.data.AmplifyCredential +import com.amplifyframework.statemachine.codegen.errors.SessionError +import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent +import com.amplifyframework.statemachine.codegen.states.AuthorizationState +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.onSubscription internal class FetchAuthSessionUseCase( - private val plugin: RealAWSCognitoAuthPlugin + private val stateMachine: AuthStateMachine, + private val emitter: AuthHubEventEmitter = AuthHubEventEmitter() ) { - suspend fun execute(): AWSCognitoAuthSession { - // TODO - we should migrate the fetch auth session business logic to this class - val session = suspendCoroutine { continuation -> - plugin.fetchAuthSession( - onSuccess = { continuation.resume(it) }, - onError = { continuation.resumeWithException(it) } - ) + suspend fun execute( + options: AuthFetchSessionOptions = AuthFetchSessionOptions.defaults() + ): AWSCognitoAuthSession { + val forceRefresh = options.forceRefresh + val currentState = stateMachine.getCurrentState() + val authZState = currentState.authZState + + when (authZState) { + is AuthorizationState.Configured -> { + return waitForSession( + AuthorizationEvent(AuthorizationEvent.EventType.FetchUnAuthSession) + ) + } + is AuthorizationState.SessionEstablished -> { + val credential = authZState.amplifyCredential + if (credential.isValid() && !forceRefresh) { + return credential.getCognitoSession() as AWSCognitoAuthSession + } + val event = if (credential is AmplifyCredential.IdentityPoolFederated) { + AuthorizationEvent( + AuthorizationEvent.EventType.StartFederationToIdentityPool( + credential.federatedToken, + credential.identityId, + credential + ) + ) + } else { + AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(credential)) + } + return waitForSession(event) + } + is AuthorizationState.Error -> { + val error = authZState.exception + if (error is SessionError) { + val amplifyCredential = error.amplifyCredential + val event = if (amplifyCredential is AmplifyCredential.IdentityPoolFederated) { + AuthorizationEvent( + AuthorizationEvent.EventType.StartFederationToIdentityPool( + amplifyCredential.federatedToken, + amplifyCredential.identityId, + amplifyCredential + ) + ) + } else { + AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(amplifyCredential)) + } + return waitForSession(event) + } else { + throw InvalidStateException() + } + } + else -> throw InvalidStateException() } - return session as AWSCognitoAuthSession + } + + private suspend fun waitForSession(event: AuthorizationEvent): AWSCognitoAuthSession { + return stateMachine.state + .onSubscription { stateMachine.send(event) } + .drop(1) + .mapNotNull { authState -> + when (val authZState = authState.authZState) { + is AuthorizationState.SessionEstablished -> { + authZState.amplifyCredential.getCognitoSession() as AWSCognitoAuthSession + } + is AuthorizationState.Error -> { + when (val error = authZState.exception) { + is SessionError -> { + when (val innerException = error.exception) { + is SignedOutException -> { + error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession + } + is SessionExpiredException -> { + emitter.sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) + error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession + } + is ServiceException -> { + error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession + } + is NotAuthorizedException -> { + error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession + } + else -> { + val errorResult = UnknownException( + "Fetch auth session failed.", + innerException + ) + error.amplifyCredential.getCognitoSession(errorResult) as AWSCognitoAuthSession + } + } + } + is ConfigurationException -> { + val errorResult = InvalidAccountTypeException(error) + AmplifyCredential.Empty.getCognitoSession(errorResult) as AWSCognitoAuthSession + } + else -> { + val errorResult = UnknownException("Fetch auth session failed.", error) + AmplifyCredential.Empty.getCognitoSession(errorResult) as AWSCognitoAuthSession + } + } + } + else -> null + } + }.first() } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt index 28aeafa0be..9f4669596e 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt @@ -56,7 +56,6 @@ import com.amplifyframework.core.Consumer import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.mockk.verify import kotlin.test.assertEquals import org.junit.Before import org.junit.Test @@ -307,9 +306,11 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.fetchAuthSession() + authPlugin.fetchAuthSession(expectedOptions, expectedOnSuccess, expectedOnError) - verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchAuthSession(expectedOptions, any(), any()) } + coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute(expectedOptions) } } @Test @@ -317,9 +318,11 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.fetchAuthSession() + authPlugin.fetchAuthSession(expectedOnSuccess, expectedOnError) - verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchAuthSession(any(), any()) } + coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute() } } @Test diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt deleted file mode 100644 index bd91f1463e..0000000000 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.auth.cognito - -import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient -import com.amplifyframework.auth.AuthException -import com.amplifyframework.auth.AuthSession -import com.amplifyframework.auth.cognito.helpers.AuthHelper -import com.amplifyframework.auth.cognito.helpers.SRPHelper -import com.amplifyframework.core.Consumer -import com.amplifyframework.logging.Logger -import com.amplifyframework.statemachine.codegen.data.AmplifyCredential -import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens -import com.amplifyframework.statemachine.codegen.data.SignInMethod -import com.amplifyframework.statemachine.codegen.data.SignedInData -import com.amplifyframework.statemachine.codegen.data.UserPoolConfiguration -import com.amplifyframework.statemachine.codegen.states.AuthState -import com.amplifyframework.statemachine.codegen.states.AuthenticationState -import com.amplifyframework.statemachine.codegen.states.AuthorizationState -import io.mockk.coEvery -import io.mockk.every -import io.mockk.invoke -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.unmockkAll -import io.mockk.verify -import java.util.Date -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.After -import org.junit.Before -import org.junit.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class RealAWSCognitoAuthPluginTest { - - private val dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2Mj" + - "M5MDIyfQ.e4RpZTfAb3oXkfq3IwHtR_8Zhn0U1JDV7McZPlBXyhw" - - private var logger = mockk(relaxed = true) - private val appClientId = "app Client Id" - private val appClientSecret = "app Client Secret" - private var authConfiguration = mockk { - every { userPool } returns UserPoolConfiguration.invoke { - this.appClientId = this@RealAWSCognitoAuthPluginTest.appClientId - this.appClientSecret = this@RealAWSCognitoAuthPluginTest.appClientSecret - this.pinpointAppId = null - } - } - - private val credentials = AmplifyCredential.UserPool( - SignedInData( - "userId", - "username", - Date(0), - SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH), - CognitoUserPoolTokens(dummyToken, dummyToken, dummyToken, 120L) - ) - ) - - private val mockCognitoIPClient = mockk() - private var authService = mockk { - every { cognitoIdentityProviderClient } returns mockCognitoIPClient - } - - private val expectedEndpointId = "test-endpoint-id" - - private var authEnvironment = mockk { - every { context } returns mockk() - every { configuration } returns authConfiguration - every { logger } returns this@RealAWSCognitoAuthPluginTest.logger - every { cognitoAuthService } returns authService - every { getPinpointEndpointId() } returns expectedEndpointId - } - - private var authStateMachine = mockk(relaxed = true) - - private lateinit var plugin: RealAWSCognitoAuthPlugin - - @Before - fun setup() { - plugin = RealAWSCognitoAuthPlugin( - authConfiguration, - authEnvironment, - authStateMachine, - logger - ) - - mockkStatic("com.amplifyframework.auth.cognito.AWSCognitoAuthSessionKt") - every { any().isValid() } returns true - - // set up user pool - coEvery { authConfiguration.userPool } returns UserPoolConfiguration.invoke { - appClientId = "app Client Id" - appClientSecret = "app Client Secret" - } - - coEvery { authEnvironment.getUserContextData(any()) } returns null - - // set up SRP helper - mockkObject(SRPHelper) - mockkObject(AuthHelper) - coEvery { AuthHelper.getSecretHash(any(), any(), any()) } returns "dummy Hash" - - setupCurrentAuthState( - authNState = AuthenticationState.SignedIn( - mockk { - every { username } returns "username" - }, - mockk() - ), - authZState = AuthorizationState.SessionEstablished(credentials) - ) - } - - @After - fun teardown() { - unmockkAll() - } - - @Test - fun testFetchAuthSessionSucceedsIfSignedOut() { - // GIVEN - val onSuccess = mockk>() - val onError = mockk>(relaxed = true) - - setupCurrentAuthState( - authNState = AuthenticationState.SignedOut(mockk()), - authZState = AuthorizationState.Configured() - ) - - // WHEN - plugin.fetchAuthSession(onSuccess, onError) - - // THEN - verify(exactly = 0) { onSuccess.accept(any()) } - } - - private fun setupCurrentAuthState(authNState: AuthenticationState? = null, authZState: AuthorizationState? = null) { - val currentAuthState = mockk { - every { this@mockk.authNState } returns authNState - every { this@mockk.authZState } returns authZState - } - every { authStateMachine.getCurrentState(captureLambda()) } answers { - lambda<(AuthState) -> Unit>().invoke(currentAuthState) - } - } -} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt new file mode 100644 index 0000000000..053dc202d3 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt @@ -0,0 +1,415 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.auth.cognito.usecases + +import com.amplifyframework.auth.AuthChannelEventName +import com.amplifyframework.auth.cognito.AWSCognitoAuthSession +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.isValid +import com.amplifyframework.auth.cognito.testUtil.authState +import com.amplifyframework.auth.exceptions.ConfigurationException +import com.amplifyframework.auth.exceptions.InvalidStateException +import com.amplifyframework.auth.exceptions.NotAuthorizedException +import com.amplifyframework.auth.exceptions.ServiceException +import com.amplifyframework.auth.exceptions.SessionExpiredException +import com.amplifyframework.auth.exceptions.SignedOutException +import com.amplifyframework.auth.exceptions.UnknownException +import com.amplifyframework.auth.options.AuthFetchSessionOptions +import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter +import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.codegen.data.AWSCredentials +import com.amplifyframework.statemachine.codegen.data.AmplifyCredential +import com.amplifyframework.statemachine.codegen.data.FederatedToken +import com.amplifyframework.statemachine.codegen.data.SignedOutData +import com.amplifyframework.statemachine.codegen.errors.SessionError +import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent +import com.amplifyframework.statemachine.codegen.states.AuthState +import com.amplifyframework.statemachine.codegen.states.AuthenticationState +import com.amplifyframework.statemachine.codegen.states.AuthorizationState +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.mockk.coEvery +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class FetchAuthSessionUseCaseTest { + + private val awsCredentials = AWSCredentials( + accessKeyId = "accessKeyId", + secretAccessKey = "secretAccessKey", + sessionToken = "sessionToken", + expiration = 9999999999L + ) + + private val userPoolCredential: AmplifyCredential.UserAndIdentityPool = mockk(relaxed = true) { + every { identityId } returns "identity-id" + every { credentials } returns awsCredentials + } + + private val federatedCredential = AmplifyCredential.IdentityPoolFederated( + federatedToken = FederatedToken("token", "graph.facebook.com"), + identityId = "identity-id", + credentials = awsCredentials + ) + + private val stateFlow = MutableStateFlow( + authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + ) + + private val stateMachine: AuthStateMachine = mockk { + every { state } returns stateFlow + every { stateTransitions } answers { stateFlow.drop(1) } + coEvery { getCurrentState() } answers { stateFlow.value } + justRun { send(any()) } + } + + private val emitter: AuthHubEventEmitter = mockk(relaxed = true) + + private val useCase = FetchAuthSessionUseCase( + stateMachine = stateMachine, + emitter = emitter + ) + + @Before + fun setUp() { + mockkStatic("com.amplifyframework.auth.cognito.AWSCognitoAuthSessionKt") + every { any().isValid() } returns true + } + + @After + fun tearDown() { + unmockkStatic("com.amplifyframework.auth.cognito.AWSCognitoAuthSessionKt") + } + + @Test + fun `sends FetchUnAuthSession when authorization state is Configured`() = runTest { + backgroundScope.async { useCase.execute() } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + event.eventType.shouldBeInstanceOf() + } + ) + } + } + + @Test + fun `returns cached session for valid credentials with forceRefresh false`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.SessionEstablished(userPoolCredential) + ) + + val result = useCase.execute() + result.shouldBeInstanceOf() + } + + @Test + fun `sends RefreshSession for invalid non-federated credentials`() = runTest { + every { any().isValid() } returns false + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.SessionEstablished(userPoolCredential) + ) + + backgroundScope.async { useCase.execute() } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + val type = event.eventType + .shouldBeInstanceOf() + type.amplifyCredential shouldBe userPoolCredential + } + ) + } + } + + @Test + fun `sends StartFederationToIdentityPool for invalid federated credentials`() = runTest { + every { any().isValid() } returns false + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.SessionEstablished(federatedCredential) + ) + + backgroundScope.async { useCase.execute() } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + val type = event.eventType + .shouldBeInstanceOf() + type.token shouldBe federatedCredential.federatedToken + type.identityId shouldBe federatedCredential.identityId + } + ) + } + } + + @Test + fun `sends RefreshSession for forceRefresh with valid non-federated credentials`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.SessionEstablished(userPoolCredential) + ) + + val options = AuthFetchSessionOptions.builder().forceRefresh(true).build() + backgroundScope.async { useCase.execute(options) } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + event.eventType.shouldBeInstanceOf() + } + ) + } + } + + @Test + fun `sends RefreshSession for SessionError with non-federated credentials`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(Exception("session error"), userPoolCredential) + ) + ) + + backgroundScope.async { useCase.execute() } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + event.eventType.shouldBeInstanceOf() + } + ) + } + } + + @Test + fun `sends StartFederationToIdentityPool for SessionError with federated credentials`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(Exception("session error"), federatedCredential) + ) + ) + + backgroundScope.async { useCase.execute() } + runCurrent() + + verify { + stateMachine.send( + withArg { + val event = it.shouldBeInstanceOf() + event.eventType + .shouldBeInstanceOf() + } + ) + } + } + + @Test + fun `throws InvalidStateException for non-SessionError in error state`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error(RuntimeException("not a session error")) + ) + + shouldThrow { + useCase.execute() + } + } + + @Test + fun `embeds SignedOutException in session result`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Error( + SessionError(SignedOutException(), AmplifyCredential.Empty) + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.identityIdResult.error.shouldBeInstanceOf() + } + + @Test + fun `embeds SessionExpiredException in session result and publishes hub event`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(SessionExpiredException(), AmplifyCredential.Empty) + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.identityIdResult.error.shouldBeInstanceOf() + + verify { + emitter.sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) + } + } + + @Test + fun `embeds ServiceException in session result`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(ServiceException("service error", "retry"), AmplifyCredential.Empty) + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.identityIdResult.error.shouldBeInstanceOf() + } + + @Test + fun `embeds NotAuthorizedException in session result`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(NotAuthorizedException(), AmplifyCredential.Empty) + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.identityIdResult.error.shouldBeInstanceOf() + } + + @Test + fun `wraps unknown exceptions in UnknownException`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + SessionError(RuntimeException("something broke"), AmplifyCredential.Empty) + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.identityIdResult.error.shouldBeInstanceOf() + } + + @Test + fun `returns empty session with InvalidAccountTypeException for ConfigurationException`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedOut(SignedOutData()), + authZState = AuthorizationState.Configured() + ) + + val deferred = backgroundScope.async { useCase.execute() } + runCurrent() + + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.Error( + ConfigurationException("config error", "fix config") + ) + ) + + val result = deferred.await() + result.shouldBeInstanceOf() + result.isSignedIn shouldBe false + } + + @Test + fun `throws InvalidStateException for unexpected authorization states`() = runTest { + stateFlow.value = authState( + authNState = AuthenticationState.SignedIn(mockk(), mockk()), + authZState = AuthorizationState.FederatingToIdentityPool(mockk(), mockk(), mockk()) + ) + + shouldThrow { + useCase.execute() + } + } +} From cfadaef716d26eb9f09c4df14d9d840ac1ca4a2d Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Thu, 26 Mar 2026 14:25:32 -0300 Subject: [PATCH 2/5] Cleanup FetchAuthSessionUseCase --- .../auth/cognito/AWSCognitoAuthSession.kt | 4 +- .../usecases/FetchAuthSessionUseCase.kt | 152 ++++++++---------- 2 files changed, 68 insertions(+), 88 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthSession.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthSession.kt index 63f37af112..1aec290fb7 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthSession.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthSession.kt @@ -66,9 +66,7 @@ internal fun AmplifyCredential.isValid(): Boolean = when (this) { else -> false } -internal fun AmplifyCredential.getCognitoSession( - exception: AuthException? = null -): AWSAuthSessionBehavior { +internal fun AmplifyCredential.getCognitoSession(exception: AuthException? = null): AWSCognitoAuthSession { fun getCredentialsResult( awsCredentials: CognitoCredentials, exception: AuthException? diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt index 991531d5ba..5d1d2b4b39 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt @@ -18,133 +18,115 @@ package com.amplifyframework.auth.cognito.usecases import com.amplifyframework.auth.AuthChannelEventName import com.amplifyframework.auth.cognito.AWSCognitoAuthSession import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter.Companion.toAuthException +import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException import com.amplifyframework.auth.cognito.getCognitoSession import com.amplifyframework.auth.cognito.isValid -import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.auth.exceptions.InvalidStateException import com.amplifyframework.auth.exceptions.NotAuthorizedException import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.auth.exceptions.SessionExpiredException import com.amplifyframework.auth.exceptions.SignedOutException -import com.amplifyframework.auth.exceptions.UnknownException import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter import com.amplifyframework.statemachine.codegen.data.AmplifyCredential import com.amplifyframework.statemachine.codegen.errors.SessionError import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent import com.amplifyframework.statemachine.codegen.states.AuthorizationState -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onSubscription internal class FetchAuthSessionUseCase( private val stateMachine: AuthStateMachine, private val emitter: AuthHubEventEmitter = AuthHubEventEmitter() ) { - suspend fun execute( - options: AuthFetchSessionOptions = AuthFetchSessionOptions.defaults() - ): AWSCognitoAuthSession { + suspend fun execute(options: AuthFetchSessionOptions = AuthFetchSessionOptions.defaults()): AWSCognitoAuthSession { val forceRefresh = options.forceRefresh val currentState = stateMachine.getCurrentState() - val authZState = currentState.authZState - when (authZState) { + return when (val authZState = currentState.authZState) { is AuthorizationState.Configured -> { - return waitForSession( - AuthorizationEvent(AuthorizationEvent.EventType.FetchUnAuthSession) - ) + waitForSession(AuthorizationEvent(AuthorizationEvent.EventType.FetchUnAuthSession)) } is AuthorizationState.SessionEstablished -> { val credential = authZState.amplifyCredential if (credential.isValid() && !forceRefresh) { - return credential.getCognitoSession() as AWSCognitoAuthSession - } - val event = if (credential is AmplifyCredential.IdentityPoolFederated) { - AuthorizationEvent( - AuthorizationEvent.EventType.StartFederationToIdentityPool( - credential.federatedToken, - credential.identityId, - credential - ) - ) + // Return existing credential + credential.getCognitoSession() } else { - AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(credential)) + // Refresh session + val event = getRefreshSessionEvent(credential) + waitForSession(event) } - return waitForSession(event) } is AuthorizationState.Error -> { - val error = authZState.exception - if (error is SessionError) { - val amplifyCredential = error.amplifyCredential - val event = if (amplifyCredential is AmplifyCredential.IdentityPoolFederated) { - AuthorizationEvent( - AuthorizationEvent.EventType.StartFederationToIdentityPool( - amplifyCredential.federatedToken, - amplifyCredential.identityId, - amplifyCredential - ) - ) - } else { - AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(amplifyCredential)) - } - return waitForSession(event) - } else { - throw InvalidStateException() + when (val error = authZState.exception) { + is SessionError -> waitForSession(getRefreshSessionEvent(error.amplifyCredential)) + else -> throw InvalidStateException() } } else -> throw InvalidStateException() } } - private suspend fun waitForSession(event: AuthorizationEvent): AWSCognitoAuthSession { - return stateMachine.state - .onSubscription { stateMachine.send(event) } - .drop(1) - .mapNotNull { authState -> - when (val authZState = authState.authZState) { - is AuthorizationState.SessionEstablished -> { - authZState.amplifyCredential.getCognitoSession() as AWSCognitoAuthSession - } - is AuthorizationState.Error -> { - when (val error = authZState.exception) { - is SessionError -> { - when (val innerException = error.exception) { - is SignedOutException -> { - error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession - } - is SessionExpiredException -> { - emitter.sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) - error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession - } - is ServiceException -> { - error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession - } - is NotAuthorizedException -> { - error.amplifyCredential.getCognitoSession(innerException) as AWSCognitoAuthSession - } - else -> { - val errorResult = UnknownException( - "Fetch auth session failed.", - innerException - ) - error.amplifyCredential.getCognitoSession(errorResult) as AWSCognitoAuthSession - } + private fun getRefreshSessionEvent(credential: AmplifyCredential): AuthorizationEvent = + if (credential is AmplifyCredential.IdentityPoolFederated) { + AuthorizationEvent( + AuthorizationEvent.EventType.StartFederationToIdentityPool( + credential.federatedToken, + credential.identityId, + credential + ) + ) + } else { + AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(credential)) + } + + private suspend fun waitForSession(event: AuthorizationEvent): AWSCognitoAuthSession = stateMachine.state + .onSubscription { stateMachine.send(event) } + .drop(1) + .mapNotNull { authState -> + when (val authZState = authState.authZState) { + is AuthorizationState.SessionEstablished -> { + authZState.amplifyCredential.getCognitoSession() + } + is AuthorizationState.Error -> { + when (val error = authZState.exception) { + is SessionError -> { + when (val innerException = error.exception) { + is SignedOutException -> { + error.amplifyCredential.getCognitoSession(innerException) + } + is SessionExpiredException -> { + emitter.sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) + error.amplifyCredential.getCognitoSession(innerException) + } + is ServiceException -> { + error.amplifyCredential.getCognitoSession(innerException) + } + is NotAuthorizedException -> { + error.amplifyCredential.getCognitoSession(innerException) + } + else -> { + val errorResult = innerException.toAuthException("Fetch auth session failed") + error.amplifyCredential.getCognitoSession(errorResult) } } - is ConfigurationException -> { - val errorResult = InvalidAccountTypeException(error) - AmplifyCredential.Empty.getCognitoSession(errorResult) as AWSCognitoAuthSession - } - else -> { - val errorResult = UnknownException("Fetch auth session failed.", error) - AmplifyCredential.Empty.getCognitoSession(errorResult) as AWSCognitoAuthSession - } + } + is ConfigurationException -> { + val errorResult = InvalidAccountTypeException(error) + AmplifyCredential.Empty.getCognitoSession(errorResult) + } + else -> { + val errorResult = error.toAuthException("Fetch auth session failed") + AmplifyCredential.Empty.getCognitoSession(errorResult) } } - else -> null } - }.first() - } + else -> null + } + }.first() } From 8ed2dba285bb8aed0551644efc933ad74bfbbe00 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Thu, 26 Mar 2026 14:31:57 -0300 Subject: [PATCH 3/5] Remove unused stateTransitions flow --- .../java/com/amplifyframework/statemachine/StateMachine.kt | 6 ------ .../usecases/ClearFederationToIdentityPoolUseCaseTest.kt | 2 -- .../cognito/usecases/FederateToIdentityPoolUseCaseTest.kt | 2 -- .../auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt | 2 -- .../auth/cognito/usecases/SignOutUseCaseTest.kt | 2 -- .../auth/cognito/usecases/WebUiSignInUseCaseTest.kt | 2 -- 6 files changed, 16 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt index b0d034710b..690688bf18 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt @@ -23,10 +23,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext @@ -74,10 +72,6 @@ internal open class StateMachine - get() = state.drop(1) - // Manage consistency of internal state machine state and limits invocation of listeners to a minimum of one at a time. @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class) private val stateMachineContext = SupervisorJob() + newSingleThreadContext("StateMachineContext") diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ClearFederationToIdentityPoolUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ClearFederationToIdentityPoolUseCaseTest.kt index 55efa1a8a8..87d326f427 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ClearFederationToIdentityPoolUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ClearFederationToIdentityPoolUseCaseTest.kt @@ -36,7 +36,6 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.test.runTest import org.junit.Test @@ -51,7 +50,6 @@ class ClearFederationToIdentityPoolUseCaseTest { private val stateMachine: AuthStateMachine = mockk { every { state } returns stateFlow - every { stateTransitions } answers { stateFlow.drop(1) } coEvery { getCurrentState() } answers { stateFlow.value } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FederateToIdentityPoolUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FederateToIdentityPoolUseCaseTest.kt index 13634e573d..0ede8089f3 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FederateToIdentityPoolUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FederateToIdentityPoolUseCaseTest.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -69,7 +68,6 @@ class FederateToIdentityPoolUseCaseTest { private val stateMachine: AuthStateMachine = mockk { every { state } returns stateFlow - every { stateTransitions } answers { stateFlow.drop(1) } coEvery { getCurrentState() } answers { stateFlow.value } justRun { send(any()) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt index 053dc202d3..69c460b58d 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt @@ -52,7 +52,6 @@ import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -89,7 +88,6 @@ class FetchAuthSessionUseCaseTest { private val stateMachine: AuthStateMachine = mockk { every { state } returns stateFlow - every { stateTransitions } answers { stateFlow.drop(1) } coEvery { getCurrentState() } answers { stateFlow.value } justRun { send(any()) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SignOutUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SignOutUseCaseTest.kt index e3b207f9d0..767d97e39b 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SignOutUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SignOutUseCaseTest.kt @@ -40,7 +40,6 @@ import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -56,7 +55,6 @@ class SignOutUseCaseTest { private val stateMachine: AuthStateMachine = mockk { every { state } returns stateFlow - every { stateTransitions } answers { stateFlow.drop(1) } coEvery { getCurrentState() } answers { stateFlow.value } justRun { send(any()) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/WebUiSignInUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/WebUiSignInUseCaseTest.kt index 823730e858..18e9ff5477 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/WebUiSignInUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/WebUiSignInUseCaseTest.kt @@ -51,7 +51,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -76,7 +75,6 @@ class WebUiSignInUseCaseTest { private val stateMachine: AuthStateMachine = mockk { every { state } returns stateFlow - every { stateTransitions } answers { stateFlow.drop(1) } coEvery { getCurrentState() } answers { stateFlow.value } justRun { send(any()) } } From cc73c2c7d303543f7d283fa2ffb04cd9b5eb841e Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Thu, 26 Mar 2026 14:52:40 -0300 Subject: [PATCH 4/5] Keep same error message --- .../auth/cognito/usecases/FetchAuthSessionUseCase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt index 5d1d2b4b39..526063a4bc 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCase.kt @@ -111,7 +111,7 @@ internal class FetchAuthSessionUseCase( error.amplifyCredential.getCognitoSession(innerException) } else -> { - val errorResult = innerException.toAuthException("Fetch auth session failed") + val errorResult = innerException.toAuthException("Fetch auth session failed.") error.amplifyCredential.getCognitoSession(errorResult) } } @@ -121,7 +121,7 @@ internal class FetchAuthSessionUseCase( AmplifyCredential.Empty.getCognitoSession(errorResult) } else -> { - val errorResult = error.toAuthException("Fetch auth session failed") + val errorResult = error.toAuthException("Fetch auth session failed.") AmplifyCredential.Empty.getCognitoSession(errorResult) } } From 748432553cb3eaa1bee3adafb3d30157fc1c4467 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Fri, 27 Mar 2026 10:18:11 -0300 Subject: [PATCH 5/5] Update feature test to match change in in exception --- .../FetchAuthSessionTestCaseGenerator.kt | 23 +++++++++++------- ...ssfully_returned_after_failed_refresh.json | 24 +++++++++---------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchAuthSessionTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchAuthSessionTestCaseGenerator.kt index 744ee30145..33954fc9ec 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchAuthSessionTestCaseGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchAuthSessionTestCaseGenerator.kt @@ -20,6 +20,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResourceNotFoundExc import com.amplifyframework.auth.AWSCognitoUserPoolTokens import com.amplifyframework.auth.AWSCredentials import com.amplifyframework.auth.cognito.AWSCognitoAuthSession +import com.amplifyframework.auth.cognito.exceptions.service.ResourceNotFoundException as AmplifyResourceNotFoundException import com.amplifyframework.auth.cognito.featuretest.API import com.amplifyframework.auth.cognito.featuretest.AuthAPI import com.amplifyframework.auth.cognito.featuretest.CognitoType @@ -75,7 +76,7 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { CognitoType.CognitoIdentity, "getId", ResponseType.Failure, - TooManyRequestsException.invoke { + TooManyRequestsException { message = "Error type: Client, Protocol response: (empty response)" }.toJsonElement() ) @@ -98,7 +99,7 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { CognitoType.CognitoIdentity, "getCredentialsForIdentity", ResponseType.Failure, - TooManyRequestsException.invoke { + TooManyRequestsException { message = "Error type: Client, Protocol response: (empty response)" }.toJsonElement() ) @@ -107,7 +108,7 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { CognitoType.CognitoIdentityProvider, "getTokensFromRefreshToken", ResponseType.Failure, - ResourceNotFoundException.invoke { + ResourceNotFoundException { message = "Error type: Client, Protocol response: (empty response)" }.toJsonElement() ) @@ -156,20 +157,24 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { private val unknownRefreshException = UnknownException( message = "Fetch auth session failed.", - cause = ResourceNotFoundException.invoke { } + cause = ResourceNotFoundException { } ) private val identityRefreshException = UnknownException( message = "Fetch auth session failed.", - cause = TooManyRequestsException.invoke { } + cause = TooManyRequestsException { } + ) + + private val resourceNotFoundException = AmplifyResourceNotFoundException( + cause = ResourceNotFoundException { } ) private val expectedRefreshFailure = AWSCognitoAuthSession( isSignedIn = true, - identityIdResult = AuthSessionResult.failure(unknownRefreshException), - awsCredentialsResult = AuthSessionResult.failure(unknownRefreshException), - userSubResult = AuthSessionResult.failure(unknownRefreshException), - userPoolTokensResult = AuthSessionResult.failure(unknownRefreshException) + identityIdResult = AuthSessionResult.failure(resourceNotFoundException), + awsCredentialsResult = AuthSessionResult.failure(resourceNotFoundException), + userSubResult = AuthSessionResult.failure(resourceNotFoundException), + userPoolTokensResult = AuthSessionResult.failure(resourceNotFoundException) ).toJsonElement() private val expectedRefreshIdentityFailure = AWSCognitoAuthSession( diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/AuthSession_object_is_successfully_returned_after_failed_refresh.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/AuthSession_object_is_successfully_returned_after_failed_refresh.json index 4ea73f71e7..a59aabd750 100644 --- a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/AuthSession_object_is_successfully_returned_after_failed_refresh.json +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/AuthSession_object_is_successfully_returned_after_failed_refresh.json @@ -30,18 +30,18 @@ "response": { "accessToken": null, "awsCredentialsResult": { - "errorType": "UnknownException", - "errorMessage": "Fetch auth session failed.", - "recoverySuggestion": "See the attached exception for more details", + "errorType": "ResourceNotFoundException", + "errorMessage": "Could not find the requested online resource.", + "recoverySuggestion": "Retry with exponential back-off or check your config file to be sure the endpoint is valid.", "cause": { "errorType": "ResourceNotFoundException", "errorMessage": "Error type: Client, Protocol response: (empty response)" } }, "identityIdResult": { - "errorType": "UnknownException", - "errorMessage": "Fetch auth session failed.", - "recoverySuggestion": "See the attached exception for more details", + "errorType": "ResourceNotFoundException", + "errorMessage": "Could not find the requested online resource.", + "recoverySuggestion": "Retry with exponential back-off or check your config file to be sure the endpoint is valid.", "cause": { "errorType": "ResourceNotFoundException", "errorMessage": "Error type: Client, Protocol response: (empty response)" @@ -49,18 +49,18 @@ }, "isSignedIn": true, "userPoolTokensResult": { - "errorType": "UnknownException", - "errorMessage": "Fetch auth session failed.", - "recoverySuggestion": "See the attached exception for more details", + "errorType": "ResourceNotFoundException", + "errorMessage": "Could not find the requested online resource.", + "recoverySuggestion": "Retry with exponential back-off or check your config file to be sure the endpoint is valid.", "cause": { "errorType": "ResourceNotFoundException", "errorMessage": "Error type: Client, Protocol response: (empty response)" } }, "userSubResult": { - "errorType": "UnknownException", - "errorMessage": "Fetch auth session failed.", - "recoverySuggestion": "See the attached exception for more details", + "errorType": "ResourceNotFoundException", + "errorMessage": "Could not find the requested online resource.", + "recoverySuggestion": "Retry with exponential back-off or check your config file to be sure the endpoint is valid.", "cause": { "errorType": "ResourceNotFoundException", "errorMessage": "Error type: Client, Protocol response: (empty response)"