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 838cb2aa18..929179d318 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 @@ -68,7 +68,7 @@ import org.json.JSONObject /** * A Cognito implementation of the Auth Plugin. */ -class AWSCognitoAuthPlugin : AuthPlugin() { +class AWSCognitoAuthPlugin : AuthPlugin() { companion object { const val AWS_COGNITO_AUTH_LOG_NAMESPACE = "amplify:aws-cognito-auth:%s" private const val AWS_COGNITO_AUTH_PLUGIN_KEY = "awsCognitoAuthPlugin" @@ -111,7 +111,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { val authEnvironment = AuthEnvironment( context, configuration, - AWSCognitoAuthServiceBehavior.fromConfiguration(configuration), + AWSCognitoAuthService.fromConfiguration(configuration), credentialStoreClient, configuration.userPool?.let { UserContextDataProvider(context, it.poolId!!, it.appClient!!) }, HostedUIClient.create(context, configuration.oauth, logger), diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehavior.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt similarity index 92% rename from aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehavior.kt rename to aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt index ccdd1c4b0c..38d9c9c3d1 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehavior.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt @@ -20,12 +20,12 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderCl import aws.smithy.kotlin.runtime.http.endpoints.Endpoint import com.amplifyframework.statemachine.codegen.data.AuthConfiguration -interface AWSCognitoAuthServiceBehavior { +interface AWSCognitoAuthService { var cognitoIdentityProviderClient: CognitoIdentityProviderClient? var cognitoIdentityClient: CognitoIdentityClient? companion object { - internal fun fromConfiguration(configuration: AuthConfiguration): AWSCognitoAuthServiceBehavior { + internal fun fromConfiguration(configuration: AuthConfiguration): AWSCognitoAuthService { val cognitoIdentityProviderClient = configuration.userPool?.let { it -> CognitoIdentityProviderClient { @@ -40,7 +40,7 @@ interface AWSCognitoAuthServiceBehavior { CognitoIdentityClient { this.region = it.region } } - return object : AWSCognitoAuthServiceBehavior { + return object : AWSCognitoAuthService { override var cognitoIdentityProviderClient = cognitoIdentityProviderClient override var cognitoIdentityClient = cognitoIdentityClient } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthEnvironment.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthEnvironment.kt index eb420aacf6..027e9c8099 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthEnvironment.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthEnvironment.kt @@ -37,7 +37,7 @@ import java.util.UUID internal class AuthEnvironment internal constructor( val context: Context, val configuration: AuthConfiguration, - val cognitoAuthService: AWSCognitoAuthServiceBehavior, + val cognitoAuthService: AWSCognitoAuthService, val credentialStoreClient: StoreClientBehavior, private val userContextDataProvider: UserContextDataProvider? = null, val hostedUIClient: HostedUIClient?, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/GlobalSignOutException.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/GlobalSignOutException.kt index bb0097bb9d..5ee67063db 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/GlobalSignOutException.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/GlobalSignOutException.kt @@ -23,6 +23,6 @@ import com.amplifyframework.auth.exceptions.ServiceException open class GlobalSignOutException(cause: Throwable) : ServiceException( "Failed to sign out globally", "See attached exception for more details. GlobalSignOut can be retried using the " + - "CognitoIdentityProviderClient is accessible from the escape hatch.", + "CognitoIdentityProviderClient accessible from the escape hatch.", cause ) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginFeatureTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginFeatureTest.kt index 87f95f6d18..8b1a1f8a80 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginFeatureTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginFeatureTest.kt @@ -152,7 +152,7 @@ class AWSCognitoAuthPluginFeatureTest(private val testCase: FeatureTestCase) { .getJSONObject("awsCognitoAuthPlugin") val authConfiguration = AuthConfiguration.fromJson(configJSONObject) - val authService = mockk { + val authService = mockk { every { cognitoIdentityProviderClient } returns mockCognitoIPClient every { cognitoIdentityClient } returns mockCognitoIdClient } 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 f8f121ecea..4a7738e2be 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 @@ -516,7 +516,7 @@ class AWSCognitoAuthPluginTest { @Test fun verifyEscapeHatch() { - val expectedEscapeHatch = mockk() + val expectedEscapeHatch = mockk() every { realPlugin.escapeHatch() } returns expectedEscapeHatch assertEquals(expectedEscapeHatch, authPlugin.escapeHatch) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehaviorTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceTest.kt similarity index 94% rename from aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehaviorTest.kt rename to aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceTest.kt index 03e33ce907..9162032888 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceBehaviorTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthServiceTest.kt @@ -25,7 +25,7 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) -class AWSCognitoAuthServiceBehaviorTest { +class AWSCognitoAuthServiceTest { // Robolectric needed due to internals of AWSCognitoAuthServiceBehavior initialization @Test @@ -45,7 +45,7 @@ class AWSCognitoAuthServiceBehaviorTest { authFlowType = AuthFlowType.USER_SRP_AUTH ) - val testObject = AWSCognitoAuthServiceBehavior.fromConfiguration(config) + val testObject = AWSCognitoAuthService.fromConfiguration(config) assertEquals(expectedIdentityPoolConfigRegion, testObject.cognitoIdentityClient!!.config.region) assertEquals(expectedUserPoolRegion, testObject.cognitoIdentityProviderClient!!.config.region) 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 1c9e7e2ea2..8b3d201a7e 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 @@ -129,7 +129,7 @@ class RealAWSCognitoAuthPluginTest { ) private val mockCognitoIPClient = mockk() - private var authService = mockk { + private var authService = mockk { every { cognitoIdentityProviderClient } returns mockCognitoIPClient } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt index 32bd799900..51c5e1004f 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt @@ -81,7 +81,7 @@ open class StateTransitionTestBase { internal lateinit var configuration: AuthConfiguration @Mock - internal lateinit var cognitoAuthService: AWSCognitoAuthServiceBehavior + internal lateinit var cognitoAuthService: AWSCognitoAuthService @Mock internal lateinit var mockAuthActions: AuthActions diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/JsonGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/JsonGenerator.kt index e4a454071e..b91a8981bf 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/JsonGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/JsonGenerator.kt @@ -19,6 +19,7 @@ import com.amplifyframework.auth.cognito.featuretest.FeatureTestCase import com.amplifyframework.auth.cognito.featuretest.generators.authstategenerators.AuthStateJsonGenerator import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.ConfirmSignInTestCaseGenerator import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.DeleteUserTestCaseGenerator +import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.FetchAuthSessionTestCaseGenerator import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.ResetPasswordTestCaseGenerator import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.SignInTestCaseGenerator import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.SignOutTestCaseGenerator @@ -40,7 +41,8 @@ object JsonGenerator { SignInTestCaseGenerator, SignOutTestCaseGenerator, ConfirmSignInTestCaseGenerator, - DeleteUserTestCaseGenerator + DeleteUserTestCaseGenerator, + FetchAuthSessionTestCaseGenerator, ) fun generate() { diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/SerializationTools.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/SerializationTools.kt index bbb675a420..4484cd8695 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/SerializationTools.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/SerializationTools.kt @@ -17,18 +17,23 @@ package com.amplifyframework.auth.cognito.featuretest.generators import aws.sdk.kotlin.services.cognitoidentity.model.CognitoIdentityException import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException +import aws.smithy.kotlin.runtime.time.Instant import com.amplifyframework.auth.AuthException import com.amplifyframework.auth.cognito.featuretest.FeatureTestCase import com.amplifyframework.auth.cognito.featuretest.serializers.CognitoIdentityExceptionSerializer import com.amplifyframework.auth.cognito.featuretest.serializers.CognitoIdentityProviderExceptionSerializer import com.amplifyframework.auth.cognito.featuretest.serializers.deserializeToAuthState import com.amplifyframework.auth.cognito.featuretest.serializers.serialize +import com.amplifyframework.auth.result.AuthSessionResult import com.amplifyframework.statemachine.codegen.states.AuthState import com.google.gson.Gson import java.io.BufferedWriter import java.io.File import java.io.FileOutputStream import java.io.FileWriter +import kotlin.reflect.KClass +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberProperties import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray @@ -166,11 +171,13 @@ fun Any?.toJsonElement(): JsonElement { is Boolean -> JsonPrimitive(this) is Number -> JsonPrimitive(this) is String -> JsonPrimitive(this) + is Instant -> JsonPrimitive(this.epochSeconds) is AuthException -> toJsonElement() is CognitoIdentityProviderException -> Json.encodeToJsonElement( CognitoIdentityProviderExceptionSerializer, this ) + is AuthSessionResult<*> -> toJsonElement() is CognitoIdentityException -> Json.encodeToJsonElement(CognitoIdentityExceptionSerializer, this) else -> gsonBasedSerializer(this) } @@ -187,6 +194,10 @@ fun AuthException.toJsonElement(): JsonElement { return responseMap.toJsonElement() } +fun AuthSessionResult<*>.toJsonElement(): JsonElement { + return (if (type == AuthSessionResult.Type.SUCCESS) value else error).toJsonElement() +} + /** * Uses Gson to convert objects which cannot be serialized, * tries to convert to map of params to vals @@ -196,6 +207,19 @@ fun gsonBasedSerializer(value: Any): JsonElement { return try { gson.fromJson(gson.toJson(value).toString(), Map::class.java).toJsonElement() } catch (ex: Exception) { - JsonPrimitive(value.toString()) + reflectionBasedSerializer(value) } } + +/** + * Final fallback to serialize by using reflection, traversing the object members and converting it to Map. + * Note that this method is similar to what Gson does. But Gson fails when there is name collision in parent and child + * classes. + */ +fun reflectionBasedSerializer(value: Any): JsonElement { + return (value::class as KClass<*>).declaredMemberProperties.filter { + it.visibility == KVisibility.PUBLIC + }.associate { + it.name to it.getter.call(value) + }.toMap().toJsonElement() +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/authstategenerators/AuthStateJsonGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/authstategenerators/AuthStateJsonGenerator.kt index 55ce81a5d3..538d3bddb5 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/authstategenerators/AuthStateJsonGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/authstategenerators/AuthStateJsonGenerator.kt @@ -40,10 +40,16 @@ object AuthStateJsonGenerator : SerializableProvider { const val dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1l" + "IiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU" + const val accessKeyId = "someAccessKey" + const val secretAccessKey = "someSecretKey" + const val identityId = "someIdentityId" + const val expiration: Long = 2342134 + const val userId = "userId" + private const val username = "username" private val signedInData = SignedInData( - userId = "userId", + userId = userId, username = username, signedInDate = Date.from(Instant.ofEpochSecond(0)), signInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH), @@ -60,12 +66,12 @@ object AuthStateJsonGenerator : SerializableProvider { AuthorizationState.SessionEstablished( AmplifyCredential.UserAndIdentityPool( signedInData, - identityId = "someIdentityId", + identityId = identityId, AWSCredentials( - accessKeyId = "someAccessKey", - secretAccessKey = "someSecretKey", + accessKeyId = accessKeyId, + secretAccessKey = secretAccessKey, sessionToken = dummyToken, - expiration = 2342134 + expiration = expiration ) ) ) 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 new file mode 100644 index 0000000000..02c413dac3 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchAuthSessionTestCaseGenerator.kt @@ -0,0 +1,159 @@ +package com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators + +import com.amplifyframework.auth.AWSCredentials +import com.amplifyframework.auth.cognito.AWSCognitoAuthSession +import com.amplifyframework.auth.cognito.AWSCognitoUserPoolTokens +import com.amplifyframework.auth.cognito.featuretest.API +import com.amplifyframework.auth.cognito.featuretest.AuthAPI +import com.amplifyframework.auth.cognito.featuretest.CognitoType +import com.amplifyframework.auth.cognito.featuretest.ExpectationShapes +import com.amplifyframework.auth.cognito.featuretest.FeatureTestCase +import com.amplifyframework.auth.cognito.featuretest.MockResponse +import com.amplifyframework.auth.cognito.featuretest.PreConditions +import com.amplifyframework.auth.cognito.featuretest.ResponseType +import com.amplifyframework.auth.cognito.featuretest.generators.SerializableProvider +import com.amplifyframework.auth.cognito.featuretest.generators.authstategenerators.AuthStateJsonGenerator +import com.amplifyframework.auth.cognito.featuretest.generators.toJsonElement +import com.amplifyframework.auth.exceptions.ConfigurationException +import com.amplifyframework.auth.exceptions.SignedOutException +import com.amplifyframework.auth.result.AuthSessionResult +import kotlinx.serialization.json.JsonObject + +object FetchAuthSessionTestCaseGenerator : SerializableProvider { + + private val mockedInitiateAuthResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "initiateAuth", + ResponseType.Success, + mapOf( + "authenticationResult" to mapOf( + "idToken" to AuthStateJsonGenerator.dummyToken, + "accessToken" to AuthStateJsonGenerator.dummyToken, + "refreshToken" to AuthStateJsonGenerator.dummyToken, + "expiresIn" to 300 + ) + ).toJsonElement() + ) + + private val expectedSuccess = AWSCognitoAuthSession( + isSignedIn = true, + identityIdResult = AWSCognitoAuthSession.getIdentityIdResult("someIdentityId"), + awsCredentialsResult = AuthSessionResult.success( + AWSCredentials.createAWSCredentials( + AuthStateJsonGenerator.accessKeyId, + AuthStateJsonGenerator.secretAccessKey, + AuthStateJsonGenerator.dummyToken, + AuthStateJsonGenerator.expiration + ) + ), + userSubResult = AuthSessionResult.success(AuthStateJsonGenerator.userId), + userPoolTokensResult = AuthSessionResult.success( + AWSCognitoUserPoolTokens( + accessToken = AuthStateJsonGenerator.dummyToken, + idToken = AuthStateJsonGenerator.dummyToken, + refreshToken = AuthStateJsonGenerator.dummyToken + ) + ) + ).toJsonElement() + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.fetchAuthSession, + ResponseType.Success, + expectedSuccess, + ) + + private val baseCase = FeatureTestCase( + description = "AuthSession object is successfully returned for UserAndIdentity Pool", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf() + ), + api = API( + name = AuthAPI.fetchAuthSession, + params = JsonObject(emptyMap()), + JsonObject(emptyMap()) + ), + validations = listOf(apiReturnValidation) + ) + + private val refreshSuccessCase: FeatureTestCase = baseCase.copy( + description = "AuthSession object is successfully returned after refresh", + preConditions = baseCase.preConditions.copy( + mockedResponses = listOf(mockedInitiateAuthResponse) + ), + api = API( + name = AuthAPI.fetchAuthSession, + params = JsonObject(emptyMap()), + options = mapOf("forceRefresh" to true).toJsonElement(), + ), + validations = baseCase.validations + ) + + private val identityPoolCase: FeatureTestCase = baseCase.copy( + description = "AuthSession object is successfully returned for Identity Pool", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_IdentityPoolConfigured.json" + ), + api = baseCase.api, + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.fetchAuthSession, + ResponseType.Success, + AWSCognitoAuthSession( + isSignedIn = false, + identityIdResult = AWSCognitoAuthSession.getIdentityIdResult("someIdentityId"), + awsCredentialsResult = AuthSessionResult.success( + AWSCredentials.createAWSCredentials( + AuthStateJsonGenerator.accessKeyId, + AuthStateJsonGenerator.secretAccessKey, + AuthStateJsonGenerator.dummyToken, + AuthStateJsonGenerator.expiration + ) + ), + userSubResult = AuthSessionResult.failure(SignedOutException()), + userPoolTokensResult = AuthSessionResult.failure(SignedOutException()) + ).toJsonElement() + ) + ) + ) + + private val userPoolCase: FeatureTestCase = baseCase.copy( + description = "AuthSession object is successfully returned for User Pool", + preConditions = baseCase.preConditions.copy( + state = "SignedIn_UserPoolSessionEstablished.json" + ), + api = baseCase.api, + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.fetchAuthSession, + ResponseType.Success, + AWSCognitoAuthSession( + isSignedIn = true, + identityIdResult = AuthSessionResult.failure( + ConfigurationException( + "Could not retrieve Identity ID", + "Cognito Identity not configured. Please check amplifyconfiguration.json file." + ) + ), + awsCredentialsResult = AuthSessionResult.failure( + ConfigurationException( + "Could not fetch AWS Cognito credentials", + "Cognito Identity not configured. Please check amplifyconfiguration.json file." + ) + ), + userSubResult = AuthSessionResult.success(AuthStateJsonGenerator.userId), + userPoolTokensResult = AuthSessionResult.success( + AWSCognitoUserPoolTokens( + accessToken = AuthStateJsonGenerator.dummyToken, + idToken = AuthStateJsonGenerator.dummyToken, + refreshToken = AuthStateJsonGenerator.dummyToken + ) + ) + ).toJsonElement() + ) + ) + ) + + override val serializables: List = listOf(baseCase, refreshSuccessCase, identityPoolCase, userPoolCase) +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/GetCurrentUserTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/GetCurrentUserTestCaseGenerator.kt new file mode 100644 index 0000000000..7eebb704b1 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/GetCurrentUserTestCaseGenerator.kt @@ -0,0 +1,62 @@ +package com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators + +import aws.sdk.kotlin.services.cognitoidentityprovider.model.NotAuthorizedException +import com.amplifyframework.auth.AuthUser +import com.amplifyframework.auth.cognito.featuretest.API +import com.amplifyframework.auth.cognito.featuretest.AuthAPI +import com.amplifyframework.auth.cognito.featuretest.ExpectationShapes +import com.amplifyframework.auth.cognito.featuretest.FeatureTestCase +import com.amplifyframework.auth.cognito.featuretest.PreConditions +import com.amplifyframework.auth.cognito.featuretest.ResponseType +import com.amplifyframework.auth.cognito.featuretest.generators.SerializableProvider +import com.amplifyframework.auth.cognito.featuretest.generators.toJsonElement +import kotlinx.serialization.json.JsonObject + +object GetCurrentUserTestCaseGenerator : SerializableProvider { + + private val expectedSuccess = AuthUser("userId", "username").toJsonElement() + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.getCurrentUser, + ResponseType.Success, + expectedSuccess, + ) + + private val baseCase = FeatureTestCase( + description = "User object is successfully returned", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf() + ), + api = API( + name = AuthAPI.getCurrentUser, + params = JsonObject(emptyMap()), + JsonObject(emptyMap()) + ), + validations = listOf(apiReturnValidation) + ) + + private val errorCase: FeatureTestCase + get() { + val errorResponse = NotAuthorizedException.invoke {} + return baseCase.copy( + description = "SignedOutException is thrown when user signs out", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_Configured.json" + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.getCurrentUser, + ResponseType.Failure, + com.amplifyframework.auth.exceptions.SignedOutException().toJsonElement(), + ) + ) + ) + } + + override val serializables: List = listOf( + baseCase, + errorCase + ) +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/SignOutTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/SignOutTestCaseGenerator.kt index b20a5e7409..08d7a305e2 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/SignOutTestCaseGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/SignOutTestCaseGenerator.kt @@ -14,6 +14,7 @@ */ package com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators +import aws.sdk.kotlin.services.cognitoidentityprovider.model.NotAuthorizedException import com.amplifyframework.auth.cognito.featuretest.API import com.amplifyframework.auth.cognito.featuretest.AuthAPI import com.amplifyframework.auth.cognito.featuretest.CognitoType @@ -23,8 +24,13 @@ import com.amplifyframework.auth.cognito.featuretest.MockResponse import com.amplifyframework.auth.cognito.featuretest.PreConditions import com.amplifyframework.auth.cognito.featuretest.ResponseType import com.amplifyframework.auth.cognito.featuretest.generators.SerializableProvider +import com.amplifyframework.auth.cognito.featuretest.generators.authstategenerators.AuthStateJsonGenerator import com.amplifyframework.auth.cognito.featuretest.generators.toJsonElement import com.amplifyframework.auth.cognito.result.AWSCognitoAuthSignOutResult +import com.amplifyframework.auth.cognito.result.GlobalSignOutError +import com.amplifyframework.auth.cognito.result.RevokeTokenError +import com.amplifyframework.statemachine.codegen.data.GlobalSignOutErrorData +import com.amplifyframework.statemachine.codegen.data.RevokeTokenErrorData import kotlinx.serialization.json.JsonObject object SignOutTestCaseGenerator : SerializableProvider { @@ -36,19 +42,35 @@ object SignOutTestCaseGenerator : SerializableProvider { JsonObject(emptyMap()) ) - private val mockedRevokeTokenSignOutSuccessResponse = MockResponse( + private val mockedGlobalSignOutFailureResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "globalSignOut", + ResponseType.Failure, + NotAuthorizedException.invoke {}.toJsonElement() + ) + + private val mockedRevokeTokenSuccessResponse = MockResponse( CognitoType.CognitoIdentityProvider, "revokeToken", ResponseType.Success, JsonObject(emptyMap()) ) - private val signedOutSuccessCase = FeatureTestCase( - description = "Test that signOut while already signed out returns complete with success", + private val mockedRevokeTokenFailureResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "revokeToken", + ResponseType.Failure, + NotAuthorizedException.invoke {}.toJsonElement() + ) + + private val successCase = FeatureTestCase( + description = "Test that signOut while signed in returns complete with success", preConditions = PreConditions( "authconfiguration.json", - "SignedOut_Configured.json", - mockedResponses = emptyList() + "SignedIn_SessionEstablished.json", + mockedResponses = listOf( + mockedRevokeTokenSuccessResponse + ) ), api = API( AuthAPI.signOut, @@ -66,43 +88,113 @@ object SignOutTestCaseGenerator : SerializableProvider { ) ) - private val signedInSuccessCase = FeatureTestCase( - description = "Test that signOut while signed in returns complete with success", - preConditions = PreConditions( - "authconfiguration.json", - "SignedIn_SessionEstablished.json", + private val signedOutSuccessCase = successCase.copy( + description = "Test that signOut while already signed out returns complete with success", + preConditions = successCase.preConditions.copy( + state = "SignedOut_Configured.json", + mockedResponses = emptyList() + ) + ) + + private val globalSuccessCase = successCase.copy( + description = "Test that global signOut while signed in returns complete with success", + preConditions = successCase.preConditions.copy( mockedResponses = listOf( - mockedRevokeTokenSignOutSuccessResponse + mockedGlobalSignOutSuccessResponse, + mockedRevokeTokenSuccessResponse ) ), - api = API( - AuthAPI.signOut, - params = emptyMap().toJsonElement(), - options = mapOf( - "globalSignOut" to false - ).toJsonElement() + api = successCase.api.copy( + options = mapOf("globalSignOut" to true).toJsonElement() + ) + ) + + private val revokeTokenErrorCase = successCase.copy( + description = "Test that signOut returns partial success with revoke token error", + preConditions = successCase.preConditions.copy( + mockedResponses = listOf( + mockedRevokeTokenFailureResponse + ) ), validations = listOf( ExpectationShapes.Amplify( apiName = AuthAPI.signOut, responseType = ResponseType.Complete, - response = AWSCognitoAuthSignOutResult.CompleteSignOut.toJsonElement() + response = AWSCognitoAuthSignOutResult.PartialSignOut( + revokeTokenError = RevokeTokenError( + RevokeTokenErrorData( + refreshToken = AuthStateJsonGenerator.dummyToken, + error = NotAuthorizedException.invoke { } + ) + ) + ).toJsonElement() ) ) ) - private val globalSignedInSuccessCase = signedInSuccessCase.copy( - description = "Test that global signOut while signed in returns complete with success", - preConditions = signedInSuccessCase.preConditions.copy( + private val revokeTokenWithGlobalSignOutErrorCase = successCase.copy( + description = "Test that globalSignOut returns partial success with revoke token error", + preConditions = successCase.preConditions.copy( mockedResponses = listOf( mockedGlobalSignOutSuccessResponse, - mockedRevokeTokenSignOutSuccessResponse + mockedRevokeTokenFailureResponse + ) + ), + validations = listOf( + ExpectationShapes.Amplify( + apiName = AuthAPI.signOut, + responseType = ResponseType.Complete, + response = AWSCognitoAuthSignOutResult.PartialSignOut( + revokeTokenError = RevokeTokenError( + RevokeTokenErrorData( + refreshToken = AuthStateJsonGenerator.dummyToken, + error = NotAuthorizedException.invoke { } + ) + ) + ).toJsonElement() + ) + ), + api = successCase.api.copy( + options = mapOf("globalSignOut" to true).toJsonElement() + ) + ) + + private val globalErrorCase = successCase.copy( + description = "Test that global signOut error returns partial success with global sign out error", + preConditions = successCase.preConditions.copy( + mockedResponses = listOf(mockedGlobalSignOutFailureResponse) + ), + validations = listOf( + ExpectationShapes.Amplify( + apiName = AuthAPI.signOut, + responseType = ResponseType.Complete, + response = AWSCognitoAuthSignOutResult.PartialSignOut( + globalSignOutError = GlobalSignOutError( + GlobalSignOutErrorData( + accessToken = AuthStateJsonGenerator.dummyToken, + error = NotAuthorizedException.invoke { } + ) + ), + revokeTokenError = RevokeTokenError( + RevokeTokenErrorData( + refreshToken = AuthStateJsonGenerator.dummyToken, + error = Exception("RevokeToken not attempted because GlobalSignOut failed.") + ) + ) + ).toJsonElement() ) ), - api = signedInSuccessCase.api.copy( + api = successCase.api.copy( options = mapOf("globalSignOut" to true).toJsonElement() ) ) - override val serializables: List = listOf(signedOutSuccessCase, signedInSuccessCase, globalSignedInSuccessCase) + override val serializables: List = listOf( + successCase, + signedOutSuccessCase, + revokeTokenErrorCase, + revokeTokenWithGlobalSignOutErrorCase, + globalSuccessCase, + globalErrorCase + ) } diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt new file mode 100644 index 0000000000..a7a625de8b --- /dev/null +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt @@ -0,0 +1,127 @@ +/* + * 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 featureTest.utilities + +import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.AuthSession +import com.amplifyframework.auth.AuthUser +import com.amplifyframework.auth.cognito.featuretest.AuthAPI +import com.amplifyframework.auth.cognito.featuretest.ExpectationShapes +import com.amplifyframework.auth.cognito.featuretest.ResponseType +import com.amplifyframework.auth.result.AuthResetPasswordResult +import com.amplifyframework.auth.result.AuthSignInResult +import com.amplifyframework.auth.result.AuthSignOutResult +import com.amplifyframework.auth.result.AuthSignUpResult +import com.amplifyframework.core.Action +import com.amplifyframework.core.Consumer +import io.mockk.CapturingSlot +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import java.util.concurrent.CountDownLatch + +/** + * Factory with association of results captor to top level APIs + */ +class APICaptorFactory( + private val authApi: ExpectationShapes.Amplify, + private val latch: CountDownLatch, // ToDo: Remove this param +) { + companion object { + val onSuccess = mapOf( + AuthAPI.resetPassword to mockk>(), + AuthAPI.signUp to mockk>(), + AuthAPI.signIn to mockk>(), + AuthAPI.deleteUser to mockk(), + AuthAPI.fetchAuthSession to mockk(), + AuthAPI.getCurrentUser to mockk() + ) + val onError = mockk>() + val onComplete = mapOf( + AuthAPI.signOut to mockk>() + ) + val successCaptors: MutableMap> = mutableMapOf() + val completeCaptors: MutableMap> = mutableMapOf() + val errorCaptor = slot() + val actionCaptor = slot>().apply { + captured = emptyMap() + isCaptured = true + } + } + + init { + successCaptors.clear() + completeCaptors.clear() + if (authApi.responseType == ResponseType.Success) setupOnSuccess() + if (authApi.responseType == ResponseType.Complete) setupOnComplete() + else setupOnError() + } + + private fun setupOnSuccess() { + when (val apiName = authApi.apiName) { + AuthAPI.resetPassword -> { + val resultCaptor = slot() + val consumer = onSuccess[apiName] as Consumer + every { consumer.accept(capture(resultCaptor)) } answers { latch.countDown() } + successCaptors[apiName] = resultCaptor + } + AuthAPI.signUp -> { + val resultCaptor = slot() + val consumer = onSuccess[apiName] as Consumer + every { consumer.accept(capture(resultCaptor)) } answers { latch.countDown() } + successCaptors[apiName] = resultCaptor + } + AuthAPI.signIn -> { + val resultCaptor = slot() + val consumer = onSuccess[apiName] as Consumer + every { consumer.accept(capture(resultCaptor)) } answers { latch.countDown() } + successCaptors[apiName] = resultCaptor + } + AuthAPI.deleteUser -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + AuthAPI.fetchAuthSession -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + AuthAPI.getCurrentUser -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + else -> throw Error("onSuccess for $authApi is not defined!") + } + } + + private fun setupOnComplete() { + when (val apiName = authApi.apiName) { + AuthAPI.signOut -> { + val resultCaptor = slot() + val consumer = onComplete[apiName] as Consumer + every { consumer.accept(capture(resultCaptor)) } answers { latch.countDown() } + completeCaptors[apiName] = resultCaptor + } + else -> throw Error("onComplete for $authApi is not defined!") + } + } + + private fun setupOnError() { + every { onError.accept(capture(errorCaptor)) } answers { latch.countDown() } + } +} diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt index adf2f7444e..3a80d136f4 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt @@ -18,15 +18,13 @@ package featureTest.utilities import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.cognito.featuretest.AuthAPI -import com.amplifyframework.auth.cognito.featuretest.AuthAPI.confirmSignIn -import com.amplifyframework.auth.cognito.featuretest.AuthAPI.deleteUser import com.amplifyframework.auth.cognito.featuretest.AuthAPI.resetPassword import com.amplifyframework.auth.cognito.featuretest.AuthAPI.signIn -import com.amplifyframework.auth.cognito.featuretest.AuthAPI.signOut import com.amplifyframework.auth.cognito.featuretest.AuthAPI.signUp import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions import com.amplifyframework.auth.options.AuthConfirmSignInOptions import com.amplifyframework.auth.options.AuthConfirmSignUpOptions +import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.options.AuthResendSignUpCodeOptions import com.amplifyframework.auth.options.AuthResendUserAttributeConfirmationCodeOptions import com.amplifyframework.auth.options.AuthResetPasswordOptions @@ -52,11 +50,11 @@ object AuthOptionsFactory { AuthAPI.confirmSignUp -> AuthConfirmSignUpOptions.defaults() AuthAPI.confirmUserAttribute -> null AuthAPI.deleteUser -> null - AuthAPI.fetchAuthSession -> null + AuthAPI.fetchAuthSession -> getFetchAuthSessionOptions(optionsData) AuthAPI.fetchDevices -> null AuthAPI.fetchUserAttributes -> TODO() AuthAPI.forgetDevice -> TODO() - AuthAPI.getCurrentUser -> TODO() + AuthAPI.getCurrentUser -> null AuthAPI.handleWebUISignInResponse -> TODO() AuthAPI.rememberDevice -> TODO() AuthAPI.resendSignUpCode -> AuthResendSignUpCodeOptions.defaults() @@ -89,4 +87,11 @@ object AuthOptionsFactory { .globalSignOut(globalSignOutData) .build() } + + private fun getFetchAuthSessionOptions(optionsData: JsonObject): AuthFetchSessionOptions { + val refresh = (optionsData["forceRefresh"] as? JsonPrimitive)?.booleanOrNull ?: false + return AuthFetchSessionOptions.builder() + .forceRefresh(refresh) + .build() + } } diff --git a/aws-auth-cognito/src/test/resources/feature-test/states/SignedIn_UserPoolSessionEstablished.json b/aws-auth-cognito/src/test/resources/feature-test/states/SignedIn_UserPoolSessionEstablished.json new file mode 100644 index 0000000000..93bbd31c57 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/states/SignedIn_UserPoolSessionEstablished.json @@ -0,0 +1,42 @@ +{ + "type": "AuthState.Configured", + "AuthenticationState": { + "type": "AuthenticationState.SignedIn", + "signedInData": { + "userId": "userId", + "username": "username", + "signedInDate": 0, + "signInMethod": { + "type": "SignInMethod.ApiBased", + "authType": "USER_SRP_AUTH" + }, + "cognitoUserPoolTokens": { + "idToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "expiration": 300 + } + } + }, + "AuthorizationState": { + "type": "AuthorizationState.SessionEstablished", + "amplifyCredential": { + "type": "userPool", + "signedInData": { + "userId": "userId", + "username": "username", + "signedInDate": 0, + "signInMethod": { + "type": "SignInMethod.ApiBased", + "authType": "USER_SRP_AUTH" + }, + "cognitoUserPoolTokens": { + "idToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "expiration": 300 + } + } + } + } +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/states/SignedOut_IdentityPoolConfigured.json b/aws-auth-cognito/src/test/resources/feature-test/states/SignedOut_IdentityPoolConfigured.json new file mode 100644 index 0000000000..028d2d4bd1 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/states/SignedOut_IdentityPoolConfigured.json @@ -0,0 +1,34 @@ +{ + "type": "AuthState.Configured", + "AuthenticationState": { + "type": "AuthenticationState.SignedIn", + "signedInData": { + "userId": "userId", + "username": "username", + "signedInDate": 0, + "signInMethod": { + "type": "SignInMethod.ApiBased", + "authType": "USER_SRP_AUTH" + }, + "cognitoUserPoolTokens": { + "idToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "expiration": 300 + } + } + }, + "AuthorizationState": { + "type": "AuthorizationState.SessionEstablished", + "amplifyCredential": { + "type": "identityPool", + "identityId": "someIdentityId", + "credentials": { + "accessKeyId": "someAccessKey", + "secretAccessKey": "someSecretKey", + "sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "expiration": 2342134 + } + } + } +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/Test_that_API_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/Test_that_API_is_called_with_given_payload_and_returns_successful_data.json new file mode 100644 index 0000000000..aacf3ab58c --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchAuthSession/Test_that_API_is_called_with_given_payload_and_returns_successful_data.json @@ -0,0 +1,39 @@ +{ + "description": "Test that API is called with given payload and returns successful data", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + ] + }, + "api": { + "name": "fetchAuthSession", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchAuthSession", + "responseType": "success", + "response": { + "awsCredentialsResult": { + "accessKeyId": "someAccessKey", + "expiration": 2342134, + "secretAccessKey": "someSecretKey", + "sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU" + }, + "identityIdResult": "someIdentityId", + "isSignedIn": true, + "userPoolTokensResult": { + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "idToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU" + }, + "userSubResult": "userId" + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_globalSignOut_returns_partial_success_with_revoke_token_error.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_globalSignOut_returns_partial_success_with_revoke_token_error.json new file mode 100644 index 0000000000..ece33c3204 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_globalSignOut_returns_partial_success_with_revoke_token_error.json @@ -0,0 +1,57 @@ +{ + "description": "Test that globalSignOut returns partial success with revoke token error", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "globalSignOut", + "responseType": "success", + "response": { + } + }, + { + "type": "cognitoIdentityProvider", + "apiName": "revokeToken", + "responseType": "failure", + "response": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + } + ] + }, + "api": { + "name": "signOut", + "params": { + }, + "options": { + "globalSignOut": true + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "signOut", + "responseType": "complete", + "response": { + "globalSignOutError": null, + "hostedUIError": null, + "revokeTokenError": { + "exception": { + "errorType": "RevokeTokenException", + "errorMessage": "Failed to revoke token", + "recoverySuggestion": "See attached exception for more details. RevokeToken can be retried using the CognitoIdentityProviderClient accessible from the escape hatch.", + "cause": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + }, + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU" + }, + "signedOutLocally": true + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_global_signOut_error_returns_partial_success_with_global_sign_out_error.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_global_signOut_error_returns_partial_success_with_global_sign_out_error.json new file mode 100644 index 0000000000..80b7fcb772 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_global_signOut_error_returns_partial_success_with_global_sign_out_error.json @@ -0,0 +1,67 @@ +{ + "description": "Test that global signOut error returns partial success with global sign out error", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "globalSignOut", + "responseType": "failure", + "response": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + } + ] + }, + "api": { + "name": "signOut", + "params": { + }, + "options": { + "globalSignOut": true + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "signOut", + "responseType": "complete", + "response": { + "globalSignOutError": { + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "exception": { + "errorType": "GlobalSignOutException", + "errorMessage": "Failed to sign out globally", + "recoverySuggestion": "See attached exception for more details. GlobalSignOut can be retried using the CognitoIdentityProviderClient accessible from the escape hatch.", + "cause": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + } + }, + "hostedUIError": null, + "revokeTokenError": { + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU", + "exception": { + "recoverySuggestion": "See attached exception for more details. RevokeToken can be retried using the CognitoIdentityProviderClient accessible from the escape hatch.", + "detailMessage": "Failed to revoke token", + "cause": { + "detailMessage": "RevokeToken not attempted because GlobalSignOut failed.", + "stackTrace": [ + ], + "suppressedExceptions": [ + ] + }, + "stackTrace": [ + ], + "suppressedExceptions": [ + ] + } + }, + "signedOutLocally": true + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_signOut_returns_partial_success_with_revoke_token_error.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_signOut_returns_partial_success_with_revoke_token_error.json new file mode 100644 index 0000000000..857211df4b --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/signOut/Test_that_signOut_returns_partial_success_with_revoke_token_error.json @@ -0,0 +1,50 @@ +{ + "description": "Test that signOut returns partial success with revoke token error", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "revokeToken", + "responseType": "failure", + "response": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + } + ] + }, + "api": { + "name": "signOut", + "params": { + }, + "options": { + "globalSignOut": false + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "signOut", + "responseType": "complete", + "response": { + "globalSignOutError": null, + "hostedUIError": null, + "revokeTokenError": { + "exception": { + "errorType": "RevokeTokenException", + "errorMessage": "Failed to revoke token", + "recoverySuggestion": "See attached exception for more details. RevokeToken can be retried using the CognitoIdentityProviderClient accessible from the escape hatch.", + "cause": { + "errorType": "NotAuthorizedException", + "errorMessage": null + } + }, + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiZXhwIjoxNTE2MjM5MDIyLCJvcmlnaW5fanRpIjoib3JpZ2luX2p0aSJ9.Xqa-vjJe5wwwsqeRAdHf8kTBn_rYSkDn2lB7xj9Z1xU" + }, + "signedOutLocally": true + } + } + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 26bf10b416..547a37374c 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ buildscript { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath 'org.jlleitschuh.gradle:ktlint-gradle:9.4.1' + classpath "org.gradle:test-retry-gradle-plugin:1.4.1" } } @@ -181,6 +182,16 @@ subprojects { project -> } } } + + apply plugin: 'org.gradle.test-retry' + + tasks.withType(Test).configureEach { + retry { + maxRetries = 9 + maxFailures = 100 + failOnPassedAfterRetry = true + } + } } private void configureAndroidLibrary(Project project) {