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 38bbf0cdf4..e9db425a6a 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 @@ -176,7 +176,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { logger ) - useCaseFactory = AuthUseCaseFactory(authEnvironment, authStateMachine) + useCaseFactory = AuthUseCaseFactory(realPlugin, authEnvironment, authStateMachine) blockQueueChannelWhileConfiguring() } @@ -291,10 +291,10 @@ class AWSCognitoAuthPlugin : AuthPlugin() { options: AuthFetchSessionOptions, onSuccess: Consumer, onError: Consumer - ) = enqueue(onSuccess, onError) { useCaseFactory.fetchAuthSession().execute(options) } + ) = enqueue(onSuccess, onError) { queueFacade.fetchAuthSession(options) } override fun fetchAuthSession(onSuccess: Consumer, onError: Consumer) = - enqueue(onSuccess, onError) { useCaseFactory.fetchAuthSession().execute() } + enqueue(onSuccess, onError) { queueFacade.fetchAuthSession() } 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/AWSCognitoAuthSession.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthSession.kt index e85cb3b9a9..7f31374087 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,7 +66,9 @@ internal fun AmplifyCredential.isValid(): Boolean = when (this) { else -> false } -internal fun AmplifyCredential.getCognitoSession(exception: AuthException? = null): AWSCognitoAuthSession { +internal fun AmplifyCredential.getCognitoSession( + exception: AuthException? = null +): AWSAuthSessionBehavior { fun getCredentialsResult( awsCredentials: CognitoCredentials, exception: AuthException? 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 index a2bc97903d..bc9f2335b4 100644 --- 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 @@ -18,9 +18,11 @@ package com.amplifyframework.auth.cognito import android.app.Activity import android.content.Intent import com.amplifyframework.auth.AuthProvider +import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult import com.amplifyframework.auth.options.AuthConfirmSignInOptions +import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.options.AuthSignInOptions import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions @@ -116,6 +118,21 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth delegate.handleWebUISignInResponse(intent) } + 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) } + ) + } + suspend fun signOut(): AuthSignOutResult = suspendCoroutine { continuation -> delegate.signOut { continuation.resume(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 5d9d9d7eff..429392d637 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 @@ -29,11 +29,13 @@ import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthException import com.amplifyframework.auth.AuthFactorType import com.amplifyframework.auth.AuthProvider +import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidOauthConfigurationException import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException import com.amplifyframework.auth.cognito.exceptions.invalidstate.SignedInException import com.amplifyframework.auth.cognito.exceptions.service.HostedUISignOutException +import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException import com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException import com.amplifyframework.auth.cognito.exceptions.service.UserCancelledException import com.amplifyframework.auth.cognito.helpers.HostedUIHelper @@ -55,9 +57,15 @@ import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult import com.amplifyframework.auth.cognito.result.GlobalSignOutError import com.amplifyframework.auth.cognito.result.HostedUIError import com.amplifyframework.auth.cognito.result.RevokeTokenError +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.AuthConfirmSignInOptions +import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.options.AuthSignInOptions import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions @@ -104,6 +112,9 @@ import java.lang.ref.WeakReference import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.takeWhile @@ -980,6 +991,142 @@ internal class RealAWSCognitoAuthPlugin( } } + private suspend fun getSession(): AWSCognitoAuthSession = suspendCoroutine { continuation -> + fetchAuthSession( + { authSession -> + if (authSession is AWSCognitoAuthSession) { + continuation.resume(authSession) + } else { + continuation.resumeWithException( + UnknownException( + message = "fetchAuthSession did not return a type of AWSCognitoAuthSession" + ) + ) + } + }, + { continuation.resumeWithException(it) } + ) + } + + 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 + ) + } + fun signOut(onComplete: Consumer) { signOut(AuthSignOutOptions.builder().build(), onComplete) } 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 584cd2f68d..fdbc5e2a2a 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 @@ -17,19 +17,19 @@ package com.amplifyframework.auth.cognito.usecases import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.RealAWSCognitoAuthPlugin import com.amplifyframework.auth.cognito.helpers.WebAuthnHelper import com.amplifyframework.auth.cognito.requireIdentityProviderClient import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter internal class AuthUseCaseFactory( + private val plugin: RealAWSCognitoAuthPlugin, private val authEnvironment: AuthEnvironment, private val stateMachine: AuthStateMachine, private val hubEmitter: AuthHubEventEmitter = AuthHubEventEmitter() ) { - fun fetchAuthSession() = FetchAuthSessionUseCase( - stateMachine = stateMachine - ) + fun fetchAuthSession() = FetchAuthSessionUseCase(plugin) 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 daa2270533..1eb9d96faa 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,114 +15,23 @@ 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.exceptions.service.InvalidAccountTypeException -import com.amplifyframework.auth.cognito.getCognitoSession -import com.amplifyframework.auth.cognito.isValid -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.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.first -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onStart +import com.amplifyframework.auth.cognito.RealAWSCognitoAuthPlugin +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine internal class FetchAuthSessionUseCase( - private val stateMachine: AuthStateMachine, - private val emitter: AuthHubEventEmitter = AuthHubEventEmitter() + private val plugin: RealAWSCognitoAuthPlugin ) { - suspend fun execute(options: AuthFetchSessionOptions = AuthFetchSessionOptions.defaults()): AWSCognitoAuthSession = - when (val authZState = stateMachine.getCurrentState().authZState) { - is AuthorizationState.Configured -> { - listenForSessionEstablished( - eventToSend = AuthorizationEvent(AuthorizationEvent.EventType.FetchUnAuthSession) - ) - } - is AuthorizationState.SessionEstablished -> { - val credential = authZState.amplifyCredential - if (!credential.isValid() || options.forceRefresh) { - listenForSessionEstablished(eventToSend = refreshSessionEvent(credential)) - } else { - credential.getCognitoSession() - } - } - is AuthorizationState.Error -> { - val error = authZState.exception - if (error is SessionError) { - listenForSessionEstablished(eventToSend = refreshSessionEvent(error.amplifyCredential)) - } else { - throw InvalidStateException() - } - } - else -> throw InvalidStateException() - } - - private fun refreshSessionEvent(credential: AmplifyCredential): StateMachineEvent = - if (credential is AmplifyCredential.IdentityPoolFederated) { - AuthorizationEvent( - AuthorizationEvent.EventType.StartFederationToIdentityPool( - credential.federatedToken, - credential.identityId, - credential - ) + 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) } ) - } else { - AuthorizationEvent(AuthorizationEvent.EventType.RefreshSession(credential)) } - - private suspend fun listenForSessionEstablished(eventToSend: StateMachineEvent): AWSCognitoAuthSession { - val session = stateMachine.stateTransitions - .onStart { - stateMachine.send(eventToSend) - } - .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 ServiceException -> error.amplifyCredential.getCognitoSession(innerException) - is NotAuthorizedException -> error.amplifyCredential.getCognitoSession( - innerException - ) - is SessionExpiredException -> { - emitter.sendHubEvent(AuthChannelEventName.SESSION_EXPIRED.toString()) - error.amplifyCredential.getCognitoSession(innerException) - } - else -> { - val errorResult = UnknownException("Fetch auth session failed.", innerException) - error.amplifyCredential.getCognitoSession(errorResult) - } - } - } - is ConfigurationException -> { - val errorResult = InvalidAccountTypeException(error) - AmplifyCredential.Empty.getCognitoSession(errorResult) - } - else -> { - val errorResult = UnknownException("Fetch auth session failed.", error) - AmplifyCredential.Empty.getCognitoSession(errorResult) - } - } - } - else -> null // no-op - } - }.first() - - return session + return session as AWSCognitoAuthSession } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/AuthorizationEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/AuthorizationEvent.kt index 1ef7592885..debf1d3edf 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/AuthorizationEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/AuthorizationEvent.kt @@ -21,10 +21,12 @@ import com.amplifyframework.statemachine.codegen.data.AmplifyCredential import com.amplifyframework.statemachine.codegen.data.FederatedToken import java.util.Date -internal class AuthorizationEvent(val eventType: EventType, override val time: Date? = null) : StateMachineEvent { +internal class AuthorizationEvent(val eventType: EventType, override val time: Date? = null) : + StateMachineEvent { sealed class EventType { - data object Configure : EventType() - data object FetchUnAuthSession : EventType() + object Configure : EventType() + object FetchAuthSession : EventType() + object FetchUnAuthSession : EventType() data class Fetched(val identityId: String, val awsCredentials: AWSCredentials) : EventType() data class RefreshSession(val amplifyCredential: AmplifyCredential) : EventType() data class Refreshed(val amplifyCredential: AmplifyCredential) : EventType() 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 b6f8720080..bdf748d05b 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 @@ -313,11 +313,9 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } - val useCase = authPlugin.useCaseFactory.fetchAuthSession() - authPlugin.fetchAuthSession(expectedOptions, expectedOnSuccess, expectedOnError) - coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute(expectedOptions) } + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchAuthSession(expectedOptions, any(), any()) } } @Test @@ -325,11 +323,9 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } - val useCase = authPlugin.useCaseFactory.fetchAuthSession() - authPlugin.fetchAuthSession(expectedOnSuccess, expectedOnError) - coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute() } + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchAuthSession(any(), any()) } } @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 index 14baae256c..6a5e57779b 100644 --- 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 @@ -17,6 +17,7 @@ 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.exceptions.configuration.InvalidUserPoolConfigurationException import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.SRPHelper @@ -141,6 +142,24 @@ class RealAWSCognitoAuthPluginTest { 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()) } + } + @Test fun testCustomSignInWithSRPSucceedsWithChallenge() { // GIVEN diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/EventMatchers.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/EventMatchers.kt index 3fc072cabd..84cdf998dc 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/EventMatchers.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/EventMatchers.kt @@ -17,7 +17,6 @@ package com.amplifyframework.auth.cognito.testUtil import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.events.AuthenticationEvent -import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent import com.amplifyframework.statemachine.codegen.events.DeleteUserEvent import com.amplifyframework.statemachine.codegen.events.SignInEvent import com.amplifyframework.statemachine.codegen.events.SignUpEvent @@ -58,14 +57,6 @@ internal inline fun MockKVerificatio assertions(type) } -internal inline fun MockKVerificationScope.withAuthZEvent( - noinline assertions: MockKAssertScope.(T) -> Unit = { } -) = withArg { - val event = it.shouldBeInstanceOf() - val type = event.eventType.shouldBeInstanceOf() - assertions(type) -} - internal inline fun MockKVerificationScope.withDeleteEvent( noinline assertions: MockKAssertScope.(T) -> Unit = { } ) = withArg { 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 deleted file mode 100644 index 76c0c24300..0000000000 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchAuthSessionUseCaseTest.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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.cognito.AuthStateMachine -import com.amplifyframework.auth.cognito.helpers.SessionHelper -import com.amplifyframework.auth.cognito.mockSignedInData -import com.amplifyframework.auth.cognito.testUtil.authState -import com.amplifyframework.auth.cognito.testUtil.withAuthZEvent -import com.amplifyframework.auth.exceptions.InvalidStateException -import com.amplifyframework.auth.options.AuthFetchSessionOptions -import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter -import com.amplifyframework.auth.result.AuthSessionResult -import com.amplifyframework.statemachine.codegen.data.AWSCredentials -import com.amplifyframework.statemachine.codegen.data.AmplifyCredential -import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens -import com.amplifyframework.statemachine.codegen.data.FederatedToken -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.AuthorizationState -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.booleans.shouldBeFalse -import io.kotest.matchers.booleans.shouldBeTrue -import io.kotest.matchers.shouldBe -import io.mockk.coEvery -import io.mockk.every -import io.mockk.justRun -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.unmockkObject -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 -import org.junit.After -import org.junit.Before -import org.junit.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class FetchAuthSessionUseCaseTest { - private val credential = AmplifyCredential.UserPool(signedInData = mockSignedInData()) - - private val stateFlow = MutableStateFlow( - authState( - authZState = AuthorizationState.SessionEstablished(amplifyCredential = credential) - ) - ) - - 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() { - mockkObject(SessionHelper) - coEvery { SessionHelper.isValidSession(any()) } returns true - coEvery { SessionHelper.isValidTokens(any()) } returns true - } - - @After - fun teardown() { - unmockkObject(SessionHelper) - } - - @Test - fun `fetches unauthed session`() = runTest { - stateFlow.value = authState(authZState = AuthorizationState.Configured()) - - backgroundScope.launch { useCase.execute() } - runCurrent() - - verify { - stateMachine.send( - withArg { it is AuthorizationEvent && it.eventType !is AuthorizationEvent.EventType.FetchUnAuthSession } - ) - } - } - - @Test - fun `returns valid session`() = runTest { - val result = useCase.execute() - result.userPoolTokensResult.type shouldBe AuthSessionResult.Type.SUCCESS - } - - @Test - fun `refreshes expired session`() = runTest { - every { SessionHelper.isValidTokens(any()) } returns false - - backgroundScope.launch { useCase.execute() } - runCurrent() - - verify { - stateMachine.send( - withAuthZEvent { event -> - event.amplifyCredential shouldBe credential - } - ) - } - } - - @Test - fun `sends federation event to refresh federated session`() = runTest { - val credential = AmplifyCredential.IdentityPoolFederated( - federatedToken = FederatedToken(token = "token", providerName = "provider"), - identityId = "federatedIdentity", - credentials = mockk() - ) - - stateFlow.value = authState(authZState = AuthorizationState.SessionEstablished(credential)) - every { SessionHelper.isValidSession(any()) } returns false - - backgroundScope.launch { useCase.execute() } - runCurrent() - - verify { - stateMachine.send( - withAuthZEvent { event -> - event.token shouldBe credential.federatedToken - event.identityId shouldBe credential.identityId - event.existingCredential shouldBe credential - } - ) - } - } - - @Test - fun `refreshes session if forceRefresh is true`() = runTest { - val options = AuthFetchSessionOptions.builder().forceRefresh(true).build() - - backgroundScope.launch { useCase.execute(options) } - runCurrent() - - verify { - stateMachine.send( - withAuthZEvent { event -> - event.amplifyCredential shouldBe credential - } - ) - } - } - - @Test - fun `refreshes federated session if forceRefresh is true`() = runTest { - val credential = AmplifyCredential.IdentityPoolFederated( - federatedToken = FederatedToken(token = "token", providerName = "provider"), - identityId = "federatedIdentity", - credentials = mockk() - ) - - stateFlow.value = authState(authZState = AuthorizationState.SessionEstablished(credential)) - - val options = AuthFetchSessionOptions.builder().forceRefresh(true).build() - backgroundScope.launch { useCase.execute(options) } - runCurrent() - - verify { - stateMachine.send( - withAuthZEvent { event -> - event.token shouldBe credential.federatedToken - event.identityId shouldBe credential.identityId - event.existingCredential shouldBe credential - } - ) - } - } - - @Test - fun `refreshes session from a session error state`() = runTest { - val sessionError = SessionError(Exception("Failure"), credential) - stateFlow.value = authState(authZState = AuthorizationState.Error(sessionError)) - - backgroundScope.launch { useCase.execute() } - runCurrent() - - verify { - stateMachine.send( - withAuthZEvent { event -> - event.amplifyCredential shouldBe credential - } - ) - } - } - - @Test - fun `fails if authorization is in a non-session error state`() = runTest { - val error = Exception("Some non-session error") - stateFlow.value = authState(authZState = AuthorizationState.Error(error)) - - shouldThrow { - useCase.execute() - } - } - - @Test - fun `fails if authorization is in an unexpected state`() = runTest { - stateFlow.value = authState(authZState = AuthorizationState.NotConfigured()) - - shouldThrow { - useCase.execute() - } - } - - @Test - fun `returns session when session is established`() = runTest { - val options = AuthFetchSessionOptions.builder().forceRefresh(true).build() - val result = backgroundScope.async { useCase.execute(options) } - runCurrent() - - val userPoolTokens = CognitoUserPoolTokens( - idToken = "idToken", - accessToken = "access", - refreshToken = "refresh", - expiration = 100L - ) - val newCredential = AmplifyCredential.UserPool( - signedInData = mockSignedInData(cognitoUserPoolTokens = userPoolTokens) - ) - stateFlow.value = authState(authZState = AuthorizationState.SessionEstablished(newCredential)) - - val session = result.await() - session.isSignedIn.shouldBeTrue() - session.userPoolTokensResult.value?.accessToken shouldBe userPoolTokens.accessToken - } - - @Test - fun `returns session when error state is reached`() = runTest { - val options = AuthFetchSessionOptions.builder().forceRefresh(true).build() - val result = backgroundScope.async { useCase.execute(options) } - runCurrent() - - val exception = Exception("Something failed") - stateFlow.value = authState(authZState = AuthorizationState.Error(exception)) - - val session = result.await() - session.isSignedIn.shouldBeFalse() - } -} diff --git a/configuration/instrumentation-tests.gradle b/configuration/instrumentation-tests.gradle index c1e0d404aa..6c4c58bac1 100644 --- a/configuration/instrumentation-tests.gradle +++ b/configuration/instrumentation-tests.gradle @@ -9,6 +9,7 @@ def module_backends = [ 'aws-storage-s3' : 'NONE', 'aws-analytics-pinpoint': 'NONE', 'aws-predictions' : 'NONE', + 'core' : 'NONE', 'aws-auth-cognito' : 'AuthIntegrationTests' ] diff --git a/core/src/test/java/com/amplifyframework/core/util/UserAgentConfigurationTest.java b/core/src/androidTest/java/com/amplifyframework/core/util/UserAgentConfigurationTest.java similarity index 100% rename from core/src/test/java/com/amplifyframework/core/util/UserAgentConfigurationTest.java rename to core/src/androidTest/java/com/amplifyframework/core/util/UserAgentConfigurationTest.java