diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5922170e88..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,56 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -version: 2.1 - -orbs: - getting-started-smoke-test: - orbs: - android: circleci/android@2.0.0 - aws-cli: circleci/aws-cli@3.1.1 - - commands: - send-metric-on-fail: - description: Send failure datapoint to cloudwatch - steps: - - run: - name: Send failure datapoint to cloudwatch - command: | - payload="{\"jobName\": \"${CIRCLE_JOB}\", \"projectRepoName\": \"${CIRCLE_PROJECT_REPONAME}\"}" - echo $payload - aws lambda invoke --function-name CircleCIWorkflowFailureHandler --payload "$payload" --cli-binary-format raw-in-base64-out response.json - when: on_fail - jobs: - android: - working_directory: ~/android-canaries/canaries/example - executor: - name: android/android-machine - resource-class: xlarge - tag: 2022.03.1 - steps: - - checkout: - path: ~/android-canaries - - aws-cli/setup: - role-session-name: ${CIRCLE_WORKFLOW_JOB_ID} - role-arn: ${AWS_ROLE_ARN} - session-duration: '2000' - - android/create-avd: - avd-name: myavd - install: true - system-image: system-images;android-29;default;x86 - - android/start-emulator: - avd-name: myavd - - android/run-tests - - send-metric-on-fail - - - -workflows: - canaries: - when: - and: - - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - - equal: [ "Canaries", << pipeline.schedule.name >> ] - jobs: - - getting-started-smoke-test/android: - context: - - cloudwatch-monitoring \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e2b0cc6734..52487093eb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1 @@ * @aws-amplify/amplify-android -/aws-api-appsync @aws-amplify/amplify-android @aws-amplify/amplify-data -/aws-api @aws-amplify/amplify-android @aws-amplify/amplify-data -/aws-datastore @aws-amplify/amplify-android @aws-amplify/amplify-data -/aws-core @aws-amplify/amplify-android @aws-amplify/amplify-data -/.github @aws-amplify/amplify-android @aws-amplify/amplify-data -/.circleci @aws-amplify/amplify-android @aws-amplify/amplify-data -/testmodels @aws-amplify/amplify-android @aws-amplify/amplify-data -/testutils @aws-amplify/amplify-android @aws-amplify/amplify-data diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 427635bc84..5f833511a6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,11 +7,13 @@ *How did you test these changes?* (Please add a line here how the changes were tested) -- [ ] Added Unit Tests -- [ ] Added Integration Tests - *Documentation update required?* - [ ] No - [ ] Yes (Please include a PR link for the documentation update) +*General Checklist* +- [ ] Added Unit Tests +- [ ] Added Integration Tests +- [ ] Security oriented best practices and standards are followed (e.g. using input sanitization, principle of least privilege, etc) + By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json index f37720c3a2..1f952a1be9 100644 --- a/.github/pr-title-checker-config.json +++ b/.github/pr-title-checker-config.json @@ -7,7 +7,7 @@ "prefixes": ["chore: ", "refactor: ", "perf: ", "test: ", "docs: ", "release: "], "regexp": "(fix|feat)\\((all|analytics|api|auth|core|datastore|geo|predictions|storage|notifications)\\): ", "regexpFlags": "", - "ignoreLabels" : ["ignore-pr-title"] + "ignoreLabels" : ["ignore-pr-title"] }, "MESSAGES": { "success": "All OK", diff --git a/.github/workflows/codecov_code_coverage.yml b/.github/workflows/codecov_code_coverage.yml index 64263d90b0..31a6c2bba9 100644 --- a/.github/workflows/codecov_code_coverage.yml +++ b/.github/workflows/codecov_code_coverage.yml @@ -32,14 +32,14 @@ jobs: java-version: '11' distribution: 'corretto' - - name: Run test and generate jacoco report - run: ./gradlew jacocoTestReport + - name: Run test and generate kover report + run: ./gradlew koverReport - name: create temp directory run: mkdir /home/runner/work/amplify-android/amplify-android/code-coverage - - name: copy jacoco test report to temp directory - run: cp -r /home/runner/work/amplify-android/amplify-android/**/build/coverage-report/*.xml /home/runner/work/amplify-android/amplify-android/code-coverage/ + - name: copy kover test report to temp directory + run: cp -r /home/runner/work/amplify-android/amplify-android/**/build/reports/kover/xml/*.xml /home/runner/work/amplify-android/amplify-android/code-coverage/ - name: Upload Test Report uses: codecov/codecov-action@v3 diff --git a/.github/workflows/release_pr.yml b/.github/workflows/release_pr.yml index 4d7a29b1c7..058fcd2da7 100644 --- a/.github/workflows/release_pr.yml +++ b/.github/workflows/release_pr.yml @@ -38,7 +38,7 @@ jobs: bundle exec fastlane android configure_git_options git_user_email:$GIT_USER_EMAIL git_user_name:$GIT_USER_NAME - name: Create/checkout a branch for the release run: | - branch_name=bump_version + branch_name=bump_version_${{ env.BASE_BRANCH }} git fetch --all (git branch -D $branch_name &>/dev/null) && (echo 'Existing $branch_name branch deleted') || (echo 'No existing $branch_name branch to delete.') git checkout -b $branch_name @@ -48,7 +48,7 @@ jobs: RELEASE_TAG: ${{ github.event.inputs.release_tag }} run: | cd scripts - bundle exec fastlane android create_next_release_pr release_tag:"$RELEASE_TAG" + bundle exec fastlane android create_next_release_pr release_tag:"$RELEASE_TAG" base_branch:"$BASE_BRANCH" - name: Check modified file content run: | cat gradle.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index 92d6e312a2..08428cf88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## [Release 2.2.2](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.2.2) + +### Bug Fixes +- **auth:** fix npe in initialize fetch auth session ([#2284](https://github.com/aws-amplify/amplify-android/issues/2284)) +- **auth:** Fix confirm signin when incorrect MFA code is entered ([#2286](https://github.com/aws-amplify/amplify-android/issues/2286)) + +[See all changes between 2.2.1 and 2.2.2](https://github.com/aws-amplify/amplify-android/compare/release_v2.2.1...release_v2.2.2) + +## [Release 2.2.1](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.2.1) + +### Bug Fixes +- **auth:** Moving credential provider to main (#2273) + +[See all changes between 2.2.0 and 2.2.1](https://github.com/aws-amplify/amplify-android/compare/release_v2.2.0...release_v2.2.1) + +## [Release 2.2.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.2.0) + +### Features +- **auth:** add required hash param to cognito api calls (#2266) +- **datastore:** Add recoverability improvements (#2201) +- **auth:** Added parity test for fetchDevices,rememberDevice,forgetDevice and fetchUserAttributes (#2174) + +### Bug Fixes +- **analytics:** Remove test dependencies from implementation configuration (#2253) +- **auth:** Fix Authorization header for HostedUI fetchToken when appSecret is used (#2264) + +[See all changes between 2.1.1 and 2.2.0](https://github.com/aws-amplify/amplify-android/compare/release_v2.1.1...release_v2.2.0) + +## [Release 2.1.1](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.1.1) + +### Bug Fixes +- **datastore:** Fix lock contention issue when running DataStore.start() from the callback of DataStore.stop() (#2208) +- **core:** Remove unused dependencies (#2207) +- **geo:** Bump MapLibre SDK to 9.6.0 (#2254) + +[See all changes between 2.1.0 and 2.1.1](https://github.com/aws-amplify/amplify-android/compare/release_v2.1.0...release_v2.1.1) + ## [Release 2.1.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.1.0) ### Features diff --git a/README.md b/README.md index b96683ba22..f83edf1f29 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,12 @@ dependencies section: ```groovy dependencies { // Only specify modules that provide functionality your app will use - implementation 'com.amplifyframework:aws-analytics-pinpoint:2.1.0' - implementation 'com.amplifyframework:aws-api:2.1.0' - implementation 'com.amplifyframework:aws-auth-cognito:2.1.0' - implementation 'com.amplifyframework:aws-datastore:2.1.0' - implementation 'com.amplifyframework:aws-predictions:2.1.0' - implementation 'com.amplifyframework:aws-storage-s3:2.1.0' + implementation 'com.amplifyframework:aws-analytics-pinpoint:2.2.2' + implementation 'com.amplifyframework:aws-api:2.2.2' + implementation 'com.amplifyframework:aws-auth-cognito:2.2.2' + implementation 'com.amplifyframework:aws-datastore:2.2.2' + implementation 'com.amplifyframework:aws-predictions:2.2.2' + implementation 'com.amplifyframework:aws-storage-s3:2.2.2' } ``` diff --git a/aws-analytics-pinpoint-targeting/build.gradle b/aws-analytics-pinpoint-targeting/build.gradle deleted file mode 100644 index 1b032eeac3..0000000000 --- a/aws-analytics-pinpoint-targeting/build.gradle +++ /dev/null @@ -1,44 +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. - */ - -plugins { - id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10" -} -apply plugin: 'com.android.library' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") -apply plugin: 'kotlin-android' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -dependencies { - implementation project(path: ':core') - - implementation dependency.androidx.appcompat - implementation dependency.aws.pinpoint - implementation dependency.kotlin.serializationJson - implementation 'androidx.test.ext:junit-ktx:1.1.5' - - testImplementation dependency.junit - testImplementation dependency.mockk - testImplementation dependency.mockito - testImplementation dependency.mockitoinline - testImplementation dependency.robolectric - testImplementation dependency.androidx.test.core - testImplementation dependency.kotlin.test.coroutines - - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit -} diff --git a/aws-analytics-pinpoint-targeting/build.gradle.kts b/aws-analytics-pinpoint-targeting/build.gradle.kts new file mode 100644 index 0000000000..cc72f71a6d --- /dev/null +++ b/aws-analytics-pinpoint-targeting/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright 2023 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. + */ + +plugins { + id("org.jetbrains.kotlin.plugin.serialization") version "1.6.10" + id("com.android.library") + id("kotlin-android") + id("org.jlleitschuh.gradle.ktlint") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + + implementation(dependency.androidx.appcompat) + implementation(dependency.aws.pinpoint) + implementation(dependency.kotlin.serializationJson) +// implementation("androidx.test.ext:junit-ktx:1.1.5") + + testImplementation(testDependency.junit) + testImplementation(testDependency.mockk) + testImplementation(testDependency.mockito) + testImplementation(testDependency.mockitoinline) + testImplementation(testDependency.robolectric) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.kotlin.test.coroutines) + + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) +} diff --git a/aws-analytics-pinpoint-targeting/src/main/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorder.kt b/aws-analytics-pinpoint-targeting/src/main/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorder.kt index 9f46fe77c1..c705ff8f03 100644 --- a/aws-analytics-pinpoint-targeting/src/main/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorder.kt +++ b/aws-analytics-pinpoint-targeting/src/main/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorder.kt @@ -180,7 +180,7 @@ class EventRecorder( logger.info("Successfully submitted event with eventId ${pinpointEvent.eventId}") eventIdToDelete.add(pinpointEvent) } else { - if (isRetryableError(message, pinpointEventResponse.statusCode)) { + if (isRetryableError(pinpointEventResponse.statusCode)) { logger.error( "Failed to deliver event with ${pinpointEvent.eventId}," + " will be re-delivered later" @@ -196,13 +196,8 @@ class EventRecorder( return eventIdToDelete } - private fun isRetryableError(message: String, code: Int): Boolean { - return !( - message.equals("ValidationException", ignoreCase = true) || - message.equals("SerializationException", ignoreCase = true) || - message.equals("BadRequestException", ignoreCase = true) || - code == badRequestCode - ) + private fun isRetryableError(code: Int): Boolean { + return code in 500..599 } private fun processEndpointResponse(endpointResponse: EndpointItemResponse?) { diff --git a/aws-analytics-pinpoint-targeting/src/test/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorderTest.kt b/aws-analytics-pinpoint-targeting/src/test/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorderTest.kt index 9dbd2b9a6f..1b704c89b9 100644 --- a/aws-analytics-pinpoint-targeting/src/test/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorderTest.kt +++ b/aws-analytics-pinpoint-targeting/src/test/java/com/amplifyframework/analytics/pinpoint/targeting/EventRecorderTest.kt @@ -132,6 +132,52 @@ class EventRecorderTest { } } + @Test + fun `test retryable errors`() = runTest { + val pinpointEvent1 = getPinpointEvent("testEvent1") + val pinpointEvent2 = getPinpointEvent("testEvent2") + + // setup database + val pinpointEvents = listOf( + arrayOf(1, pinpointEvent1.toJsonString().length, pinpointEvent1.toJsonString()), + arrayOf(2, pinpointEvent2.toJsonString().length, pinpointEvent2.toJsonString()) + ) + val matrixCursor = MatrixCursor(arrayOf(EventTable.COLUMN_ID, EventTable.COLUMN_SIZE, EventTable.COLUMN_JSON)) + pinpointEvents.forEach { + matrixCursor.addRow(it) + } + coEvery { pinpointDatabaseMock.queryAllEvents() }.answers { matrixCursor } + + // setup pinpoint client + val endpointId = UUID.randomUUID().toString() + coEvery { endpointProfile.endpointId }.answers { endpointId } + coEvery { targetingClient.currentEndpoint() }.answers { endpointProfile } + val itemResponse = ItemResponse { + eventsItemResponse = mapOf( + pinpointEvent1.eventId to EventItemResponse { + message = "Internal Server Error" + statusCode = 500 + }, + pinpointEvent2.eventId to EventItemResponse { + message = "Not real, but 599 should be retryable as well" + statusCode = 599 + } + ) + } + val putEventResponse = PutEventsResponse { + eventsResponse = EventsResponse { + results = mapOf(endpointId to itemResponse) + } + } + coEvery { pinpointClient.putEvents(any()) }.answers { putEventResponse } + + eventRecorder.submitEvents() + + coVerify(exactly = 0) { + pinpointDatabaseMock.deleteEventById(any()) + } + } + private fun getPinpointEvent(eventType: String): PinpointEvent { return PinpointEvent( eventType = eventType, diff --git a/aws-analytics-pinpoint/build.gradle b/aws-analytics-pinpoint/build.gradle deleted file mode 100644 index 012e9b4068..0000000000 --- a/aws-analytics-pinpoint/build.gradle +++ /dev/null @@ -1,49 +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. - */ - -plugins { - id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10" -} -apply plugin: 'com.android.library' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") -apply plugin: 'kotlin-android' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -group = POM_GROUP - -dependencies { - implementation project(path: ':core') - implementation project(path: ':aws-auth-cognito') - implementation project(path: ':aws-analytics-pinpoint-targeting') - - implementation dependency.androidx.appcompat - implementation dependency.aws.pinpoint - implementation dependency.kotlin.serializationJson - - testImplementation dependency.junit - testImplementation dependency.mockk - testImplementation dependency.robolectric - implementation dependency.androidx.test.core - testImplementation dependency.kotlin.test.coroutines - testImplementation project(path: ':aws-analytics-pinpoint') - - androidTestImplementation project(path: ':testutils') - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.kotlin.test.coroutines - implementation dependency.androidx.test.junit - androidTestImplementation project(path: ':aws-analytics-pinpoint') -} diff --git a/aws-analytics-pinpoint/build.gradle.kts b/aws-analytics-pinpoint/build.gradle.kts new file mode 100644 index 0000000000..79e3a108b9 --- /dev/null +++ b/aws-analytics-pinpoint/build.gradle.kts @@ -0,0 +1,53 @@ +/* + * Copyright 2023 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. + */ + +plugins { + id("org.jetbrains.kotlin.plugin.serialization") version "1.6.10" + id("com.android.library") + id("kotlin-android") + id("org.jlleitschuh.gradle.ktlint") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(project(":aws-analytics-pinpoint-targeting")) + + implementation(dependency.androidx.appcompat) + implementation(dependency.aws.pinpoint) + implementation(dependency.kotlin.serializationJson) + + testImplementation(testDependency.junit) + testImplementation(testDependency.mockk) + testImplementation(testDependency.mockito) + testImplementation(testDependency.mockitoinline) + testImplementation(testDependency.robolectric) + testImplementation(testDependency.androidx.test.junit) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.kotlin.test.coroutines) + testImplementation(project(":aws-analytics-pinpoint")) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(project(":aws-auth-cognito")) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.kotlin.test.coroutines) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(project(":aws-analytics-pinpoint")) +} diff --git a/aws-analytics-pinpoint/src/androidTest/java/com/amplifyframework/analytics/pinpoint/PinpointAnalyticsInstrumentationTest.kt b/aws-analytics-pinpoint/src/androidTest/java/com/amplifyframework/analytics/pinpoint/PinpointAnalyticsInstrumentationTest.kt index 6f9de317c2..dfcedbf0fb 100644 --- a/aws-analytics-pinpoint/src/androidTest/java/com/amplifyframework/analytics/pinpoint/PinpointAnalyticsInstrumentationTest.kt +++ b/aws-analytics-pinpoint/src/androidTest/java/com/amplifyframework/analytics/pinpoint/PinpointAnalyticsInstrumentationTest.kt @@ -27,8 +27,8 @@ import aws.sdk.kotlin.services.pinpoint.model.GetEndpointRequest import com.amplifyframework.analytics.AnalyticsEvent import com.amplifyframework.analytics.AnalyticsProperties import com.amplifyframework.analytics.UserProfile -import com.amplifyframework.analytics.pinpoint.targeting.endpointProfile.EndpointProfile import com.amplifyframework.analytics.pinpoint.models.AWSPinpointUserProfile +import com.amplifyframework.analytics.pinpoint.targeting.endpointProfile.EndpointProfile import com.amplifyframework.auth.AuthPlugin import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin import com.amplifyframework.core.Amplify diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPlugin.kt b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPlugin.kt index 552e09c628..066414b4a8 100644 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPlugin.kt +++ b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPlugin.kt @@ -20,7 +20,7 @@ import com.amplifyframework.analytics.AnalyticsEventBehavior import com.amplifyframework.analytics.AnalyticsPlugin import com.amplifyframework.analytics.AnalyticsProperties import com.amplifyframework.analytics.UserProfile -import com.amplifyframework.analytics.pinpoint.credentials.CognitoCredentialsProvider +import com.amplifyframework.auth.CognitoCredentialsProvider import org.json.JSONObject /** diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/credentials/CognitoCredentialsProvider.kt b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/credentials/CognitoCredentialsProvider.kt deleted file mode 100644 index 315d746622..0000000000 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/credentials/CognitoCredentialsProvider.kt +++ /dev/null @@ -1,94 +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.analytics.pinpoint.credentials - -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import com.amplifyframework.auth.AWSCredentials -import com.amplifyframework.auth.AWSTemporaryCredentials -import com.amplifyframework.auth.AuthCredentialsProvider -import com.amplifyframework.auth.AuthSession -import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import com.amplifyframework.core.Amplify -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -/** - * Internal implementation of cognito credentials provider. - * This will be ported to core once it seems feasible to do so. - */ -internal class CognitoCredentialsProvider : AuthCredentialsProvider { - /** - * Request identityId from the provider. - */ - override suspend fun getIdentityId(): String { - return suspendCoroutine { continuation -> - Amplify.Auth.fetchAuthSession( - { authSession -> - authSession.toAWSCognitoAuthSession()?.identityIdResult?.value?.let { - continuation.resume(it) - } ?: continuation.resumeWithException( - Exception( - "Failed to get identity ID. " + - "Check if you are signed in and configured identity pools correctly." - ) - ) - }, - { - continuation.resumeWithException(it) - } - ) - } - } - - /** - * Request [Credentials] from the provider. - */ - override suspend fun getCredentials(): Credentials { - return suspendCoroutine { continuation -> - Amplify.Auth.fetchAuthSession( - { authSession -> - authSession.toAWSCognitoAuthSession()?.awsCredentialsResult?.value?.let { - continuation.resume(it.toCredentials()) - } ?: continuation.resumeWithException( - Exception( - "Failed to get credentials. " + - "Check if you are signed in and configured identity pools correctly." - ) - ) - }, - { - continuation.resumeWithException(it) - } - ) - } - } -} - -private fun AWSCredentials.toCredentials(): Credentials { - return Credentials( - accessKeyId = this.accessKeyId, - secretAccessKey = this.secretAccessKey, - sessionToken = (this as? AWSTemporaryCredentials)?.sessionToken, - expiration = (this as? AWSTemporaryCredentials)?.expiration - ) -} - -private fun AuthSession.toAWSCognitoAuthSession(): AWSCognitoAuthSession? { - if (this is AWSCognitoAuthSession) { - return this - } - - return null -} diff --git a/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt b/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt index 9ecc591f16..d6ef968ab6 100644 --- a/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt +++ b/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt @@ -18,12 +18,12 @@ import android.content.SharedPreferences import androidx.test.core.app.ApplicationProvider import com.amplifyframework.analytics.AnalyticsEvent import com.amplifyframework.analytics.AnalyticsProperties +import com.amplifyframework.analytics.pinpoint.models.AWSPinpointUserProfile import com.amplifyframework.analytics.pinpoint.targeting.AnalyticsClient import com.amplifyframework.analytics.pinpoint.targeting.TargetingClient import com.amplifyframework.analytics.pinpoint.targeting.data.AndroidAppDetails import com.amplifyframework.analytics.pinpoint.targeting.data.AndroidDeviceDetails import com.amplifyframework.analytics.pinpoint.targeting.endpointProfile.EndpointProfile -import com.amplifyframework.analytics.pinpoint.models.AWSPinpointUserProfile import com.amplifyframework.analytics.pinpoint.targeting.models.PinpointEvent import com.amplifyframework.analytics.pinpoint.targeting.models.PinpointSession import com.amplifyframework.analytics.pinpoint.targeting.models.SDKInfo diff --git a/aws-api-appsync/build.gradle b/aws-api-appsync/build.gradle deleted file mode 100644 index 8f40bfbe83..0000000000 --- a/aws-api-appsync/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 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. - */ - -apply plugin: 'com.android.library' -apply from: rootProject.file('configuration/checkstyle.gradle') -apply from: rootProject.file('configuration/publishing.gradle') - -group = POM_GROUP - -dependencies { - implementation project(':core') - - implementation dependency.androidx.annotation - implementation dependency.androidx.core - implementation dependency.gson - - testImplementation dependency.junit - testImplementation dependency.robolectric - testImplementation dependency.jsonassert - testImplementation project(path: ':testmodels') - testImplementation project(path: ':testutils') -} diff --git a/aws-api-appsync/build.gradle.kts b/aws-api-appsync/build.gradle.kts new file mode 100644 index 0000000000..093f53b53c --- /dev/null +++ b/aws-api-appsync/build.gradle.kts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + + implementation(dependency.androidx.annotation) + implementation(dependency.androidx.core) + implementation(dependency.gson) + + testImplementation(testDependency.junit) + testImplementation(testDependency.robolectric) + testImplementation(testDependency.jsonassert) + testImplementation(project(":testmodels")) + testImplementation(project(":testutils")) +} diff --git a/aws-api/build.gradle b/aws-api/build.gradle deleted file mode 100644 index 0ff90b404b..0000000000 --- a/aws-api/build.gradle +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019 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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -dependencies { - api project(':core') - implementation project(':aws-api-appsync') - implementation project(':aws-auth-cognito') - - implementation dependency.androidx.appcompat - implementation dependency.aws.signing - implementation dependency.gson - implementation dependency.okhttp - - testImplementation project(path: ':testutils') - testImplementation project(path: ':testmodels') - testImplementation dependency.androidx.test.core - testImplementation dependency.jsonassert - testImplementation dependency.junit - testImplementation dependency.mockito - testImplementation dependency.mockwebserver - testImplementation dependency.rxjava - testImplementation dependency.robolectric - - androidTestImplementation project(path: ':testutils') - androidTestImplementation project(path: ':testmodels') - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit - androidTestImplementation dependency.rxjava -} diff --git a/aws-api/build.gradle.kts b/aws-api/build.gradle.kts new file mode 100644 index 0000000000..e5a1da97d1 --- /dev/null +++ b/aws-api/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + api(project(":core")) + implementation(project(":aws-api-appsync")) + + implementation(dependency.androidx.appcompat) + implementation(dependency.aws.signing) + implementation(dependency.gson) + implementation(dependency.okhttp) + + testImplementation(project(":testutils")) + testImplementation(project(":testmodels")) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.jsonassert) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) + testImplementation(testDependency.mockwebserver) + testImplementation(dependency.rxjava) + testImplementation(testDependency.robolectric) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(project(":testmodels")) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(project(":aws-auth-cognito")) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(dependency.rxjava) +} diff --git a/aws-api/src/androidTest/java/com/amplifyframework/api/aws/GraphQLInstrumentationTest.java b/aws-api/src/androidTest/java/com/amplifyframework/api/aws/GraphQLInstrumentationTest.java index a920eb2420..e97a7b99b9 100644 --- a/aws-api/src/androidTest/java/com/amplifyframework/api/aws/GraphQLInstrumentationTest.java +++ b/aws-api/src/androidTest/java/com/amplifyframework/api/aws/GraphQLInstrumentationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -15,8 +15,6 @@ package com.amplifyframework.api.aws; -import androidx.annotation.NonNull; - import com.amplifyframework.AmplifyException; import com.amplifyframework.api.ApiCategory; import com.amplifyframework.api.ApiException; @@ -62,7 +60,6 @@ */ public final class GraphQLInstrumentationTest { private static final String API_WITH_API_KEY = "eventsApi"; - private static final String API_WITH_AWS_IAM = "eventsApiWithIam"; private static final String API_WITH_COGNITO_USER_POOLS = "eventsApiWithUserPools"; private static SynchronousApi api; @@ -96,8 +93,8 @@ public static void tearDown() { } /** - * Start auth with signedout state. - * @throws AuthException if signout fails. + * Start auth with signed out state. + * @throws AuthException if sign out fails. */ @Before public void setUpAuth() throws AuthException { @@ -231,22 +228,15 @@ private String createEvent() throws ApiException { */ static final class Comment implements Model { private final String content; - private final String id; @SuppressWarnings("checkstyle:ParameterName") - Comment(final String id, final String content) { - this.id = id; + Comment(final String content) { this.content = content; } String content() { return content; } - - @NonNull - public String getId() { - return id; - } } /** @@ -293,11 +283,6 @@ String where() { String description() { return description; } - - @NonNull - public String getId() { - return id; - } } } diff --git a/aws-api/src/androidTest/java/com/amplifyframework/api/aws/TestApiCategory.java b/aws-api/src/androidTest/java/com/amplifyframework/api/aws/TestApiCategory.java index c494127abe..da30aec527 100644 --- a/aws-api/src/androidTest/java/com/amplifyframework/api/aws/TestApiCategory.java +++ b/aws-api/src/androidTest/java/com/amplifyframework/api/aws/TestApiCategory.java @@ -20,9 +20,9 @@ import com.amplifyframework.AmplifyException; import com.amplifyframework.api.ApiCategory; -import com.amplifyframework.api.aws.auth.CognitoCredentialsProvider; import com.amplifyframework.api.aws.sigv4.CognitoUserPoolsAuthProvider; import com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider; +import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Amplify; import com.amplifyframework.core.AmplifyConfiguration; import com.amplifyframework.core.category.CategoryConfiguration; diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/ApiAuthProviders.java b/aws-api/src/main/java/com/amplifyframework/api/aws/ApiAuthProviders.java index 790366708d..e2a15b889d 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/ApiAuthProviders.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/ApiAuthProviders.java @@ -17,11 +17,11 @@ import androidx.annotation.NonNull; -import com.amplifyframework.api.aws.auth.CognitoCredentialsProvider; import com.amplifyframework.api.aws.sigv4.ApiKeyAuthProvider; import com.amplifyframework.api.aws.sigv4.CognitoUserPoolsAuthProvider; import com.amplifyframework.api.aws.sigv4.FunctionAuthProvider; import com.amplifyframework.api.aws.sigv4.OidcAuthProvider; +import com.amplifyframework.auth.CognitoCredentialsProvider; import java.util.Objects; diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/AppSyncGraphQLOperation.java b/aws-api/src/main/java/com/amplifyframework/api/aws/AppSyncGraphQLOperation.java index a67f9882b2..342ce1f9da 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/AppSyncGraphQLOperation.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/AppSyncGraphQLOperation.java @@ -134,6 +134,7 @@ public void onResponse(@NonNull Call call, @NonNull Response response) { try { jsonResponse = responseBody.string(); } catch (IOException exception) { + LOG.warn("Error retrieving JSON from response.", exception); onFailure.accept(new ApiException( "Could not retrieve the response body from the returned JSON", exception, AmplifyException.TODO_RECOVERY_SUGGESTION diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactory.java b/aws-api/src/main/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactory.java index 749812fb34..1857d346f4 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactory.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactory.java @@ -22,6 +22,7 @@ import com.amplifyframework.api.graphql.GraphQLRequest; import com.amplifyframework.api.graphql.GraphQLResponse; import com.amplifyframework.api.graphql.PaginatedResult; +import com.amplifyframework.util.Empty; import com.amplifyframework.util.GsonFactory; import com.amplifyframework.util.TypeMaker; @@ -32,7 +33,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; -import com.google.gson.JsonSyntaxException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -57,17 +57,29 @@ final class GsonGraphQLResponseFactory implements GraphQLResponse.Factory { @Override public GraphQLResponse buildResponse(GraphQLRequest request, String responseJson) throws ApiException { + + // On empty strings, Gson returns null instead of throwing JsonSyntaxException. See: + // https://github.com/google/gson/issues/457 + // https://github.com/google/gson/issues/1697 + if (Empty.check(responseJson)) { + throw new ApiException( + "Amplify encountered an error while deserializing an object.", + new JsonParseException("Empty response."), + AmplifyException.TODO_RECOVERY_SUGGESTION + ); + } + Type responseType = TypeMaker.getParameterizedType(GraphQLResponse.class, request.getResponseType()); try { Gson responseGson = gson.newBuilder() .registerTypeHierarchyAdapter(Iterable.class, new IterableDeserializer<>(request)) .create(); return responseGson.fromJson(responseJson, responseType); - } catch (JsonSyntaxException jsonSyntaxException) { + } catch (JsonParseException jsonParseException) { throw new ApiException( - "Amplify encountered an error while deserializing an object.", - jsonSyntaxException, - AmplifyException.TODO_RECOVERY_SUGGESTION + "Amplify encountered an error while deserializing an object.", + jsonParseException, + AmplifyException.TODO_RECOVERY_SUGGESTION ); } } diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/MultiAuthAppSyncGraphQLOperation.java b/aws-api/src/main/java/com/amplifyframework/api/aws/MultiAuthAppSyncGraphQLOperation.java index d285f424e7..5926698a22 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/MultiAuthAppSyncGraphQLOperation.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/MultiAuthAppSyncGraphQLOperation.java @@ -55,7 +55,6 @@ public final class MultiAuthAppSyncGraphQLOperation extends GraphQLOperation { private static final Logger LOG = Amplify.Logging.forNamespace("amplify:aws-api"); private static final String CONTENT_TYPE = "application/json"; - private static final String UNAUTHORIZED_EXCEPTION = "UnauthorizedException"; private final String endpoint; private final OkHttpClient client; diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionAuthorizer.java b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionAuthorizer.java index 8b1bedb3ed..96665fc1b7 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionAuthorizer.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionAuthorizer.java @@ -20,7 +20,6 @@ import com.amplifyframework.AmplifyException; import com.amplifyframework.api.ApiException; import com.amplifyframework.api.ApiException.ApiAuthException; -import com.amplifyframework.api.aws.auth.CognitoCredentialsProvider; import com.amplifyframework.api.aws.auth.IamRequestDecorator; import com.amplifyframework.api.aws.sigv4.ApiKeyAuthProvider; import com.amplifyframework.api.aws.sigv4.AppSyncV4Signer; @@ -29,6 +28,7 @@ import com.amplifyframework.api.aws.sigv4.FunctionAuthProvider; import com.amplifyframework.api.aws.sigv4.OidcAuthProvider; import com.amplifyframework.api.graphql.GraphQLRequest; +import com.amplifyframework.auth.CognitoCredentialsProvider; import org.json.JSONException; import org.json.JSONObject; diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java index a8da99d9b0..cb7d2b0db6 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java @@ -148,15 +148,16 @@ synchronized void requestSubscription( } try { - webSocket.send(new JSONObject() + String jsonMessage = new JSONObject() .put("id", subscriptionId) .put("type", "start") .put("payload", new JSONObject() .put("data", request.getContent()) .put("extensions", new JSONObject() .put("authorization", authorizer.createHeadersForSubscription(request, authType)))) - .toString() - ); + .toString(); + + webSocket.send(jsonMessage); } catch (JSONException | ApiException exception) { // If the subscriptionId was still pending, then we can call the onSubscriptionError if (pendingSubscriptionIds.remove(subscriptionId)) { @@ -266,10 +267,12 @@ synchronized void releaseSubscription(String subscriptionId) throws ApiException if (!wasSubscriptionPending && !webSocketListener.isDisconnectedState()) { try { - webSocket.send(new JSONObject() + String jsonMessage = new JSONObject() .put("type", "stop") .put("id", subscriptionId) - .toString()); + .toString(); + + webSocket.send(jsonMessage); } catch (JSONException jsonException) { throw new ApiException( "Failed to construct subscription release message.", @@ -557,9 +560,11 @@ public Connection waitForConnectionReady() { private void sendConnectionInit(WebSocket webSocket) { try { - webSocket.send(new JSONObject() + String jsonMessage = new JSONObject() .put("type", "connection_init") - .toString()); + .toString(); + + webSocket.send(jsonMessage); } catch (JSONException jsonException) { notifyError(jsonException); } @@ -573,10 +578,14 @@ private void processJsonMessage(WebSocket webSocket, String message) throws ApiE switch (subscriptionMessageType) { case CONNECTION_ACK: - timeoutWatchdog.start(() -> webSocket.close( - NORMAL_CLOSURE_STATUS, - "WebSocket closed due to timeout." - ), + timeoutWatchdog.start(() -> { + LOG.warn("WebSocket closed due to timeout."); + + webSocket.close( + NORMAL_CLOSURE_STATUS, + "WebSocket closed due to timeout." + ); + }, Integer.parseInt( jsonMessage.getJSONObject("payload").getString("connectionTimeoutMs") ) diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/auth/ApiRequestDecoratorFactory.java b/aws-api/src/main/java/com/amplifyframework/api/aws/auth/ApiRequestDecoratorFactory.java index e47f199858..4739360961 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/auth/ApiRequestDecoratorFactory.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/auth/ApiRequestDecoratorFactory.java @@ -30,6 +30,7 @@ import com.amplifyframework.api.aws.sigv4.CognitoUserPoolsAuthProvider; import com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider; import com.amplifyframework.api.graphql.GraphQLRequest; +import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Amplify; import com.amplifyframework.logging.Logger; diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/auth/CognitoCredentialsProvider.kt b/aws-api/src/main/java/com/amplifyframework/api/aws/auth/CognitoCredentialsProvider.kt deleted file mode 100644 index 62c7d81f07..0000000000 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/auth/CognitoCredentialsProvider.kt +++ /dev/null @@ -1,97 +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.api.aws.auth - -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import com.amplifyframework.api.ApiException -import com.amplifyframework.auth.AWSCredentials -import com.amplifyframework.auth.AWSTemporaryCredentials -import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import com.amplifyframework.core.Amplify -import com.amplifyframework.core.Consumer -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext - -/** - * Wrapper to provide credentials from Auth synchronously and asynchronously - */ -internal open class CognitoCredentialsProvider : CredentialsProvider { - /** - * Request [Credentials] synchronously by blocking current suspend execution. - */ - fun getCredentialsBlocking(): Credentials { - return runBlocking { - withContext(Dispatchers.IO) { - getCredentials() - } - } - } - - /** - * Request [Credentials] from the provider. - */ - override suspend fun getCredentials(): Credentials { - return suspendCoroutine { continuation -> - Amplify.Auth.fetchAuthSession( - { authSession -> - (authSession as? AWSCognitoAuthSession)?.awsCredentialsResult?.value?.let { - continuation.resume(it.toCredentials()) - } ?: continuation.resumeWithException( - Exception( - "Failed to get credentials. " + - "Check if you are signed in and configured identity pools correctly." - ) - ) - }, - { - continuation.resumeWithException(it) - } - ) - } - } - - fun getAccessToken(onResult: Consumer, onFailure: Consumer) { - Amplify.Auth.fetchAuthSession( - { session -> - val tokens = (session as? AWSCognitoAuthSession)?.userPoolTokensResult?.value?.accessToken - tokens?.let { onResult.accept(it) } - ?: onFailure.accept( - ApiException.ApiAuthException( - "Token is null", - "Token received but is null. Check if you are signed in" - ) - ) - }, - { - onFailure.accept(it) - } - ) - } -} - -private fun AWSCredentials.toCredentials(): Credentials { - return Credentials( - accessKeyId = this.accessKeyId, - secretAccessKey = this.secretAccessKey, - sessionToken = (this as? AWSTemporaryCredentials)?.sessionToken, - expiration = (this as? AWSTemporaryCredentials)?.expiration - ) -} diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/sigv4/DefaultCognitoUserPoolsAuthProvider.java b/aws-api/src/main/java/com/amplifyframework/api/aws/sigv4/DefaultCognitoUserPoolsAuthProvider.java index d3d5acfd00..50e870d08c 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/sigv4/DefaultCognitoUserPoolsAuthProvider.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/sigv4/DefaultCognitoUserPoolsAuthProvider.java @@ -18,8 +18,8 @@ import com.amplifyframework.AmplifyException; import com.amplifyframework.api.ApiException; import com.amplifyframework.api.ApiException.ApiAuthException; -import com.amplifyframework.api.aws.auth.CognitoCredentialsProvider; import com.amplifyframework.auth.AuthUser; +import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Amplify; import java.util.concurrent.Semaphore; diff --git a/aws-api/src/test/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactoryTest.java b/aws-api/src/test/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactoryTest.java index a4e4bcf01d..f4eec9df72 100644 --- a/aws-api/src/test/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactoryTest.java +++ b/aws-api/src/test/java/com/amplifyframework/api/aws/GsonGraphQLResponseFactoryTest.java @@ -49,6 +49,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; /** * Unit test for implementation of ResponseFactory. @@ -92,6 +93,47 @@ public void nullDataNullErrorsReturnsEmptyResponseObject() throws ApiException { assertEquals(new ArrayList<>(), response.getErrors()); } + /** + * Validates that a response with non-Json content throws an + * ApiException instead of a RuntimeException. + * @throws ApiException From API configuration + */ + @Test + public void nonJsonResponseThrowsApiException() throws ApiException { + // Arrange some non-JSON string from a "server" + final String nonJsonResponse = + Resources.readAsString("non-json-gql-response.json"); + + // Act! Parse it into a model. + Type responseType = TypeMaker.getParameterizedType(PaginatedResult.class, Todo.class); + GraphQLRequest> request = buildDummyRequest(responseType); + + // Assert that the appropriate exception is thrown + assertThrows(ApiException.class, () -> { + responseFactory.buildResponse(request, nonJsonResponse); + }); + } + + /** + * Validates that an empty response throws an + * ApiException instead of returning a null reference. + * @throws ApiException From API configuration + */ + @Test + public void emptyResponseThrowsApiException() throws ApiException { + // Arrange some empty string from a "server" + final String emptyResponse = ""; + + // Act! Parse it into a model. + Type responseType = TypeMaker.getParameterizedType(PaginatedResult.class, Todo.class); + GraphQLRequest> request = buildDummyRequest(responseType); + + // Assert that the appropriate exception is thrown + assertThrows(ApiException.class, () -> { + responseFactory.buildResponse(request, emptyResponse); + }); + } + /** * Validates that the converter is able to parse a partial GraphQL * response into a result. In this case, the result contains some diff --git a/aws-api/src/test/java/com/amplifyframework/api/aws/auth/DummyCredentialsProvider.kt b/aws-api/src/test/java/com/amplifyframework/api/aws/auth/DummyCredentialsProvider.kt index aca7a2957f..412649b580 100644 --- a/aws-api/src/test/java/com/amplifyframework/api/aws/auth/DummyCredentialsProvider.kt +++ b/aws-api/src/test/java/com/amplifyframework/api/aws/auth/DummyCredentialsProvider.kt @@ -16,6 +16,7 @@ package com.amplifyframework.api.aws.auth import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import com.amplifyframework.auth.CognitoCredentialsProvider internal object DummyCredentialsProvider : CognitoCredentialsProvider() { override suspend fun getCredentials(): Credentials { diff --git a/aws-api/src/test/resources/non-json-gql-response.json b/aws-api/src/test/resources/non-json-gql-response.json new file mode 100644 index 0000000000..9dc834840f --- /dev/null +++ b/aws-api/src/test/resources/non-json-gql-response.json @@ -0,0 +1 @@ +Not a JSON response \ No newline at end of file diff --git a/aws-auth-cognito/build.gradle b/aws-auth-cognito/build.gradle deleted file mode 100644 index c83f863946..0000000000 --- a/aws-auth-cognito/build.gradle +++ /dev/null @@ -1,61 +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. - */ - -plugins { - id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10" -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file("configuration/publishing.gradle") -apply from: rootProject.file("configuration/checkstyle.gradle") - -group = POM_GROUP - -dependencies { - implementation project(path: ':core') - implementation dependency.kotlin.coroutines - implementation dependency.kotlin.serializationJson - implementation dependency.androidx.appcompat - implementation dependency.androidx.security - implementation dependency.androidx.browser - - implementation dependency.aws.cognitoidentity - implementation dependency.aws.cognitoidentityprovider - - testImplementation project(path: ':testutils') - testImplementation project(path: ':aws-auth-cognito') - //noinspection GradleDependency - testImplementation 'org.json:json:20180813' - testImplementation dependency.kotlin.test.junit - testImplementation dependency.kotlin.test.kotlinTest - testImplementation dependency.kotlin.test.coroutines - - testImplementation dependency.gson - testImplementation dependency.junit - testImplementation dependency.mockito - testImplementation dependency.mockk - testImplementation dependency.robolectric - testImplementation dependency.androidx.test.core - testImplementation dependency.kotlin.reflection - - androidTestImplementation dependency.gson - //noinspection GradleDependency - androidTestImplementation "com.amazonaws:aws-android-sdk-core:2.55.0" - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit - androidTestImplementation project(path: ':testutils') - androidTestImplementation project(path: ':aws-auth-cognito') -} diff --git a/aws-auth-cognito/build.gradle.kts b/aws-auth-cognito/build.gradle.kts new file mode 100644 index 0000000000..0d4db7c918 --- /dev/null +++ b/aws-auth-cognito/build.gradle.kts @@ -0,0 +1,60 @@ +/* + * 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. + */ + +plugins { + id("org.jetbrains.kotlin.plugin.serialization") version "1.6.10" + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/publishing.gradle")) +apply(from = rootProject.file("configuration/checkstyle.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(dependency.kotlin.coroutines) + implementation(dependency.kotlin.serializationJson) + implementation(dependency.androidx.appcompat) + implementation(dependency.androidx.security) + implementation(dependency.androidx.browser) + + implementation(dependency.aws.cognitoidentity) + implementation(dependency.aws.cognitoidentityprovider) + + testImplementation(project(":testutils")) + //noinspection GradleDependency + testImplementation(testDependency.json) + + testImplementation(testDependency.kotlin.test.junit) + testImplementation(testDependency.kotlin.test.kotlinTest) + testImplementation(testDependency.kotlin.test.coroutines) + + testImplementation(dependency.gson) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) + testImplementation(testDependency.mockk) + testImplementation(testDependency.robolectric) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.kotlin.reflection) + + androidTestImplementation(dependency.gson) + //noinspection GradleDependency + androidTestImplementation(dependency.aws.sdk.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(project(":testutils")) +} 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 55e131490b..da0e5e46c2 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 @@ -15,9 +15,10 @@ package com.amplifyframework.auth.cognito +import com.amplifyframework.auth.AWSAuthSessionInternal +import com.amplifyframework.auth.AWSCognitoUserPoolTokens import com.amplifyframework.auth.AWSCredentials import com.amplifyframework.auth.AuthException -import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.cognito.helpers.SessionHelper import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.auth.exceptions.InvalidStateException @@ -39,46 +40,12 @@ import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens */ data class AWSCognitoAuthSession internal constructor( @get:JvmName("getSignedIn") - val isSignedIn: Boolean, - val identityIdResult: AuthSessionResult, - val awsCredentialsResult: AuthSessionResult, - val userSubResult: AuthSessionResult, - val userPoolTokensResult: AuthSessionResult -) : AuthSession(isSignedIn) { - internal companion object { - fun getCredentialsResult(awsCredentials: CognitoCredentials): AuthSessionResult = - with(awsCredentials) { - AWSCredentials.createAWSCredentials(accessKeyId, secretAccessKey, sessionToken, expiration) - }?.let { - AuthSessionResult.success(it) - } ?: AuthSessionResult.failure(UnknownException("Failed to fetch AWS credentials.")) - - fun getIdentityIdResult(identityId: String): AuthSessionResult { - return if (identityId.isNotEmpty()) AuthSessionResult.success(identityId) - else AuthSessionResult.failure(UnknownException("Failed to fetch identity id.")) - } - - fun getUserSubResult(userPoolTokens: CognitoUserPoolTokens?): AuthSessionResult { - return try { - AuthSessionResult.success(userPoolTokens?.accessToken?.let(SessionHelper::getUserSub)) - } catch (e: Exception) { - AuthSessionResult.failure(UnknownException(cause = e)) - } - } - - fun getUserPoolTokensResult( - cognitoUserPoolTokens: CognitoUserPoolTokens - ): AuthSessionResult { - return AuthSessionResult.success( - AWSCognitoUserPoolTokens( - accessToken = cognitoUserPoolTokens.accessToken, - idToken = cognitoUserPoolTokens.idToken, - refreshToken = cognitoUserPoolTokens.refreshToken - ) - ) - } - } -} + override val isSignedIn: Boolean, + override val identityIdResult: AuthSessionResult, + override val awsCredentialsResult: AuthSessionResult, + override val userSubResult: AuthSessionResult, + override val userPoolTokensResult: AuthSessionResult +) : AWSAuthSessionInternal(isSignedIn, identityIdResult, awsCredentialsResult, userSubResult, userPoolTokensResult) internal fun AmplifyCredential.isValid(): Boolean { return when (this) { @@ -92,7 +59,38 @@ internal fun AmplifyCredential.isValid(): Boolean { internal fun AmplifyCredential.getCognitoSession( exception: AuthException = SignedOutException() -): AWSCognitoAuthSession { +): AWSAuthSessionInternal { + fun getCredentialsResult(awsCredentials: CognitoCredentials): AuthSessionResult = + with(awsCredentials) { + AWSCredentials.createAWSCredentials(accessKeyId, secretAccessKey, sessionToken, expiration) + }?.let { + AuthSessionResult.success(it) + } ?: AuthSessionResult.failure(UnknownException("Failed to fetch AWS credentials.")) + + fun getIdentityIdResult(identityId: String): AuthSessionResult { + return if (identityId.isNotEmpty()) AuthSessionResult.success(identityId) + else AuthSessionResult.failure(UnknownException("Failed to fetch identity id.")) + } + + fun getUserSubResult(userPoolTokens: CognitoUserPoolTokens?): AuthSessionResult { + return try { + AuthSessionResult.success(userPoolTokens?.accessToken?.let(SessionHelper::getUserSub)) + } catch (e: Exception) { + AuthSessionResult.failure(UnknownException(cause = e)) + } + } + + fun getUserPoolTokensResult( + cognitoUserPoolTokens: CognitoUserPoolTokens + ): AuthSessionResult { + return AuthSessionResult.success( + AWSCognitoUserPoolTokens( + accessToken = cognitoUserPoolTokens.accessToken, + idToken = cognitoUserPoolTokens.idToken, + refreshToken = cognitoUserPoolTokens.refreshToken + ) + ) + } return when (this) { is AmplifyCredential.UserPool -> AWSCognitoAuthSession( true, @@ -108,20 +106,20 @@ internal fun AmplifyCredential.getCognitoSession( "Cognito Identity not configured. Please check amplifyconfiguration.json file." ) ), - userSubResult = AWSCognitoAuthSession.getUserSubResult(signedInData.cognitoUserPoolTokens), - userPoolTokensResult = AWSCognitoAuthSession.getUserPoolTokensResult(signedInData.cognitoUserPoolTokens) + userSubResult = getUserSubResult(signedInData.cognitoUserPoolTokens), + userPoolTokensResult = getUserPoolTokensResult(signedInData.cognitoUserPoolTokens) ) is AmplifyCredential.UserAndIdentityPool -> AWSCognitoAuthSession( true, - identityIdResult = AWSCognitoAuthSession.getIdentityIdResult(identityId), - awsCredentialsResult = AWSCognitoAuthSession.getCredentialsResult(credentials), - userSubResult = AWSCognitoAuthSession.getUserSubResult(signedInData.cognitoUserPoolTokens), - userPoolTokensResult = AWSCognitoAuthSession.getUserPoolTokensResult(signedInData.cognitoUserPoolTokens) + identityIdResult = getIdentityIdResult(identityId), + awsCredentialsResult = getCredentialsResult(credentials), + userSubResult = getUserSubResult(signedInData.cognitoUserPoolTokens), + userPoolTokensResult = getUserPoolTokensResult(signedInData.cognitoUserPoolTokens) ) is AmplifyCredential.IdentityPool -> AWSCognitoAuthSession( false, - identityIdResult = AWSCognitoAuthSession.getIdentityIdResult(identityId), - awsCredentialsResult = AWSCognitoAuthSession.getCredentialsResult(credentials), + identityIdResult = getIdentityIdResult(identityId), + awsCredentialsResult = getCredentialsResult(credentials), userSubResult = AuthSessionResult.failure(SignedOutException()), userPoolTokensResult = AuthSessionResult.failure(SignedOutException()) ) @@ -132,8 +130,8 @@ internal fun AmplifyCredential.getCognitoSession( ) AWSCognitoAuthSession( true, - identityIdResult = AWSCognitoAuthSession.getIdentityIdResult(identityId), - awsCredentialsResult = AWSCognitoAuthSession.getCredentialsResult(credentials), + identityIdResult = getIdentityIdResult(identityId), + awsCredentialsResult = getCredentialsResult(credentials), userSubResult = AuthSessionResult.failure(userPoolException), userPoolTokensResult = AuthSessionResult.failure(userPoolException) ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt index 8d7c3ca8a5..bbb5059580 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt @@ -181,7 +181,7 @@ internal class HostedUIClient private constructor( if (configuration.appSecret != null) { put( "Authorization", - PkceHelper.encodeBase64("${configuration.appClient}:${configuration.appSecret}") + "Basic ${PkceHelper.encodeBase64("${configuration.appClient}:${configuration.appSecret}")}" ) } } 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 60fc2ffcd0..bef2b979ba 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 @@ -588,11 +588,17 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.getCurrentState { authState -> val authNState = authState.authNState val signInState = (authNState as? AuthenticationState.SigningIn)?.signInState - when ((signInState as? SignInState.ResolvingChallenge)?.challengeState) { - is SignInChallengeState.WaitingForAnswer -> { - _confirmSignIn(challengeResponse, options, onSuccess, onError) + if (signInState is SignInState.ResolvingChallenge) { + when (signInState.challengeState) { + is SignInChallengeState.WaitingForAnswer -> { + _confirmSignIn(challengeResponse, options, onSuccess, onError) + } + else -> { + onError.accept(InvalidStateException()) + } } - else -> onError.accept(InvalidStateException()) + } else { + onError.accept(InvalidStateException()) } } } @@ -610,7 +616,6 @@ internal class RealAWSCognitoAuthPlugin( val authNState = authState.authNState val authZState = authState.authZState val signInState = (authNState as? AuthenticationState.SigningIn)?.signInState - val challengeState = (signInState as? SignInState.ResolvingChallenge)?.challengeState when { authNState is AuthenticationState.SignedIn && authZState is AuthorizationState.SessionEstablished -> { @@ -625,21 +630,34 @@ internal class RealAWSCognitoAuthPlugin( signInState is SignInState.Error -> { authStateMachine.cancel(token) onError.accept( - CognitoAuthExceptionConverter.lookup(signInState.exception, "Confirm Sign in failed.") + CognitoAuthExceptionConverter.lookup( + signInState.exception, "Confirm Sign in failed." + ) + ) + } + signInState is SignInState.ResolvingChallenge && + signInState.challengeState is SignInChallengeState.Error -> { + authStateMachine.cancel(token) + onError.accept( + CognitoAuthExceptionConverter.lookup( + ( + signInState.challengeState as SignInChallengeState.Error + ).exception, + "Confirm Sign in failed." + ) ) } } - }, - { - val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions - val event = SignInChallengeEvent( - SignInChallengeEvent.EventType.VerifyChallengeAnswer( - challengeResponse, - awsCognitoConfirmSignInOptions?.metadata ?: mapOf() - ) + }, { + val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions + val event = SignInChallengeEvent( + SignInChallengeEvent.EventType.VerifyChallengeAnswer( + challengeResponse, + awsCognitoConfirmSignInOptions?.metadata ?: mapOf() ) - authStateMachine.send(event) - } + ) + authStateMachine.send(event) + } ) } @@ -1145,7 +1163,10 @@ internal class RealAWSCognitoAuthPlugin( val encodedData = authEnvironment.getUserContextData(username) val pinpointEndpointId = authEnvironment.getPinpointEndpointId() - ResetPasswordUseCase(cognitoIdentityProviderClient, appClient).execute( + ResetPasswordUseCase( + cognitoIdentityProviderClient, appClient, + configuration.userPool?.appClientSecret + ).execute( username, options, encodedData, @@ -1195,6 +1216,11 @@ internal class RealAWSCognitoAuthPlugin( this.username = username this.confirmationCode = confirmationCode password = newPassword + secretHash = AuthHelper.getSecretHash( + username, + configuration.userPool?.appClient, + configuration.userPool?.appClientSecret + ) clientMetadata = (options as? AWSCognitoAuthConfirmResetPasswordOptions)?.metadata ?: mapOf() clientId = configuration.userPool?.appClient diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/AuthorizationCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/AuthorizationCognitoActions.kt index 9c025c0499..dc0e2c2b28 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/AuthorizationCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/AuthorizationCognitoActions.kt @@ -15,6 +15,7 @@ package com.amplifyframework.auth.cognito.actions +import com.amplifyframework.AmplifyException import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.statemachine.Action @@ -75,11 +76,19 @@ internal object AuthorizationCognitoActions : AuthorizationActions { ) ) ) + signedInData.cognitoUserPoolTokens.idToken == null -> AuthorizationEvent( + AuthorizationEvent.EventType.ThrowError( + ConfigurationException( + "Identity token is null.", + AmplifyException.TODO_RECOVERY_SUGGESTION + ) + ) + ) else -> { val logins = LoginsMapProvider.CognitoUserPoolLogins( configuration.userPool.region, configuration.userPool.poolId, - signedInData.cognitoUserPoolTokens.idToken!! + signedInData.cognitoUserPoolTokens.idToken ) FetchAuthSessionEvent(FetchAuthSessionEvent.EventType.FetchIdentity(logins)) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt index ab85b40d49..29598544fd 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -26,7 +26,6 @@ import com.amplifyframework.statemachine.codegen.actions.SignInChallengeActions import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.events.CustomSignInEvent import com.amplifyframework.statemachine.codegen.events.SignInChallengeEvent -import com.amplifyframework.statemachine.codegen.events.SignInEvent internal object SignInChallengeCognitoActions : SignInChallengeActions { private const val KEY_SECRET_HASH = "SECRET_HASH" @@ -81,20 +80,25 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { ) ) } catch (e: Exception) { - SignInEvent(SignInEvent.EventType.ThrowError(e)) + SignInChallengeEvent(SignInChallengeEvent.EventType.ThrowError(e, challenge)) } logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) } + override fun resetToWaitingForAnswer( + event: SignInChallengeEvent.EventType.ThrowError, + challenge: AuthChallenge + ): Action = Action("ResetToWaitingForAnswer") { id, dispatcher -> + logger.verbose("$id Starting execution") + dispatcher.send(SignInChallengeEvent(SignInChallengeEvent.EventType.WaitForAnswer(challenge))) + } + private fun getChallengeResponseKey(challengeName: String): String? { - val VALUE_ANSWER = "ANSWER" - val VALUE_SMS_MFA = "SMS_MFA_CODE" - val VALUE_NEW_PASSWORD = "NEW_PASSWORD" return when (ChallengeNameType.fromValue(challengeName)) { - is ChallengeNameType.SmsMfa -> VALUE_SMS_MFA - is ChallengeNameType.NewPasswordRequired -> VALUE_NEW_PASSWORD - is ChallengeNameType.CustomChallenge -> VALUE_ANSWER + is ChallengeNameType.SmsMfa -> "SMS_MFA_CODE" + is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD" + is ChallengeNameType.CustomChallenge -> "ANSWER" else -> null } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/FlutterFactory.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/FlutterFactory.kt index 093a7c0b7b..fbac942874 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/FlutterFactory.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/FlutterFactory.kt @@ -15,9 +15,9 @@ package com.amplifyframework.auth.cognito.helpers +import com.amplifyframework.auth.AWSCognitoUserPoolTokens import com.amplifyframework.auth.AWSCredentials import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import com.amplifyframework.auth.cognito.AWSCognitoUserPoolTokens import com.amplifyframework.auth.result.AuthSessionResult /** diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt index 6db4d4cb2d..79c01e95b4 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt @@ -23,6 +23,7 @@ import com.amplifyframework.AmplifyException import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthException import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter +import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.options.AWSCognitoAuthResetPasswordOptions import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.result.AuthResetPasswordResult @@ -37,7 +38,8 @@ import kotlinx.coroutines.withContext */ internal class ResetPasswordUseCase( private val cognitoIdentityProviderClient: CognitoIdentityProviderClient, - private val appClientId: String + private val appClientId: String, + private val appClientSecret: String? ) { suspend fun execute( username: String, @@ -53,6 +55,11 @@ internal class ResetPasswordUseCase( this.username = username this.clientMetadata = (options as? AWSCognitoAuthResetPasswordOptions)?.metadata ?: mapOf() this.clientId = appClientId + this.secretHash = AuthHelper.getSecretHash( + username, + appClientId, + appClientSecret + ) encodedContextData?.let { this.userContextData { encodedData = it } } pinpointEndpointId?.let { this.analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = it } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt index dc98ca35f0..e9af419d36 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt @@ -24,4 +24,8 @@ internal interface SignInChallengeActions { event: SignInChallengeEvent.EventType.VerifyChallengeAnswer, challenge: AuthChallenge ): Action + fun resetToWaitingForAnswer( + event: SignInChallengeEvent.EventType.ThrowError, + challenge: AuthChallenge + ): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt index 82d4d8bd4d..8b3f823b6f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt @@ -25,6 +25,7 @@ internal class SignInChallengeEvent(val eventType: EventType, override val time: data class VerifyChallengeAnswer(val answer: String, val metadata: Map) : EventType() data class FinalizeSignIn(val accessToken: String) : EventType() data class Verified(val id: String = "") : EventType() + data class ThrowError(val exception: Exception, val challenge: AuthChallenge) : EventType() } override val type: String = eventType.javaClass.simpleName diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt index c74366ed3d..08417b9c49 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt @@ -28,6 +28,7 @@ internal sealed class SignInChallengeState : State { data class WaitingForAnswer(val challenge: AuthChallenge) : SignInChallengeState() data class Verifying(val id: String = "") : SignInChallengeState() data class Verified(val id: String = "") : SignInChallengeState() + data class Error(val exception: Exception, val challenge: AuthChallenge) : SignInChallengeState() class Resolver(private val challengeActions: SignInChallengeActions) : StateMachineResolver { override val defaultState: SignInChallengeState = NotStarted() @@ -58,8 +59,25 @@ internal sealed class SignInChallengeState : State { } is Verifying -> when (challengeEvent) { is SignInChallengeEvent.EventType.Verified -> StateResolution(Verified()) + is SignInChallengeEvent.EventType.ThrowError -> { + val action = challengeActions.resetToWaitingForAnswer(challengeEvent, challengeEvent.challenge) + StateResolution(Error(challengeEvent.exception, challengeEvent.challenge), listOf(action)) + } + else -> defaultResolution } + is Error -> { + when (challengeEvent) { + is SignInChallengeEvent.EventType.VerifyChallengeAnswer -> { + val action = challengeActions.verifyChallengeAuthAction(challengeEvent, oldState.challenge) + StateResolution(Verifying(oldState.challenge.challengeName), listOf(action)) + } + is SignInChallengeEvent.EventType.WaitForAnswer -> { + StateResolution(WaitingForAnswer(challengeEvent.challenge)) + } + else -> defaultResolution + } + } else -> defaultResolution } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index 41a81f4018..5fecde353d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -149,6 +149,10 @@ internal sealed class SignInState : State { val action = signInActions.confirmDevice(signInEvent) StateResolution(ConfirmingDevice(), listOf(action)) } + is SignInEvent.EventType.ReceivedChallenge -> { + val action = signInActions.initResolveChallenge(signInEvent) + StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action)) + } is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) else -> defaultResolution } 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 d0bca74d15..2db28e2a71 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 @@ -109,9 +109,11 @@ class RealAWSCognitoAuthPluginTest { 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 } } @@ -532,6 +534,11 @@ class RealAWSCognitoAuthPluginTest { confirmationCode = code clientMetadata = mapOf() clientId = appClientId + secretHash = AuthHelper.getSecretHash( + username, + appClientId, + appClientSecret + ) userContextData = null analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = expectedEndpointId } } 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 837dd9116e..7a7910613f 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 @@ -20,6 +20,10 @@ import com.amplifyframework.auth.cognito.featuretest.generators.authstategenerat 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.FetchDevicesTestCaseGenerator +import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.FetchUserAttributesTestCaseGenerator +import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.ForgetDeviceTestCaseGenerator +import com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators.RememberDeviceTestCaseGenerator 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 @@ -43,6 +47,10 @@ object JsonGenerator { ConfirmSignInTestCaseGenerator, DeleteUserTestCaseGenerator, FetchAuthSessionTestCaseGenerator, + RememberDeviceTestCaseGenerator, + ForgetDeviceTestCaseGenerator, + FetchDevicesTestCaseGenerator, + FetchUserAttributesTestCaseGenerator, ) fun generate() { diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ConfirmSignInTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ConfirmSignInTestCaseGenerator.kt index c73c2b2daa..ea60d8a51e 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ConfirmSignInTestCaseGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ConfirmSignInTestCaseGenerator.kt @@ -15,6 +15,8 @@ package com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException +import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter import com.amplifyframework.auth.cognito.featuretest.API import com.amplifyframework.auth.cognito.featuretest.AuthAPI import com.amplifyframework.auth.cognito.featuretest.CognitoType @@ -102,5 +104,37 @@ object ConfirmSignInTestCaseGenerator : SerializableProvider { ) ) - override val serializables: List = listOf(baseCase) + private val errorCase: FeatureTestCase + get() { + val exception = CodeMismatchException.invoke { + message = "Confirmation code entered is not correct." + } + return baseCase.copy( + description = "Test that invalid code on confirm SignIn with SMS challenge errors out", + preConditions = PreConditions( + "authconfiguration.json", + "SigningIn_SigningIn.json", + mockedResponses = listOf( + MockResponse( + CognitoType.CognitoIdentityProvider, + "respondToAuthChallenge", + ResponseType.Failure, + exception.toJsonElement() + ) + ) + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.confirmSignIn, + ResponseType.Failure, + CognitoAuthExceptionConverter.lookup( + exception, + "Confirm Sign in failed." + ).toJsonElement() + ) + ) + ) + } + + override val serializables: List = listOf(baseCase, errorCase) } 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 02c413dac3..387bc06560 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 @@ -1,8 +1,23 @@ +/* + * Copyright 2023 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.featuretest.generators.testcasegenerators +import com.amplifyframework.auth.AWSCognitoUserPoolTokens 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 @@ -37,7 +52,7 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { private val expectedSuccess = AWSCognitoAuthSession( isSignedIn = true, - identityIdResult = AWSCognitoAuthSession.getIdentityIdResult("someIdentityId"), + identityIdResult = AuthSessionResult.success("someIdentityId"), awsCredentialsResult = AuthSessionResult.success( AWSCredentials.createAWSCredentials( AuthStateJsonGenerator.accessKeyId, @@ -102,7 +117,7 @@ object FetchAuthSessionTestCaseGenerator : SerializableProvider { ResponseType.Success, AWSCognitoAuthSession( isSignedIn = false, - identityIdResult = AWSCognitoAuthSession.getIdentityIdResult("someIdentityId"), + identityIdResult = AuthSessionResult.success("someIdentityId"), awsCredentialsResult = AuthSessionResult.success( AWSCredentials.createAWSCredentials( AuthStateJsonGenerator.accessKeyId, diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchDevicesTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchDevicesTestCaseGenerator.kt new file mode 100644 index 0000000000..f87c12db89 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchDevicesTestCaseGenerator.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2023 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.featuretest.generators.testcasegenerators + +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceType +import aws.smithy.kotlin.runtime.time.Instant +import com.amplifyframework.auth.AuthDevice +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.toJsonElement +import com.amplifyframework.auth.exceptions.SignedOutException +import kotlinx.serialization.json.JsonObject + +object FetchDevicesTestCaseGenerator : SerializableProvider { + + private val expectedSuccess = listOf(AuthDevice.fromId("deviceKey")).toJsonElement() + + private val mockCognitoResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "listDevices", + ResponseType.Success, + mapOf( + "devices" to listOf( + DeviceType.invoke { + deviceAttributes = listOf( + AttributeType.invoke { + name = "name" + value = "value" + } + ) + deviceKey = "deviceKey" + deviceCreateDate = Instant.now() + deviceLastAuthenticatedDate = Instant.now() + deviceLastModifiedDate = Instant.now() + } + ) + ).toJsonElement() + ) + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.fetchDevices, + ResponseType.Success, + expectedSuccess, + ) + + private val baseCase = FeatureTestCase( + description = "Test that Cognito is called with given payload and returns successful data", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf(mockCognitoResponse) + ), + api = API( + AuthAPI.fetchDevices, + JsonObject(emptyMap()), + JsonObject(emptyMap()), + ), + validations = listOf(apiReturnValidation) + ) + + private val successCase: FeatureTestCase = baseCase.copy( + description = "List of devices returned when fetch devices API succeeds", + preConditions = baseCase.preConditions.copy(mockedResponses = listOf(mockCognitoResponse)), + validations = baseCase.validations.plus(apiReturnValidation) + ) + + private val errorCase: FeatureTestCase + get() { + val errorResponse = SignedOutException() + return baseCase.copy( + description = "AuthException is thrown when forgetDevice API is called without signing in", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_Configured.json", + mockedResponses = listOf( + MockResponse( + CognitoType.CognitoIdentityProvider, + "forgetDevice", + ResponseType.Failure, + errorResponse.toJsonElement() + ) + ) + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.forgetDevice, + ResponseType.Failure, + com.amplifyframework.auth.exceptions.SignedOutException().toJsonElement(), + ) + ) + ) + } + + override val serializables: List = listOf(baseCase, errorCase, successCase) +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchUserAttributesTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchUserAttributesTestCaseGenerator.kt new file mode 100644 index 0000000000..1688bf9b50 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/FetchUserAttributesTestCaseGenerator.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2023 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.featuretest.generators.testcasegenerators +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType +import com.amplifyframework.auth.AuthUserAttribute +import com.amplifyframework.auth.AuthUserAttributeKey +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.toJsonElement +import com.amplifyframework.auth.exceptions.SignedOutException +import kotlinx.serialization.json.JsonObject + +object FetchUserAttributesTestCaseGenerator : SerializableProvider { + + private val expectedSuccess = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"), + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "000-000-0000") + ).toJsonElement() + + private val mockCognitoResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "getUser", + ResponseType.Success, + mapOf( + "userAttributes" to listOf( + AttributeType.invoke { + name = "email" + value = "email@email.com" + }, + AttributeType.invoke { + name = "phone" + value = "000-000-0000" + } + ) + ).toJsonElement() + ) + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.fetchUserAttributes, + ResponseType.Success, + expectedSuccess, + ) + + private val baseCase = FeatureTestCase( + description = "Test that Cognito is called with given payload and returns successful data", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf(mockCognitoResponse) + ), + api = API( + AuthAPI.fetchUserAttributes, + JsonObject(emptyMap()), + JsonObject(emptyMap()), + ), + validations = listOf(apiReturnValidation) + ) + + private val successCase: FeatureTestCase = baseCase.copy( + description = "List of user attributes returned when fetch user attributes API succeeds", + preConditions = baseCase.preConditions.copy(mockedResponses = listOf(mockCognitoResponse)), + validations = baseCase.validations.plus(apiReturnValidation) + ) + + private val errorCase: FeatureTestCase + get() { + val errorResponse = SignedOutException() + return baseCase.copy( + description = "AuthException is thrown when fetchUserAttributes API is called without signing in", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_Configured.json", + mockedResponses = listOf( + MockResponse( + CognitoType.CognitoIdentityProvider, + "getUser", + ResponseType.Failure, + errorResponse.toJsonElement() + ) + ) + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.fetchUserAttributes, + ResponseType.Failure, + SignedOutException().toJsonElement(), + ) + ) + ) + } + + override val serializables: List = listOf(baseCase, errorCase, successCase) +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ForgetDeviceTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ForgetDeviceTestCaseGenerator.kt new file mode 100644 index 0000000000..52151d2322 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ForgetDeviceTestCaseGenerator.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2023 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.featuretest.generators.testcasegenerators + +import com.amplifyframework.auth.AuthDevice +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.toJsonElement +import com.amplifyframework.auth.exceptions.SignedOutException +import kotlinx.serialization.json.JsonObject + +object ForgetDeviceTestCaseGenerator : SerializableProvider { + private val mockCognitoResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "updateDeviceStatus", + ResponseType.Success, + JsonObject(emptyMap()) + ) + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.forgetDevice, + ResponseType.Success, + JsonObject(emptyMap()), + ) + + private val baseCase = FeatureTestCase( + description = "Test that Cognito is called with given payload and returns successful data", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf(mockCognitoResponse) + ), + api = API( + AuthAPI.forgetDevice, + mapOf( + "device" to AuthDevice.fromId("id", "test") + ).toJsonElement(), + JsonObject(emptyMap()), + ), + validations = listOf(apiReturnValidation) + ) + + private val successCase: FeatureTestCase = baseCase.copy( + description = "Nothing is returned when forget device succeeds", + preConditions = baseCase.preConditions.copy(mockedResponses = listOf(mockCognitoResponse)), + validations = baseCase.validations.plus(apiReturnValidation) + ) + + private val errorCase: FeatureTestCase + get() { + val errorResponse = SignedOutException() + return baseCase.copy( + description = "AuthException is thrown when forgetDevice API is called without signing in", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_Configured.json", + mockedResponses = listOf( + MockResponse( + CognitoType.CognitoIdentityProvider, + "forgetDevice", + ResponseType.Failure, + errorResponse.toJsonElement() + ) + ) + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.forgetDevice, + ResponseType.Failure, + SignedOutException().toJsonElement(), + ) + ) + ) + } + + override val serializables: List = listOf(baseCase, errorCase, successCase) +} 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 index 7eebb704b1..36c8dfbb12 100644 --- 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 @@ -1,3 +1,18 @@ +/* + * Copyright 2023 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.featuretest.generators.testcasegenerators import aws.sdk.kotlin.services.cognitoidentityprovider.model.NotAuthorizedException diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/RememberDeviceTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/RememberDeviceTestCaseGenerator.kt new file mode 100644 index 0000000000..377d787195 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/RememberDeviceTestCaseGenerator.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2023 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.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 +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.toJsonElement +import kotlinx.serialization.json.JsonObject + +object RememberDeviceTestCaseGenerator : SerializableProvider { + private val mockCognitoResponse = MockResponse( + CognitoType.CognitoIdentityProvider, + "updateDeviceStatus", + ResponseType.Success, + JsonObject(emptyMap()) + ) + + private val apiReturnValidation = ExpectationShapes.Amplify( + AuthAPI.rememberDevice, + ResponseType.Success, + JsonObject(emptyMap()), + ) + + private val baseCase = FeatureTestCase( + description = "Test that Cognito is called with given payload and returns successful data", + preConditions = PreConditions( + "authconfiguration.json", + "SignedIn_SessionEstablished.json", + mockedResponses = listOf(mockCognitoResponse) + ), + api = API( + AuthAPI.rememberDevice, + JsonObject(emptyMap()), + JsonObject(emptyMap()) + ), + validations = listOf(apiReturnValidation) + ) + + private val successCase: FeatureTestCase = baseCase.copy( + description = "Nothing is returned when remember device succeeds", + preConditions = baseCase.preConditions.copy(mockedResponses = listOf(mockCognitoResponse)), + validations = baseCase.validations.plus(apiReturnValidation) + ) + + private val errorCase: FeatureTestCase + get() { + val errorResponse = NotAuthorizedException.invoke {} + return baseCase.copy( + description = "AuthException is thrown when rememberDevice API is called without signing in", + preConditions = baseCase.preConditions.copy( + state = "SignedOut_Configured.json" + ), + validations = listOf( + ExpectationShapes.Amplify( + AuthAPI.rememberDevice, + ResponseType.Failure, + com.amplifyframework.auth.exceptions.SignedOutException().toJsonElement(), + ) + ) + ) + } + + override val serializables: List = listOf(baseCase, errorCase, successCase) +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ResetPasswordTestCaseGenerator.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ResetPasswordTestCaseGenerator.kt index a371a924ed..a9034e9453 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ResetPasswordTestCaseGenerator.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ResetPasswordTestCaseGenerator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -64,6 +64,7 @@ object ResetPasswordTestCaseGenerator : SerializableProvider { mapOf( "username" to "someUsername", "clientId" to "testAppClientId", + "secretHash" to "a hash", "clientMetadata" to emptyMap() ).toJsonElement() ) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt index 390193bc5a..f3415d6609 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt @@ -177,6 +177,7 @@ internal data class AuthStatesProxy( type = "SignInChallengeState.WaitingForAnswer", authChallenge = authState.challenge ) + is SignInChallengeState.Error -> TODO() } } else -> { diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/CognitoExceptionSerializers.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/CognitoExceptionSerializers.kt index d39029186f..0ba26b2a2f 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/CognitoExceptionSerializers.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/CognitoExceptionSerializers.kt @@ -18,8 +18,10 @@ package com.amplifyframework.auth.cognito.featuretest.serializers import aws.sdk.kotlin.services.cognitoidentity.model.CognitoIdentityException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException import aws.sdk.kotlin.services.cognitoidentityprovider.model.NotAuthorizedException +import com.amplifyframework.auth.exceptions.UnknownException import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor @@ -32,14 +34,19 @@ private data class CognitoExceptionSurrogate( val errorMessage: String? ) { fun toRealException(): T { - return when (errorType) { + val exception = when (errorType) { NotAuthorizedException::class.java.simpleName -> NotAuthorizedException.invoke { message = errorMessage } as T + UnknownException::class.java.simpleName -> UnknownException(message = errorMessage ?: "") as T + CodeMismatchException::class.java.simpleName -> CodeMismatchException.invoke { + message = errorMessage + } as T else -> { error("Exception for $errorType not defined") } } + return exception } companion object { diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt index 413399b4c1..2d20ba9773 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt @@ -23,6 +23,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgotPasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgotPasswordResponse import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep @@ -50,6 +51,7 @@ import org.junit.Test class ResetPasswordUseCaseTest { private val dummyClientId = "app client id" + private val dummyAppClientSecret = "app client secret" private val dummyUserName = "username" private val expectedPinpointEndpointId = "abc123" @@ -64,7 +66,7 @@ class ResetPasswordUseCaseTest { @Before fun setUp() { Dispatchers.setMain(mainThreadSurrogate) - resetPasswordUseCase = ResetPasswordUseCase(mockCognitoIPClient, dummyClientId) + resetPasswordUseCase = ResetPasswordUseCase(mockCognitoIPClient, dummyClientId, dummyAppClientSecret) } @After @@ -82,6 +84,11 @@ class ResetPasswordUseCaseTest { username = dummyUserName clientMetadata = mapOf() clientId = dummyClientId + secretHash = AuthHelper.getSecretHash( + username, + dummyClientId, + dummyAppClientSecret + ) analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = expectedPinpointEndpointId } } diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt index a7a625de8b..8fd7d2c25f 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/APICaptorFactory.kt @@ -47,7 +47,9 @@ class APICaptorFactory( AuthAPI.signIn to mockk>(), AuthAPI.deleteUser to mockk(), AuthAPI.fetchAuthSession to mockk(), - AuthAPI.getCurrentUser to mockk() + AuthAPI.getCurrentUser to mockk(), + AuthAPI.rememberDevice to mockk(), + AuthAPI.forgetDevice to mockk() ) val onError = mockk>() val onComplete = mapOf( @@ -105,6 +107,26 @@ class APICaptorFactory( every { consumer.call() } answers { latch.countDown() } successCaptors[apiName] = actionCaptor } + AuthAPI.rememberDevice -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + AuthAPI.forgetDevice -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + AuthAPI.fetchDevices -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } + AuthAPI.fetchUserAttributes -> { + val consumer = onSuccess[apiName] as Action + every { consumer.call() } answers { latch.countDown() } + successCaptors[apiName] = actionCaptor + } else -> throw Error("onSuccess for $authApi is not defined!") } } diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/APIExecutor.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/APIExecutor.kt index 32ebde1094..0922ce4f74 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/APIExecutor.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/APIExecutor.kt @@ -23,6 +23,7 @@ import com.google.gson.Gson import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.reflect.KClass +import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.full.declaredFunctions import kotlinx.serialization.json.JsonObject @@ -34,25 +35,37 @@ internal val apiExecutor: (AWSCognitoAuthPlugin, API) -> Any = { authPlugin: AWS lateinit var result: Any val latch = CountDownLatch(1) + val targetApis = authPlugin::class.declaredFunctions.filter { it.name == api.name.name } - val targetApi = authPlugin::class.declaredFunctions.first { it.name == api.name.name } - - val requiredParams = targetApi.parameters.associateWith { kParam -> - when { - kParam.kind == KParameter.Kind.INSTANCE -> authPlugin - kParam.type.classifier as KClass<*> == Action::class -> Action { - result = Unit - latch.countDown() - } - kParam.type.classifier as KClass<*> == Consumer::class -> Consumer { value -> - result = value - latch.countDown() + var requiredParams: Map? = null + var targetApi: KFunction<*>? = null + for (currentApi in targetApis) { + try { + val currentParams = currentApi.parameters.associateWith { kParam -> + when { + kParam.kind == KParameter.Kind.INSTANCE -> authPlugin + kParam.type.classifier as KClass<*> == Action::class -> Action { + result = Unit + latch.countDown() + } + kParam.type.classifier as KClass<*> == Consumer::class -> Consumer { value -> + result = value + latch.countDown() + } + kParam.name == "options" -> AuthOptionsFactory.create(api.name, api.options as JsonObject) + else -> kParam.name?.let { getParam(it, kParam, api.params as JsonObject) } + } } - kParam.name == "options" -> AuthOptionsFactory.create(api.name, api.options as JsonObject) - else -> kParam.name?.let { getParam(it, api.params as JsonObject) } + targetApi = currentApi + requiredParams = currentParams + break + } catch (ex: Exception) { + print(ex.toString()) } } + if (targetApi == null || requiredParams == null) + throw Exception("No matching api function with required parameters found") targetApi.callBy(requiredParams) latch.await(5, TimeUnit.SECONDS) @@ -62,10 +75,10 @@ internal val apiExecutor: (AWSCognitoAuthPlugin, API) -> Any = { authPlugin: AWS /** * Traverses given json to find value of paramName */ -private inline fun getParam(paramName: String, paramsObject: Map): T { +private inline fun getParam(paramName: String, kParam: KParameter, paramsObject: Map): kotlin.Any { paramsObject.entries.first { it.key == paramName }.apply { - return Gson().fromJson(value.toString(), T::class.java) + return Gson().fromJson(value.toString(), (kParam.type.classifier as KClass<*>).javaObjectType) } } 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 0c386a6690..787c8390a6 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/AuthOptionsFactory.kt @@ -55,10 +55,10 @@ object AuthOptionsFactory { AuthAPI.fetchAuthSession -> getFetchAuthSessionOptions(optionsData) AuthAPI.fetchDevices -> null AuthAPI.fetchUserAttributes -> TODO() - AuthAPI.forgetDevice -> TODO() + AuthAPI.forgetDevice -> null AuthAPI.getCurrentUser -> null AuthAPI.handleWebUISignInResponse -> TODO() - AuthAPI.rememberDevice -> TODO() + AuthAPI.rememberDevice -> null AuthAPI.resendSignUpCode -> AuthResendSignUpCodeOptions.defaults() AuthAPI.resendUserAttributeConfirmationCode -> AuthResendUserAttributeConfirmationCodeOptions.defaults() signIn -> getSignInOptions(optionsData) diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoMockFactory.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoMockFactory.kt index de657cb440..9a6dfeabae 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoMockFactory.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoMockFactory.kt @@ -20,18 +20,24 @@ import aws.sdk.kotlin.services.cognitoidentity.model.Credentials import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityResponse import aws.sdk.kotlin.services.cognitoidentity.model.GetIdResponse import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResultType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmDeviceResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeleteUserResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgetDeviceResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgotPasswordResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.GlobalSignOutResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.InitiateAuthResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ListDevicesResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.RevokeTokenResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.SignUpResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateDeviceStatusResponse import aws.smithy.kotlin.runtime.time.Instant import com.amplifyframework.auth.cognito.featuretest.CognitoType import com.amplifyframework.auth.cognito.featuretest.MockResponse @@ -119,6 +125,23 @@ class CognitoMockFactory( } } } + "getUser" -> { + coEvery { mockCognitoIPClient.getUser(any()) } coAnswers { + setupError(mockResponse, responseObject) + GetUserResponse.invoke { + userAttributes = listOf( + AttributeType.invoke { + name = "email" + value = "email@email.com" + }, + AttributeType.invoke { + name = "phone_number" + value = "000-000-0000" + } + ) + } + } + } "getCredentialsForIdentity" -> { coEvery { mockCognitoIdClient.getCredentialsForIdentity(any()) } coAnswers { setupError(mockResponse, responseObject) @@ -145,6 +168,39 @@ class CognitoMockFactory( GlobalSignOutResponse.invoke {} } } + "updateDeviceStatus" -> { + coEvery { mockCognitoIPClient.updateDeviceStatus(any()) } coAnswers { + setupError(mockResponse, responseObject) + UpdateDeviceStatusResponse.invoke { } + } + } + "forgetDevice" -> { + coEvery { mockCognitoIPClient.forgetDevice(any()) } coAnswers { + setupError(mockResponse, responseObject) + ForgetDeviceResponse.invoke {} + } + } + "listDevices" -> { + coEvery { mockCognitoIPClient.listDevices(any()) } coAnswers { + setupError(mockResponse, responseObject) + ListDevicesResponse.invoke { + devices = listOf( + DeviceType.invoke { + deviceAttributes = listOf( + AttributeType.invoke { + name = "name" + value = "value" + } + ) + deviceKey = "deviceKey" + deviceCreateDate = Instant.now() + deviceLastAuthenticatedDate = Instant.now() + deviceLastModifiedDate = Instant.now() + } + ) + } + } + } else -> throw Error("mock for ${mockResponse.apiName} not defined!") } } @@ -154,13 +210,14 @@ class CognitoMockFactory( responseObject: JsonObject ) { if (mockResponse.responseType == ResponseType.Failure) { - throw Json.decodeFromString( + val response = Json.decodeFromString( when (mockResponse.type) { CognitoType.CognitoIdentity -> CognitoIdentityExceptionSerializer CognitoType.CognitoIdentityProvider -> CognitoIdentityProviderExceptionSerializer }, responseObject.toString() ) + throw response } } diff --git a/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoRequestFactory.kt b/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoRequestFactory.kt index 11208c0326..7a6747c297 100644 --- a/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoRequestFactory.kt +++ b/aws-auth-cognito/src/test/java/featureTest/utilities/CognitoRequestFactory.kt @@ -38,6 +38,7 @@ object CognitoRequestFactory { clientMetadata = Json.decodeFromJsonElement>(params["clientMetadata"] as JsonObject) clientId = (params["clientId"] as JsonPrimitive).content + secretHash = (params["secretHash"] as JsonPrimitive).content } ForgotPasswordRequest.invoke(expectedRequestBuilder) diff --git a/aws-auth-cognito/src/test/resources/feature-test/states/CustomSignIn_SigningIn.json b/aws-auth-cognito/src/test/resources/feature-test/states/CustomSignIn_SigningIn.json index 5ccb17e359..e69e8f6662 100644 --- a/aws-auth-cognito/src/test/resources/feature-test/states/CustomSignIn_SigningIn.json +++ b/aws-auth-cognito/src/test/resources/feature-test/states/CustomSignIn_SigningIn.json @@ -9,7 +9,7 @@ "authChallenge": { "challengeName": "CUSTOM_CHALLENGE", "username": "username", - "session": "someSession", + "session": null, "parameters": { "SALT": "abc", "SECRET_BLOCK": "secretBlock", diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/confirmSignIn/Test_that_invalid_code_on_confirm_SignIn_with_SMS_challenge_errors_out.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/confirmSignIn/Test_that_invalid_code_on_confirm_SignIn_with_SMS_challenge_errors_out.json new file mode 100644 index 0000000000..224514edc7 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/confirmSignIn/Test_that_invalid_code_on_confirm_SignIn_with_SMS_challenge_errors_out.json @@ -0,0 +1,42 @@ +{ + "description": "Test that invalid code on confirm SignIn with SMS challenge errors out", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SigningIn_SigningIn.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "respondToAuthChallenge", + "responseType": "failure", + "response": { + "errorType": "CodeMismatchException", + "errorMessage": "Confirmation code entered is not correct." + } + } + ] + }, + "api": { + "name": "confirmSignIn", + "params": { + "challengeResponse": "000000" + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "confirmSignIn", + "responseType": "failure", + "response": { + "errorType": "CodeMismatchException", + "errorMessage": "Confirmation code entered is not correct.", + "recoverySuggestion": "Enter correct confirmation code.", + "cause": { + "errorType": "CodeMismatchException", + "errorMessage": "Confirmation code entered is not correct." + } + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json new file mode 100644 index 0000000000..7f79fddd9f --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json @@ -0,0 +1,40 @@ +{ + "description": "AuthException is thrown when forgetDevice API is called without signing in", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedOut_Configured.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "forgetDevice", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] + }, + "api": { + "name": "fetchDevices", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "forgetDevice", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/List_of_devices_returned_when_fetch_devices_API_succeeds.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/List_of_devices_returned_when_fetch_devices_API_succeeds.json new file mode 100644 index 0000000000..8941526967 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/List_of_devices_returned_when_fetch_devices_API_succeeds.json @@ -0,0 +1,74 @@ +{ + "description": "List of devices returned when fetch devices API succeeds", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "listDevices", + "responseType": "success", + "response": { + "devices": [ + { + "deviceAttributes": [ + { + "name": "name", + "value": "value" + } + ], + "deviceCreateDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65001E8 + } + }, + "deviceKey": "deviceKey", + "deviceLastAuthenticatedDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65007E8 + } + }, + "deviceLastModifiedDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65008E8 + } + } + } + ] + } + } + ] + }, + "api": { + "name": "fetchDevices", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchDevices", + "responseType": "success", + "response": [ + { + "id": "deviceKey" + } + ] + }, + { + "type": "amplify", + "apiName": "fetchDevices", + "responseType": "success", + "response": [ + { + "id": "deviceKey" + } + ] + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json new file mode 100644 index 0000000000..f81f77f531 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json @@ -0,0 +1,64 @@ +{ + "description": "Test that Cognito is called with given payload and returns successful data", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "listDevices", + "responseType": "success", + "response": { + "devices": [ + { + "deviceAttributes": [ + { + "name": "name", + "value": "value" + } + ], + "deviceCreateDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65001E8 + } + }, + "deviceKey": "deviceKey", + "deviceLastAuthenticatedDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65007E8 + } + }, + "deviceLastModifiedDate": { + "value": { + "seconds": 1.676485556E9, + "nanos": 7.65008E8 + } + } + } + ] + } + } + ] + }, + "api": { + "name": "fetchDevices", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchDevices", + "responseType": "success", + "response": [ + { + "id": "deviceKey" + } + ] + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/AuthException_is_thrown_when_fetchUserAttributes_API_is_called_without_signing_in.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/AuthException_is_thrown_when_fetchUserAttributes_API_is_called_without_signing_in.json new file mode 100644 index 0000000000..3fae2bff15 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/AuthException_is_thrown_when_fetchUserAttributes_API_is_called_without_signing_in.json @@ -0,0 +1,40 @@ +{ + "description": "AuthException is thrown when fetchUserAttributes API is called without signing in", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedOut_Configured.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "getUser", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] + }, + "api": { + "name": "fetchUserAttributes", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchUserAttributes", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/List_of_user_attributes_returned_when_fetch_user_attributes_API_succeeds.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/List_of_user_attributes_returned_when_fetch_user_attributes_API_succeeds.json new file mode 100644 index 0000000000..f29bc29423 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/List_of_user_attributes_returned_when_fetch_user_attributes_API_succeeds.json @@ -0,0 +1,73 @@ +{ + "description": "List of user attributes returned when fetch user attributes API succeeds", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "getUser", + "responseType": "success", + "response": { + "userAttributes": [ + { + "name": "email", + "value": "email@email.com" + }, + { + "name": "phone", + "value": "000-000-0000" + } + ] + } + } + ] + }, + "api": { + "name": "fetchUserAttributes", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchUserAttributes", + "responseType": "success", + "response": [ + { + "key": { + "attributeKey": "email" + }, + "value": "email@email.com" + }, + { + "key": { + "attributeKey": "phone_number" + }, + "value": "000-000-0000" + } + ] + }, + { + "type": "amplify", + "apiName": "fetchUserAttributes", + "responseType": "success", + "response": [ + { + "key": { + "attributeKey": "email" + }, + "value": "email@email.com" + }, + { + "key": { + "attributeKey": "phone_number" + }, + "value": "000-000-0000" + } + ] + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json new file mode 100644 index 0000000000..0dcda9da81 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchUserAttributes/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json @@ -0,0 +1,54 @@ +{ + "description": "Test that Cognito is called with given payload and returns successful data", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "getUser", + "responseType": "success", + "response": { + "userAttributes": [ + { + "name": "email", + "value": "email@email.com" + }, + { + "name": "phone", + "value": "000-000-0000" + } + ] + } + } + ] + }, + "api": { + "name": "fetchUserAttributes", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "fetchUserAttributes", + "responseType": "success", + "response": [ + { + "key": { + "attributeKey": "email" + }, + "value": "email@email.com" + }, + { + "key": { + "attributeKey": "phone_number" + }, + "value": "000-000-0000" + } + ] + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json new file mode 100644 index 0000000000..8b9afeaafd --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/AuthException_is_thrown_when_forgetDevice_API_is_called_without_signing_in.json @@ -0,0 +1,44 @@ +{ + "description": "AuthException is thrown when forgetDevice API is called without signing in", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedOut_Configured.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "forgetDevice", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] + }, + "api": { + "name": "forgetDevice", + "params": { + "device": { + "id": "id", + "name": "test" + } + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "forgetDevice", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Nothing_is_returned_when_forget_device_succeeds.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Nothing_is_returned_when_forget_device_succeeds.json new file mode 100644 index 0000000000..3d8498f25f --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Nothing_is_returned_when_forget_device_succeeds.json @@ -0,0 +1,43 @@ +{ + "description": "Nothing is returned when forget device succeeds", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "updateDeviceStatus", + "responseType": "success", + "response": { + } + } + ] + }, + "api": { + "name": "forgetDevice", + "params": { + "device": { + "id": "id", + "name": "test" + } + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "forgetDevice", + "responseType": "success", + "response": { + } + }, + { + "type": "amplify", + "apiName": "forgetDevice", + "responseType": "success", + "response": { + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json new file mode 100644 index 0000000000..6e3febaf04 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/forgetDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json @@ -0,0 +1,36 @@ +{ + "description": "Test that Cognito is called with given payload and returns successful data", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "updateDeviceStatus", + "responseType": "success", + "response": { + } + } + ] + }, + "api": { + "name": "forgetDevice", + "params": { + "device": { + "id": "id", + "name": "test" + } + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "forgetDevice", + "responseType": "success", + "response": { + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/AuthException_is_thrown_when_rememberDevice_API_is_called_without_signing_in.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/AuthException_is_thrown_when_rememberDevice_API_is_called_without_signing_in.json new file mode 100644 index 0000000000..012c0b52dd --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/AuthException_is_thrown_when_rememberDevice_API_is_called_without_signing_in.json @@ -0,0 +1,36 @@ +{ + "description": "AuthException is thrown when rememberDevice API is called without signing in", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedOut_Configured.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "updateDeviceStatus", + "responseType": "success", + "response": { + } + } + ] + }, + "api": { + "name": "rememberDevice", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "rememberDevice", + "responseType": "failure", + "response": { + "errorType": "SignedOutException", + "errorMessage": "You are currently signed out.", + "recoverySuggestion": "Please sign in and reattempt the operation.", + "cause": null + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Nothing_is_returned_when_remember_device_succeeds.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Nothing_is_returned_when_remember_device_succeeds.json new file mode 100644 index 0000000000..eba6010449 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Nothing_is_returned_when_remember_device_succeeds.json @@ -0,0 +1,39 @@ +{ + "description": "Nothing is returned when remember device succeeds", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "updateDeviceStatus", + "responseType": "success", + "response": { + } + } + ] + }, + "api": { + "name": "rememberDevice", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "rememberDevice", + "responseType": "success", + "response": { + } + }, + { + "type": "amplify", + "apiName": "rememberDevice", + "responseType": "success", + "response": { + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json new file mode 100644 index 0000000000..74ccf562f8 --- /dev/null +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/rememberDevice/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json @@ -0,0 +1,32 @@ +{ + "description": "Test that Cognito is called with given payload and returns successful data", + "preConditions": { + "amplify-configuration": "authconfiguration.json", + "state": "SignedIn_SessionEstablished.json", + "mockedResponses": [ + { + "type": "cognitoIdentityProvider", + "apiName": "updateDeviceStatus", + "responseType": "success", + "response": { + } + } + ] + }, + "api": { + "name": "rememberDevice", + "params": { + }, + "options": { + } + }, + "validations": [ + { + "type": "amplify", + "apiName": "rememberDevice", + "responseType": "success", + "response": { + } + } + ] +} \ No newline at end of file diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/AuthResetPasswordResult_object_is_returned_when_reset_password_succeeds.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/AuthResetPasswordResult_object_is_returned_when_reset_password_succeeds.json index 065a1c6386..a7f880c4cf 100644 --- a/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/AuthResetPasswordResult_object_is_returned_when_reset_password_succeeds.json +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/AuthResetPasswordResult_object_is_returned_when_reset_password_succeeds.json @@ -33,6 +33,7 @@ "request": { "username": "someUsername", "clientId": "testAppClientId", + "secretHash": "a hash", "clientMetadata": { } } diff --git a/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json b/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json index 633afb23f5..0d564051e3 100644 --- a/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json +++ b/aws-auth-cognito/src/test/resources/feature-test/testsuites/resetPassword/Test_that_Cognito_is_called_with_given_payload_and_returns_successful_data.json @@ -21,6 +21,7 @@ "request": { "username": "someUsername", "clientId": "testAppClientId", + "secretHash": "a hash", "clientMetadata": { } } diff --git a/aws-datastore/build.gradle b/aws-datastore/build.gradle deleted file mode 100644 index 359bfddd75..0000000000 --- a/aws-datastore/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -dependencies { - implementation project(':core') - implementation project(':aws-api-appsync') - - implementation dependency.androidx.appcompat - implementation dependency.androidx.multidex - implementation dependency.gson - implementation dependency.rxjava - implementation dependency.rxandroid - implementation dependency.uuidgen - - testImplementation project(path: ':testmodels') - testImplementation project(path: ':testutils') - testImplementation project(path: ':aws-datastore') - testImplementation dependency.jsonassert - testImplementation dependency.junit - testImplementation dependency.mockito - testImplementation dependency.robolectric - testImplementation dependency.androidx.test.core - testImplementation dependency.mockk - testImplementation project(path: ':aws-datastore') - - - androidTestImplementation project(path: ':aws-datastore') - androidTestImplementation dependency.mockito - androidTestImplementation project(path: ':testmodels') - androidTestImplementation project(path: ':testutils') - androidTestImplementation project(path: ':aws-api') - androidTestImplementation project(path: ':aws-auth-cognito') - androidTestImplementation project(path: ':aws-datastore') - androidTestImplementation dependency.androidx.annotation - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit - androidTestImplementation dependency.rxjava - androidTestImplementation dependency.okhttp - androidTestImplementation dependency.oauth2 -} - -afterEvaluate { - it.android.kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() -} \ No newline at end of file diff --git a/aws-datastore/build.gradle.kts b/aws-datastore/build.gradle.kts new file mode 100644 index 0000000000..38a8e73535 --- /dev/null +++ b/aws-datastore/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(project(":aws-api-appsync")) + + implementation(dependency.androidx.appcompat) + implementation(dependency.gson) + implementation(dependency.rxjava) + implementation(dependency.rxandroid) + implementation(dependency.uuidgen) + + testImplementation(project(":testmodels")) + testImplementation(project(":testutils")) + testImplementation(testDependency.jsonassert) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) + testImplementation(testDependency.robolectric) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.mockk) + + androidTestImplementation(testDependency.mockito) + androidTestImplementation(project(":testmodels")) + androidTestImplementation(project(":testutils")) + androidTestImplementation(project(":aws-api")) + androidTestImplementation(project(":aws-datastore")) + androidTestImplementation(dependency.androidx.annotation) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(project(":aws-auth-cognito")) + androidTestImplementation(dependency.rxjava) + androidTestImplementation(dependency.okhttp) + androidTestImplementation(dependency.oauth2) +} + +afterEvaluate { + android.kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +} diff --git a/aws-datastore/src/androidTest/assets/schemas/phonecall/phone.json b/aws-datastore/src/androidTest/assets/schemas/phonecall/phone.json index d8836739d7..efd6de154c 100644 --- a/aws-datastore/src/androidTest/assets/schemas/phonecall/phone.json +++ b/aws-datastore/src/androidTest/assets/schemas/phonecall/phone.json @@ -5,10 +5,10 @@ "associatedType": "Call", "name": "HasMany" }, - "ownedBy": { + "ownerOfPhone": { "associatedType": "Person", "name": "BelongsTo", - "targetName": "ownedById" + "targetName": "ownerOfPhoneId" } }, "authRules": [], @@ -33,24 +33,24 @@ "name": "number", "targetType": "String" }, - "ownedById": { + "ownerOfPhoneId": { "authRules": [], "isArray": false, "isEnum": false, "isModel": false, "isRequired": true, "javaClassForValue": "java.lang.String", - "name": "ownedById", + "name": "ownerOfPhoneId", "targetType": "ID" }, - "ownedBy": { + "ownerOfPhone": { "authRules": [], "isArray": false, "isEnum": false, "isModel": true, "isRequired": true, "javaClassForValue": "com.amplifyframework.core.model.SerializedModel", - "name": "ownedBy", + "name": "ownerOfPhone", "targetType": "Person" }, "calls": { diff --git a/aws-datastore/src/androidTest/java/com/amplifyframework/datastore/storage/sqlite/SQLiteStorageAdapterQueryTest.java b/aws-datastore/src/androidTest/java/com/amplifyframework/datastore/storage/sqlite/SQLiteStorageAdapterQueryTest.java index 1d6a5e9dcc..e90516747b 100644 --- a/aws-datastore/src/androidTest/java/com/amplifyframework/datastore/storage/sqlite/SQLiteStorageAdapterQueryTest.java +++ b/aws-datastore/src/androidTest/java/com/amplifyframework/datastore/storage/sqlite/SQLiteStorageAdapterQueryTest.java @@ -202,11 +202,11 @@ public void querySavedDataWithMultipleForeignKeysOfSameType() throws DataStoreEx final Phone phoneCalling = Phone.builder() .number("123-456-7890") - .ownedBy(personCalling) + .ownerOfPhone(personCalling) .build(); final Phone phoneCalled = Phone.builder() .number("567-890-1234") - .ownedBy(personCalled) + .ownerOfPhone(personCalled) .build(); final Call phoneCall = Call.builder() @@ -424,11 +424,11 @@ public void querySavedDataWithTimePredicates() throws DataStoreException { final Phone phoneCalling = Phone.builder() .number("123-456-7890") - .ownedBy(personCalling) + .ownerOfPhone(personCalling) .build(); final Phone phoneCalled = Phone.builder() .number("567-890-1234") - .ownedBy(personCalled) + .ownerOfPhone(personCalled) .build(); adapter.save(personCalling); diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/AWSDataStorePlugin.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/AWSDataStorePlugin.java index a1088820ca..61ad9b8a8e 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/AWSDataStorePlugin.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/AWSDataStorePlugin.java @@ -322,6 +322,7 @@ private Completable waitForInitialization() { return Completable.fromAction(() -> categoryInitializationsPending.await()) .timeout(LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) + .doOnComplete(() -> LOG.info("DataStore plugin initialized.")) .doOnError(error -> LOG.error("DataStore initialization timed out.", error)); } @@ -773,10 +774,14 @@ public Builder authModeStrategy(AuthModeStrategyType authModeStrategy) { /** * Enables Retry on DataStore sync engine. + * @deprecated This configuration will be deprecated in a future version. * @param isSyncRetryEnabled is sync retry enabled. * @return An implementation of the {@link ModelProvider} interface. */ + @Deprecated public Builder isSyncRetryEnabled(Boolean isSyncRetryEnabled) { + LOG.warn("The isSyncRetryEnabled configuration will be deprecated in a future version." + + " Please discontinue use of this API."); this.isSyncRetryEnabled = isSyncRetryEnabled; return this; } diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/DataStoreConfiguration.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/DataStoreConfiguration.java index ccb1dd66a7..9a1fa9af6e 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/DataStoreConfiguration.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/DataStoreConfiguration.java @@ -21,7 +21,9 @@ import androidx.annotation.VisibleForTesting; import androidx.core.util.ObjectsCompat; +import com.amplifyframework.core.Amplify; import com.amplifyframework.core.model.Model; +import com.amplifyframework.logging.Logger; import org.json.JSONException; import org.json.JSONObject; @@ -41,13 +43,15 @@ public final class DataStoreConfiguration { static final long DEFAULT_SYNC_INTERVAL_MINUTES = TimeUnit.DAYS.toMinutes(1); @VisibleForTesting static final int DEFAULT_SYNC_MAX_RECORDS = 10_000; - @VisibleForTesting + @VisibleForTesting static final int DEFAULT_SYNC_PAGE_SIZE = 1_000; @VisibleForTesting static final boolean DEFAULT_DO_SYNC_RETRY = false; static final int MAX_RECORDS = 1000; static final long MAX_TIME_SEC = 2; + private static final Logger LOG = Amplify.Logging.forNamespace("amplify:aws-datastore"); + private final DataStoreErrorHandler errorHandler; private final DataStoreConflictHandler conflictHandler; private final Integer syncMaxRecords; @@ -399,11 +403,16 @@ public Builder syncMaxRecords(@IntRange(from = 0) Integer syncMaxRecords) { /** * Sets the retry enabled on datastore sync. + * + * @deprecated This configuration will be deprecated in a future version. * @param doSyncRetry Is retry enabled on datastore sync * @return Current builder instance */ @NonNull + @Deprecated public Builder doSyncRetry(boolean doSyncRetry) { + LOG.warn("The doSyncRetry configuration will be deprecated in a future version." + + " Please discontinue use of this API."); this.doSyncRetry = doSyncRetry; return Builder.this; } diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/appsync/AppSyncRequestFactory.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/appsync/AppSyncRequestFactory.java index 12fcdde811..6f9819a0f9 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/appsync/AppSyncRequestFactory.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/appsync/AppSyncRequestFactory.java @@ -25,6 +25,7 @@ import com.amplifyframework.api.graphql.PaginatedResult; import com.amplifyframework.api.graphql.QueryType; import com.amplifyframework.api.graphql.SubscriptionType; +import com.amplifyframework.core.Amplify; import com.amplifyframework.core.model.AuthRule; import com.amplifyframework.core.model.AuthStrategy; import com.amplifyframework.core.model.Model; @@ -50,6 +51,7 @@ import com.amplifyframework.core.model.query.predicate.QueryPredicateOperation; import com.amplifyframework.core.model.query.predicate.QueryPredicates; import com.amplifyframework.datastore.DataStoreException; +import com.amplifyframework.logging.Logger; import com.amplifyframework.util.Casing; import com.amplifyframework.util.TypeMaker; @@ -75,6 +77,8 @@ * and AppSync-specific field names (`_version`, `_deleted`, etc.) */ final class AppSyncRequestFactory { + private static final Logger LOG = Amplify.Logging.forNamespace("amplify:aws-datastore"); + private AppSyncRequestFactory() {} /** @@ -485,6 +489,8 @@ private static Object extractAssociateId(ModelField modelField, Model instance, } else if (modelField.isModel() && fieldValue instanceof Map) { return ((Map) fieldValue).get("id"); } else { + LOG.warn(String.format("Can't extract identifier: modelField=%s, isModel=%s, fieldValue=%s", + modelField.getName(), modelField.isModel(), fieldValue)); throw new IllegalStateException("Associated data is not Model or Map."); } } diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/storage/sqlite/SQLiteCommandFactory.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/storage/sqlite/SQLiteCommandFactory.java index f9606ae06c..d30f5e504b 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/storage/sqlite/SQLiteCommandFactory.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/storage/sqlite/SQLiteCommandFactory.java @@ -191,7 +191,7 @@ public SqlCommand queryFor(@NonNull ModelSchema modelSchema, Iterator columnsIterator = Objects.requireNonNull(columns.get(tableAlias)).iterator(); while (columnsIterator.hasNext()) { final SQLiteColumn column = columnsIterator.next(); - String columnName = column.getQuotedColumnName().replace(column.getTableName(), tableAlias); + String columnName = column.getQuotedColumnName().replaceFirst(column.getTableName(), tableAlias); selectColumns.append(columnName); // Alias columns with a unique alias to avoid duplicate column names or alias names String columnAlias = column.getAliasedName() @@ -542,8 +542,8 @@ private void recursivelyBuildJoins(SQLiteTable table, Map Single> resolve( ConflictData conflictData = ConflictData.create(local, remote); return Single - .>create(emitter -> - conflictHandler.onConflictDetected(conflictData, emitter::onSuccess) - ) + .>create(emitter -> { + LOG.debug("Invoking conflict handler"); + conflictHandler.onConflictDetected(conflictData, emitter::onSuccess); + }) .flatMap(decision -> { + LOG.debug(String.format("Conflict handler decision: %s", decision)); @SuppressWarnings("unchecked") ConflictResolutionDecision typedDecision = (ConflictResolutionDecision) decision; return resolveModelAndMetadata(conflictData, metadata, typedDecision); diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/MutationProcessor.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/MutationProcessor.java index ebb0227380..f40c18c754 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/MutationProcessor.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/MutationProcessor.java @@ -25,6 +25,7 @@ import com.amplifyframework.core.model.ModelSchema; import com.amplifyframework.core.model.SchemaRegistry; import com.amplifyframework.core.model.SerializedModel; +import com.amplifyframework.datastore.DataStoreConfigurationProvider; import com.amplifyframework.datastore.DataStoreException; import com.amplifyframework.datastore.appsync.AppSync; import com.amplifyframework.datastore.appsync.AppSyncConflictUnhandledError; @@ -35,6 +36,7 @@ import com.amplifyframework.logging.Logger; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -56,6 +58,7 @@ final class MutationProcessor { private final SchemaRegistry schemaRegistry; private final MutationOutbox mutationOutbox; private final AppSync appSync; + private final DataStoreConfigurationProvider dataStoreConfiguration; private final ConflictResolver conflictResolver; private final CompositeDisposable ongoingOperationsDisposable; private final RetryHandler retryHandler; @@ -66,7 +69,8 @@ private MutationProcessor(Builder builder) { this.schemaRegistry = Objects.requireNonNull(builder.schemaRegistry); this.mutationOutbox = Objects.requireNonNull(builder.mutationOutbox); this.appSync = Objects.requireNonNull(builder.appSync); - this.conflictResolver = Objects.requireNonNull(builder.conflictResolver); + this.dataStoreConfiguration = Objects.requireNonNull(builder.dataStoreConfiguration); + this.conflictResolver = new ConflictResolver(this.dataStoreConfiguration, this.appSync); this.retryHandler = Objects.requireNonNull(builder.retryHandler); this.ongoingOperationsDisposable = new CompositeDisposable(); } @@ -103,7 +107,7 @@ void startDrainingMutationOutbox() { .flatMapCompletable(event -> drainMutationOutbox()) .subscribe( () -> LOG.warn("Observation of mutation outbox was completed."), - error -> LOG.warn("Error ended observation of mutation outbox: ", error) + error -> LOG.error("Error ended observation of mutation outbox: ", error) ) ); } @@ -119,9 +123,7 @@ private Completable drainMutationOutbox() { processOutboxItem(next) .blockingAwait(); } catch (RuntimeException error) { - return Completable.error(new DataStoreException( - "Failed to process " + error, "Check your internet connection." - )); + return Completable.error(error); } } while (true); } @@ -157,17 +159,12 @@ private Completable processOutboxItem(PendingMutation mutat ); publishCurrentOutboxStatus(); }) - // If caused by an AppSync error, then publish it to hub, swallow, - // and then remove from the outbox to unblock the queue. - // Otherwise, pass it through. + // Errors on a mutation shouldn't halt the processing of the remaining mutations. + // If an error happens, it has to be announced (via Hub and the error handler) and the mutation removed from + // the outbox. .onErrorResumeNext(error -> { - if (error instanceof DataStoreException.GraphQLResponseException) { - DataStoreException.GraphQLResponseException appSyncError = - (DataStoreException.GraphQLResponseException) error; - return mutationOutbox.remove(mutationOutboxItem.getMutationId()) - .doOnComplete(() -> announceMutationFailed(mutationOutboxItem, appSyncError)); - } - return Completable.error(error); + return mutationOutbox.remove(mutationOutboxItem.getMutationId()) + .doOnComplete(() -> announceMutationFailed(mutationOutboxItem, error)); }) // Finally, catch all. .doOnError(error -> { @@ -221,12 +218,27 @@ private void announceMutationProcessed( */ private void announceMutationFailed( PendingMutation pendingMutation, - DataStoreException.GraphQLResponseException error + Throwable error ) { - List errors = error.getErrors(); + List errors = error instanceof DataStoreException.GraphQLResponseException + ? ((DataStoreException.GraphQLResponseException) error).getErrors() + : Collections.emptyList(); + OutboxMutationFailedEvent errorEvent = OutboxMutationFailedEvent.create(pendingMutation, errors); + Amplify.Hub.publish(HubChannel.DATASTORE, errorEvent.toHubEvent()); + + // Exceptions from customer's error handler code shouldn't prevent the processor from working. + try { + DataStoreException err = error instanceof DataStoreException.GraphQLResponseException + ? (DataStoreException.GraphQLResponseException) error + : new DataStoreException("Mutation failed.", error, "See underlying cause."); + + this.dataStoreConfiguration.getConfiguration().getErrorHandler().accept(err); + } catch (Throwable err) { + LOG.warn("Error invoking the error handler", err); + } } /** @@ -337,11 +349,12 @@ private Single> publishWithStrategy( private Single> publishWithRetry( @NonNull PendingMutation mutation) { - List> skipException = new ArrayList<>(); - skipException.add(DataStoreException.GraphQLResponseException.class); - skipException.add(ApiException.NonRetryableException.class); + List> nonRetryableExceptions = new ArrayList<>(); + nonRetryableExceptions.add(DataStoreException.GraphQLResponseException.class); + nonRetryableExceptions.add(ApiException.NonRetryableException.class); + LOG.info("Started Publish with retry: " + mutation); - return retryHandler.retry(publishToNetwork(mutation), skipException); + return retryHandler.retry(publishToNetwork(mutation), nonRetryableExceptions); } /** @@ -363,6 +376,7 @@ private Single> handleResponseErrors( AppSyncConflictUnhandledError unhandledConflict = AppSyncConflictUnhandledError.findFirst(modelClazz, errors); if (unhandledConflict != null) { + LOG.warn(String.format("Unhandled conflict: %s", unhandledConflict)); return conflictResolver.resolve(pendingMutation, unhandledConflict); } @@ -399,7 +413,7 @@ static final class Builder implements BuilderSteps.ModelSchemaRegistryStep, BuilderSteps.MutationOutboxStep, BuilderSteps.AppSyncStep, - BuilderSteps.ConflictResolverStep, + BuilderSteps.DataStoreConfigurationProviderStep, BuilderSteps.RetryHandlerStep, BuilderSteps.BuildStep { private Merger merger; @@ -407,7 +421,7 @@ static final class Builder implements private SchemaRegistry schemaRegistry; private MutationOutbox mutationOutbox; private AppSync appSync; - private ConflictResolver conflictResolver; + private DataStoreConfigurationProvider dataStoreConfiguration; private RetryHandler retryHandler; @NonNull @@ -440,15 +454,16 @@ public BuilderSteps.AppSyncStep mutationOutbox(@NonNull MutationOutbox mutationO @NonNull @Override - public BuilderSteps.ConflictResolverStep appSync(@NonNull AppSync appSync) { + public BuilderSteps.DataStoreConfigurationProviderStep appSync(@NonNull AppSync appSync) { Builder.this.appSync = Objects.requireNonNull(appSync); return Builder.this; } @NonNull @Override - public BuilderSteps.RetryHandlerStep conflictResolver(@NonNull ConflictResolver conflictResolver) { - this.conflictResolver = Objects.requireNonNull(conflictResolver); + public BuilderSteps.RetryHandlerStep dataStoreConfigurationProvider( + @NonNull DataStoreConfigurationProvider dataStoreConfiguration) { + Builder.this.dataStoreConfiguration = Objects.requireNonNull(dataStoreConfiguration); return Builder.this; } @@ -489,12 +504,13 @@ interface MutationOutboxStep { interface AppSyncStep { @NonNull - ConflictResolverStep appSync(@NonNull AppSync appSync); + DataStoreConfigurationProviderStep appSync(@NonNull AppSync appSync); } - interface ConflictResolverStep { + interface DataStoreConfigurationProviderStep { @NonNull - RetryHandlerStep conflictResolver(@NonNull ConflictResolver conflictResolver); + RetryHandlerStep dataStoreConfigurationProvider( + DataStoreConfigurationProvider dataStoreConfiguration); } interface RetryHandlerStep { diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/Orchestrator.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/Orchestrator.java index b67d02362a..797a4ed830 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/Orchestrator.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/Orchestrator.java @@ -99,9 +99,7 @@ public Orchestrator( VersionRepository versionRepository = new VersionRepository(localStorageAdapter); Merger merger = new Merger(mutationOutbox, versionRepository, localStorageAdapter); SyncTimeRegistry syncTimeRegistry = new SyncTimeRegistry(localStorageAdapter); - ConflictResolver conflictResolver = new ConflictResolver(dataStoreConfigurationProvider, appSync); this.queryPredicateProvider = new QueryPredicateProvider(dataStoreConfigurationProvider); - RetryHandler retryHandler = new RetryHandler(); this.mutationProcessor = MutationProcessor.builder() .merger(merger) @@ -109,8 +107,8 @@ public Orchestrator( .schemaRegistry(schemaRegistry) .mutationOutbox(mutationOutbox) .appSync(appSync) - .conflictResolver(conflictResolver) - .retryHandler(retryHandler) + .dataStoreConfigurationProvider(dataStoreConfigurationProvider) + .retryHandler(new RetryHandler()) .build(); this.syncProcessor = SyncProcessor.builder() .modelProvider(modelProvider) diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/RetryHandler.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/RetryHandler.java index 150fd08dfe..1a607e4aca 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/RetryHandler.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/RetryHandler.java @@ -19,11 +19,16 @@ import com.amplifyframework.datastore.utils.ErrorInspector; import com.amplifyframework.logging.Logger; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Scheduler; import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.core.SingleEmitter; +import io.reactivex.rxjava3.schedulers.Schedulers; /** * Class for retrying call on failure on a single. @@ -31,89 +36,107 @@ public class RetryHandler { private static final Logger LOG = Amplify.Logging.forNamespace("amplify:aws-datastore"); - private static final int MAX_EXPONENT_VALUE = 8; - private static final int JITTER_FACTOR_VALUE = 100; - private static final int MAX_ATTEMPTS_VALUE = 3; - private static final int MAX_DELAY_S_VALUE = 5 * 60; - private final int maxExponent; - private final int jitterFactor; - private final int maxAttempts; - private final int maxDelayS; + private static final long JITTER_MS_VALUE = 100; + @SuppressWarnings("checkstyle:magicnumber") + private static final long MAX_DELAY_MS_VALUE = Duration.ofMinutes(5).toMillis(); + private final long jitterMs; + private final long maxDelayMs; /** * Constructor to inject constants for unit testing. - * @param maxExponent maxExponent backoff can go to. - * @param jitterFactor jitterFactor for backoff. - * @param maxAttempts max attempt for retrying. - * @param maxDelayS max delay for retrying. + * + * @param jitterMs jitterMs for backoff. + * @param maxDelayMs max delay for retrying. */ - public RetryHandler(int maxExponent, - int jitterFactor, - int maxAttempts, - int maxDelayS) { - - this.maxExponent = maxExponent; - this.jitterFactor = jitterFactor; - this.maxAttempts = maxAttempts; - this.maxDelayS = maxDelayS; + public RetryHandler(long jitterMs, + long maxDelayMs) { + + this.jitterMs = jitterMs; + this.maxDelayMs = maxDelayMs; } /** * Parameter less constructor. */ public RetryHandler() { - maxExponent = MAX_EXPONENT_VALUE; - jitterFactor = JITTER_FACTOR_VALUE; - maxAttempts = MAX_ATTEMPTS_VALUE; - maxDelayS = MAX_DELAY_S_VALUE; + jitterMs = JITTER_MS_VALUE; + maxDelayMs = MAX_DELAY_MS_VALUE; } /** - * retry. - * @param single single to be retried. - * @param skipExceptions exceptions which should not be retried. - * @param The type for single. + * Creates a {@link Single} that wraps a given one to make it retryable. + * + * @param single single to be retried. + * @param nonRetryableExceptions exceptions which should not be retried. + * @param The type for single. * @return single of type T. */ - public Single retry(Single single, List> skipExceptions) { - return Single.create(emitter -> call(single, emitter, 0L, maxAttempts, skipExceptions)); + public Single retry(Single single, List> nonRetryableExceptions) { + return retry(single, nonRetryableExceptions, Schedulers.computation()); } - @SuppressWarnings("ResultOfMethodCallIgnored") - private void call( - Single single, - SingleEmitter emitter, - Long delayInSeconds, - int attemptsLeft, - List> skipExceptions) { - single.delaySubscription(delayInSeconds, TimeUnit.SECONDS) - .subscribe(emitter::onSuccess, error -> { - if (!emitter.isDisposed()) { - LOG.verbose("Retry attempts left " + attemptsLeft + ". exception type:" + error.getClass()); - if (attemptsLeft == 0 || ErrorInspector.contains(error, skipExceptions)) { - emitter.onError(error); - } else { - call(single, emitter, jitteredDelaySec(attemptsLeft), - attemptsLeft - 1, skipExceptions); - } - } else { - LOG.verbose("The subscribing channel is disposed."); + /** + * Creates a {@link Single} that wraps a given one to make it retryable. + * + * @param single single to be retried. + * @param nonRetryableExceptions exceptions which should not be retried. + * @param scheduler Scheduler to run the timer, useful for Unit Testing. + * @param The type for single. + * @return A new single with retries support. + */ + protected Single retry(Single single, List> nonRetryableExceptions, + Scheduler scheduler) { + + AtomicInteger numAttempt = new AtomicInteger(); + AtomicBoolean isInProgress = new AtomicBoolean(); + + Observable observable = Observable.fromSingle(single); + + Observable retryableObservable = observable.doOnSubscribe(ignore -> { + LOG.info("Starting attempt #" + (numAttempt.get() + 1)); + }).doOnNext(ignore -> { + isInProgress.set(true); + LOG.info("Success on attempt #" + (numAttempt.get() + 1)); + }).retryWhen(errors -> { + return errors.flatMap(error -> { + if (!ErrorInspector.contains(error, nonRetryableExceptions)) { + long delay = jitteredDelayMillis(numAttempt.get()); + + LOG.warn("Attempt #" + (numAttempt.get() + 1) + " failed.", error); + + if (delay > maxDelayMs) { + LOG.warn("No more attempts left."); + return Observable.error(error); } - }); + + return Observable.timer(delay, TimeUnit.MILLISECONDS, scheduler).doOnSubscribe(ignore -> { + LOG.debug("Retrying in " + delay + " milliseconds."); + + numAttempt.getAndIncrement(); + }); + } + + LOG.warn("Non-retryable exception.", error); + return Observable.error(error); + }); + }).doOnDispose(() -> { + if (!isInProgress.get()) { + LOG.info("The subscribing channel is disposed, canceling retries."); + } + }); + + return retryableObservable.firstOrError(); } /** - * Method returns jittered delay time in seconds. - * @param attemptsLeft number of attempts left. - * @return delay in seconds. + * Method returns a jittered 2^numAttempt delay time in milliseconds. + * + * @param numAttempt Attempt number. + * @return delay in milliseconds. */ - long jitteredDelaySec(int attemptsLeft) { - int numAttempt = maxAttempts - (maxAttempts - attemptsLeft); - double waitTimeSeconds = - Math.min(maxDelayS, Math.pow(2, ((numAttempt) % maxExponent)) - + jitterFactor * Math.random()); - LOG.debug("Wait time is " + waitTimeSeconds + " seconds before retrying"); - return (long) waitTimeSeconds; + long jitteredDelayMillis(int numAttempt) { + return (long) (Duration.ofSeconds((long) Math.pow(2, numAttempt)).toMillis() + (jitterMs * Math.random())); } + } diff --git a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/SyncProcessor.java b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/SyncProcessor.java index 84320374ab..2d5102a1f8 100644 --- a/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/SyncProcessor.java +++ b/aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/SyncProcessor.java @@ -74,6 +74,11 @@ final class SyncProcessor { private final DataStoreConfigurationProvider dataStoreConfigurationProvider; private final QueryPredicateProvider queryPredicateProvider; private final RetryHandler requestRetry; + + /** + * The `isSyncRetryEnabled` value is being passed down all the way from the `AWSDataStorePlugin` or the + * `DataStoreConfiguration` where it's named `doSyncRetry`. + */ private final boolean isSyncRetryEnabled; private SyncProcessor(Builder builder) { @@ -86,6 +91,10 @@ private SyncProcessor(Builder builder) { this.queryPredicateProvider = builder.queryPredicateProvider; this.requestRetry = builder.requestRetry; this.isSyncRetryEnabled = builder.isSyncRetryEnabled; + + if (!this.isSyncRetryEnabled) { + LOG.warn("Disabling sync retries will be deprecated in a future version."); + } } /** @@ -278,10 +287,11 @@ private ModelWithMetadata hydrateSchemaIfNeeded(ModelWithMe */ private Single>> syncPage( GraphQLRequest>> request) { + return Single.create(emitter -> { Cancelable cancelable = appSync.sync(request, result -> { if (result.hasErrors()) { - emitter.onError(new DataStoreException( + emitter.onError(new DataStoreException.IrRecoverableException( String.format("A model sync failed: %s", result.getErrors()), "Check your schema." )); @@ -299,8 +309,10 @@ private Single>> syncPage private Single>> syncPageWithRetry( GraphQLRequest>> request) { + // We don't want to treat DataStoreException.GraphQLResponseException as non retryable here because we want to + // support merging the applicable data if any. List> skipException = new ArrayList<>(); - skipException.add(DataStoreException.GraphQLResponseException.class); + skipException.add(DataStoreException.IrRecoverableException.class); skipException.add(ApiException.NonRetryableException.class); return requestRetry.retry(syncPage(request), skipException); } diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/AWSDataStorePluginTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/AWSDataStorePluginTest.java index 011d95b335..ef3a1dde8a 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/AWSDataStorePluginTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/AWSDataStorePluginTest.java @@ -52,6 +52,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; @@ -84,6 +85,7 @@ @SuppressWarnings("SameParameterValue") @RunWith(RobolectricTestRunner.class) +@Ignore("Test class is unstable on CI - to be enabled after investigation") public final class AWSDataStorePluginTest { private static final Logger LOG = Amplify.Logging.forNamespace("amplify:datastore:test"); private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/DataStoreConfigurationTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/DataStoreConfigurationTest.java index a9bbab85ea..4eaf9bdf2d 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/DataStoreConfigurationTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/DataStoreConfigurationTest.java @@ -112,6 +112,8 @@ public void testDefaultOverriddenFromConfigurationAndObject() DataStoreSyncExpression ownerSyncExpression = () -> BlogOwner.ID.beginsWith(RandomString.string()); DataStoreSyncExpression postSyncExpression = () -> Post.ID.beginsWith(RandomString.string()); + + @SuppressWarnings("deprecation") DataStoreConfiguration configObject = DataStoreConfiguration .builder() .syncMaxRecords(expectedSyncMaxRecords) diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/MutationProcessorRetryTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/MutationProcessorRetryTest.java index 62ef05359e..a2cd6992c6 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/MutationProcessorRetryTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/MutationProcessorRetryTest.java @@ -85,7 +85,6 @@ public final class MutationProcessorRetryTest { @Before public void setup() throws AmplifyException { this.context = getApplicationContext(); - modelProvider = spy(AmplifyCliGeneratedModelProvider.singletonInstance()); this.modelProvider = spy(AmplifyCliGeneratedModelProvider.singletonInstance()); } diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/appsync/AppSyncMocking.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/appsync/AppSyncMocking.java index 39aef7bb13..fd8d45a419 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/appsync/AppSyncMocking.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/appsync/AppSyncMocking.java @@ -163,6 +163,25 @@ public CreateConfigurator mockResponse( return CreateConfigurator.this; } + /** + * When the AppSync create() method is invoked with the provided model, + * it will respond with the provided failure. + * @param model When this model is seen on the AppSync create(), + * @param error This error is emitted on the onFailure + * @param Type of model + * @return A create configurator + */ + @NonNull + public CreateConfigurator mockResponseFailure( + @NonNull T model, @NonNull Throwable error) { + Objects.requireNonNull(model); + Objects.requireNonNull(error); + callOnFailure(/* onFailure position = */ 3, error) + .when(appSync) + .create(eq(model), /* schema */ any(), /* onResponse */ any(), /* onFailure */ any()); + return CreateConfigurator.this; + } + @SuppressWarnings("SameParameterValue") private static Stubber callOnSuccess( int positionOfOnSuccess, GraphQLResponse> response) { @@ -176,6 +195,18 @@ private static Stubber callOnSuccess( }); } + private static Stubber callOnFailure( + int positionOfOnFailure, Throwable error) { + return doAnswer(invocation -> { + // Simulate a failure response callback from the create() method. + Consumer onFailure = + invocation.getArgument(positionOfOnFailure); + onFailure.accept(error); + // Technically, create() returns a Cancelable... + return new NoOpCancelable(); + }); + } + /** * When the AppSync create() method is invoked with the provided model, * return a successful GraphQLResponse that contains the given ModelWithMetadata diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/MutationProcessorTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/MutationProcessorTest.java index 71bf951403..c7fa871735 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/MutationProcessorTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/MutationProcessorTest.java @@ -48,6 +48,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowLog; +import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -60,6 +61,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; @@ -98,8 +100,7 @@ public void setup() throws AmplifyException { Merger merger = new Merger(mutationOutbox, versionRepository, localStorageAdapter); this.appSync = mock(AppSync.class); this.configurationProvider = mock(DataStoreConfigurationProvider.class); - ConflictResolver conflictResolver = new ConflictResolver(configurationProvider, appSync); - RetryHandler retryHandler = new RetryHandler(1, 1, 2, 1); + RetryHandler retryHandler = new RetryHandler(0, Duration.ofMinutes(1).toMillis()); schemaRegistry = SchemaRegistry.instance(); schemaRegistry.register(Collections.singleton(BlogOwner.class)); this.mutationProcessor = MutationProcessor.builder() @@ -108,7 +109,7 @@ public void setup() throws AmplifyException { .schemaRegistry(schemaRegistry) .mutationOutbox(mutationOutbox) .appSync(appSync) - .conflictResolver(conflictResolver) + .dataStoreConfigurationProvider(configurationProvider) .retryHandler(retryHandler) .build(); } @@ -326,6 +327,57 @@ public void canDrainMutationOutboxOnPublicationError() throws DataStoreException accumulator.await(); } + /** + * If an error is caused by AppSync response, then the error handler gets invoked. + * @throws DataStoreException On failure to save models + */ + @Test + public void callsErrorHandlerOnError() throws DataStoreException { + CountDownLatch errorHandlerInvocationsLatch = new CountDownLatch(1); + when(configurationProvider.getConfiguration()).thenReturn(DataStoreConfiguration.builder() + .errorHandler(ignore -> errorHandlerInvocationsLatch.countDown()) + .build() + ); + + ModelSchema schema = schemaRegistry.getModelSchemaForModelClass(BlogOwner.class); + + BlogOwner model = BlogOwner.builder() + .name("Blogger #1") + .build(); + synchronousStorageAdapter.save(model); + + DataStoreException.GraphQLResponseException error = new DataStoreException.GraphQLResponseException( + "Some exception.", + Collections.emptyList() + ); + + AppSyncMocking.create(appSync).mockResponseFailure(model, error); + + // Enqueue a creation in the mutation outbox + assertTrue(mutationOutbox + .enqueue(PendingMutation.creation(model, schema)) + .blockingAwait(TIMEOUT_SECONDS, TimeUnit.SECONDS)); + + // Start listening for Outbox Mutation Failed event. + HubAccumulator accumulator = HubAccumulator.create( + HubChannel.DATASTORE, + DataStoreChannelEventName.OUTBOX_MUTATION_FAILED, + 1 + ).start(); + + // Start draining the outbox. + mutationProcessor.startDrainingMutationOutbox(); + accumulator.await(); + + try { + assertTrue("Error handler wasn't invoked", + errorHandlerInvocationsLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + ); + } catch (InterruptedException exception) { + fail(exception.getMessage()); + } + } + /** * If the AppSync response to the mutation contains a retry able @@ -359,18 +411,19 @@ public void retryStrategyAppliedAfterRecoverableError() invocation.getArgument(indexOfResponseConsumer); ModelMetadata modelMetadata = new ModelMetadata(model.getId(), false, 1, Temporal.Timestamp.now()); - ModelWithMetadata modelWithMetadata = new ModelWithMetadata(model, + + ModelWithMetadata modelWithMetadata = new ModelWithMetadata<>(model, modelMetadata); retryHandlerInvocationCount.countDown(); onResponse.accept(new GraphQLResponse<>(modelWithMetadata, Collections.emptyList())); // latch makes sure success response is returned. return mock(GraphQLOperation.class); - }).when(appSync).update(ArgumentMatchers.any(), + }).when(appSync).update(ArgumentMatchers.any(), any(ModelSchema.class), anyInt(), any(QueryPredicate.class), ArgumentMatchers.>>>any(), - ArgumentMatchers.>any()); + ArgumentMatchers.any()); ModelSchema schema = schemaRegistry.getModelSchemaForModelClass(BlogOwner.class); LastSyncMetadata lastSyncMetadata = LastSyncMetadata.baseSyncedAt(schema.getName(), diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/RetryHandlerTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/RetryHandlerTest.java index 8950168557..9e1db38d80 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/RetryHandlerTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/RetryHandlerTest.java @@ -15,17 +15,25 @@ package com.amplifyframework.datastore.syncengine; +import com.amplifyframework.api.ApiException; import com.amplifyframework.datastore.DataStoreException; import org.junit.Test; +import java.time.Duration; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.observers.TestObserver; +import io.reactivex.rxjava3.schedulers.TestScheduler; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class RetryHandlerTest { @@ -37,7 +45,7 @@ public void testNoRetryOnSuccess() { //arrange RetryHandler subject = new RetryHandler(); String expectedValue = "Test value"; - Single mockSingle = Single.create(emitter -> emitter.onSuccess(expectedValue)); + Single mockSingle = Single.just(expectedValue); //act and assert subject.retry(mockSingle, new ArrayList<>()) @@ -45,7 +53,7 @@ public void testNoRetryOnSuccess() { .awaitDone(1, TimeUnit.SECONDS) .assertNoErrors() .assertValue(expectedValue) - .isDisposed(); + .assertComplete(); } /** @@ -54,20 +62,59 @@ public void testNoRetryOnSuccess() { @Test public void testNoRetryOnIrrecoverableError() { //arrange + TestScheduler scheduler = new TestScheduler(); RetryHandler subject = new RetryHandler(); - DataStoreException expectedException = new DataStoreException. + DataStoreException retryableException = new DataStoreException. GraphQLResponseException("PaginatedResult>", new ArrayList<>()); - Single mockSingle = Single.error(expectedException); - ArrayList> skipExceptionList = new ArrayList<>(); - skipExceptionList.add(DataStoreException.GraphQLResponseException.class); + ApiException nonRetryableException = new ApiException("Non recoverable", "This is intentional"); + AtomicInteger counter = new AtomicInteger(); + Single single = Single.create(emitter -> { + if (counter.incrementAndGet() >= 5) { + emitter.onError(nonRetryableException); + } else { + emitter.onError(retryableException); + } + + // Advance clock by next delay + scheduler.advanceTimeBy(subject.jitteredDelayMillis(counter.get()), TimeUnit.MILLISECONDS); + }); + List> nonRetryableExceptionList = + Collections.singletonList(nonRetryableException.getClass()); //act and assert - subject.retry(mockSingle, skipExceptionList) + subject.retry(single, nonRetryableExceptionList, scheduler) .test() .awaitDone(1, TimeUnit.SECONDS) - .assertError(expectedException) - .isDisposed(); + .assertError(nonRetryableException); + + assertEquals(5, counter.get()); + } + + /** + * Test cancel retries on dispose. + */ + @Test + public void testCancelOnDispose() { + //arrange + TestScheduler scheduler = new TestScheduler(); + RetryHandler subject = new RetryHandler(); + Single single = Single.just("some value").delay(3, TimeUnit.SECONDS, scheduler); + + //act and assert + TestObserver retry = subject.retry(single, Collections.emptyList()).test(); + + retry + .assertEmpty() + .assertNotComplete(); + + retry.dispose(); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + + retry.assertEmpty(); + + assertTrue(retry.isDisposed()); } /** @@ -76,35 +123,67 @@ public void testNoRetryOnIrrecoverableError() { @Test public void testRetryOnRecoverableError() { //arrange - RetryHandler subject = new RetryHandler(8, 0, 1, 1); + RetryHandler subject = new RetryHandler(0, Duration.ofMinutes(1).toMillis()); DataStoreException expectedException = new DataStoreException("PaginatedResult>", ""); AtomicInteger count = new AtomicInteger(0); - Single mockSingle = Single.error(expectedException) - .doOnError(e -> count.incrementAndGet()); + Single mockSingle = Single.create(emitter -> { + if (count.get() == 0) { + count.incrementAndGet(); + emitter.onError(expectedException); + } else { + count.incrementAndGet(); + emitter.onSuccess(true); + } + }); //act and assert subject.retry(mockSingle, new ArrayList<>()) .test() .awaitDone(10, TimeUnit.SECONDS) - .assertError(expectedException) - .isDisposed(); + .assertNoErrors(); assertEquals(2, count.get()); } + /** + * Test it won't retry beyond the maxDelay. + */ + @Test + public void testDoesNotGoBeyondMaxDelay() { + //arrange + TestScheduler scheduler = new TestScheduler(); + RetryHandler subject = new RetryHandler(0, Duration.ofMinutes(5).toMillis()); + DataStoreException expectedException = + new DataStoreException("PaginatedResult>", ""); + AtomicInteger count = new AtomicInteger(0); + + Single mockSingle = Single.create(emitter -> { + emitter.onError(expectedException); + scheduler.advanceTimeBy(subject.jitteredDelayMillis(count.incrementAndGet()), TimeUnit.MILLISECONDS); + }); + + //act and assert + subject.retry(mockSingle, new ArrayList<>(), scheduler) + .test() + .awaitDone(10, TimeUnit.SECONDS) + .assertError(expectedException); + + assertEquals(10, count.get()); + } + /** * test jittered delay method return the correct delay time. */ @Test public void testJitteredDelaySec() { //arrange - RetryHandler subject = new RetryHandler(8, 0, 1, 5); + RetryHandler subject = new RetryHandler(0, Integer.MAX_VALUE); //act - long delay = subject.jitteredDelaySec(2); + long delay = subject.jitteredDelayMillis(2); //assert - assertEquals(4, delay); + assertEquals(Duration.ofSeconds(4).toMillis(), delay); } /** @@ -113,11 +192,50 @@ public void testJitteredDelaySec() { @Test public void testJitteredDelaySecReturnsNoMoreThanMaxValue() { //arrange - RetryHandler subject = new RetryHandler(8, 0, 1, 1); + long maxDelayMs = Duration.ofSeconds(4).toMillis(); + RetryHandler subject = new RetryHandler(0, maxDelayMs); //act - long delay = subject.jitteredDelaySec(2); + long delay = subject.jitteredDelayMillis(2); //assert - assertEquals(1, delay); + assertEquals(maxDelayMs, delay); + } + + /** + * test jittered delay method returns powers of 2 when there's no jitter. + */ + @Test + public void testExponentialDelaysNoJitter() { + int jitterFactor = 0; + + //arrange + RetryHandler subject = new RetryHandler(jitterFactor, Integer.MAX_VALUE); + + IntStream.rangeClosed(0, 10).forEach(attempt -> { + //act + long delay = subject.jitteredDelayMillis(attempt); + + //assert + assertEquals(Duration.ofSeconds((long) Math.pow(2, attempt)).toMillis(), delay, jitterFactor); + }); + } + + /** + * test jittered delay method returns powers of 2 plus a random amount of miliseconds between 0 and jitterFactor. + */ + @Test + public void testExponentialDelaysWithJitterIsWithinDelta() { + int jitterFactor = 100; + + //arrange + RetryHandler subject = new RetryHandler(jitterFactor, Integer.MAX_VALUE); + + IntStream.rangeClosed(0, 10).forEach(attempt -> { + //act + long delay = subject.jitteredDelayMillis(attempt); + + //assert + assertEquals(Duration.ofSeconds((long) Math.pow(2, attempt)).toMillis(), delay, jitterFactor); + }); } } diff --git a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/SyncProcessorTest.java b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/SyncProcessorTest.java index 28e9024f31..410b6cb28a 100644 --- a/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/SyncProcessorTest.java +++ b/aws-datastore/src/test/java/com/amplifyframework/datastore/syncengine/SyncProcessorTest.java @@ -19,7 +19,6 @@ import com.amplifyframework.AmplifyException; import com.amplifyframework.api.graphql.GraphQLRequest; -import com.amplifyframework.api.graphql.GraphQLResponse; import com.amplifyframework.api.graphql.PaginatedResult; import com.amplifyframework.core.model.Model; import com.amplifyframework.core.model.ModelProvider; @@ -113,7 +112,6 @@ public final class SyncProcessorTest { private RetryHandler requestRetry; private boolean isSyncRetryEnabled = true; - /** * Wire up dependencies for the SyncProcessor, and build one for testing. * @throws AmplifyException On failure to load models into registry @@ -596,8 +594,7 @@ public void userProvidedErrorCallbackInvokedOnFailure() throws DataStoreExceptio // Arrange: mock failure when invoking hydrate on the mock object. AppSyncMocking.sync(appSync) .mockFailure(new DataStoreException - .GraphQLResponseException("Something timed out during sync.", - new ArrayList())); + .IrRecoverableException("Something timed out during sync.", "This was intentional")); // Act: call hydrate. assertTrue( diff --git a/aws-geo-location/build.gradle b/aws-geo-location/build.gradle deleted file mode 100644 index d205d5bef6..0000000000 --- a/aws-geo-location/build.gradle +++ /dev/null @@ -1,43 +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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file('configuration/checkstyle.gradle') -apply from: rootProject.file('configuration/publishing.gradle') - -group = POM_GROUP - -dependencies { - implementation project(':core') - // ToDo: Remove fixed dependency to cognito and abstract it to core - implementation project(':aws-auth-cognito') - implementation dependency.aws.location - - testImplementation project(':testutils') - testImplementation dependency.junit - testImplementation dependency.robolectric - - androidTestImplementation project(':testutils') - androidTestImplementation project(':aws-auth-cognito') - androidTestImplementation dependency.androidx.annotation - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit -} - -afterEvaluate { - it.android.kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() -} diff --git a/aws-geo-location/build.gradle.kts b/aws-geo-location/build.gradle.kts new file mode 100644 index 0000000000..5e72d4a9b6 --- /dev/null +++ b/aws-geo-location/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(dependency.aws.location) + + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) + testImplementation(testDependency.robolectric) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(project(":aws-auth-cognito")) + androidTestImplementation(dependency.androidx.annotation) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) +} + +afterEvaluate { + android.kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +} diff --git a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/AWSLocationGeoPlugin.kt b/aws-geo-location/src/main/java/com/amplifyframework/geo/location/AWSLocationGeoPlugin.kt index 6d03aa2981..5519cda7c2 100644 --- a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/AWSLocationGeoPlugin.kt +++ b/aws-geo-location/src/main/java/com/amplifyframework/geo/location/AWSLocationGeoPlugin.kt @@ -20,11 +20,11 @@ import aws.sdk.kotlin.services.location.LocationClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import com.amplifyframework.AmplifyException import com.amplifyframework.auth.AuthCategory +import com.amplifyframework.auth.CognitoCredentialsProvider import com.amplifyframework.core.Amplify import com.amplifyframework.core.Consumer import com.amplifyframework.geo.GeoCategoryPlugin import com.amplifyframework.geo.GeoException -import com.amplifyframework.geo.location.auth.CognitoCredentialsProvider import com.amplifyframework.geo.location.configuration.GeoConfiguration import com.amplifyframework.geo.location.options.AmazonLocationSearchByCoordinatesOptions import com.amplifyframework.geo.location.options.AmazonLocationSearchByTextOptions @@ -65,7 +65,7 @@ class AWSLocationGeoPlugin( } val credentialsProvider: CredentialsProvider by lazy { - CognitoCredentialsProvider(authCategory) + CognitoCredentialsProvider() } override fun getPluginKey(): String { diff --git a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/auth/CognitoCredentialsProvider.kt b/aws-geo-location/src/main/java/com/amplifyframework/geo/location/auth/CognitoCredentialsProvider.kt deleted file mode 100644 index 2cf7bd6d40..0000000000 --- a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/auth/CognitoCredentialsProvider.kt +++ /dev/null @@ -1,60 +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.geo.location.auth - -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import com.amplifyframework.auth.AWSCredentials -import com.amplifyframework.auth.AWSTemporaryCredentials -import com.amplifyframework.auth.AuthCategory -import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine - -/** - * Wrapper to provide credentials from Auth Cognito - */ -internal class CognitoCredentialsProvider(private val authCategory: AuthCategory) : CredentialsProvider { - override suspend fun getCredentials(): Credentials { - return suspendCancellableCoroutine { continuation -> - authCategory.fetchAuthSession( - { authSession -> - (authSession as? AWSCognitoAuthSession)?.awsCredentialsResult?.value?.let { - continuation.resume(it.toCredentials()) - } ?: continuation.resumeWithException( - Exception( - "Failed to get credentials. " + - "Check if you are signed in and configured identity pools correctly." - ) - ) - }, - { - continuation.resumeWithException(it) - } - ) - } - } -} - -private fun AWSCredentials.toCredentials(): Credentials { - return Credentials( - accessKeyId = this.accessKeyId, - secretAccessKey = this.secretAccessKey, - sessionToken = (this as? AWSTemporaryCredentials)?.sessionToken, - expiration = (this as? AWSTemporaryCredentials)?.expiration - ) -} diff --git a/aws-predictions-tensorflow/build.gradle b/aws-predictions-tensorflow/build.gradle deleted file mode 100644 index 73ed3c3fac..0000000000 --- a/aws-predictions-tensorflow/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 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. - */ - -apply plugin: 'com.android.library' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -dependencies { - implementation project(':core') - implementation dependency.androidx.appcompat - implementation dependency.tensorflow - - testImplementation project(':testutils') - testImplementation dependency.junit - testImplementation dependency.mockito -} diff --git a/aws-predictions-tensorflow/build.gradle.kts b/aws-predictions-tensorflow/build.gradle.kts new file mode 100644 index 0000000000..8e0b5dec62 --- /dev/null +++ b/aws-predictions-tensorflow/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(dependency.androidx.appcompat) + implementation(dependency.tensorflow) + + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) +} diff --git a/aws-predictions/build.gradle b/aws-predictions/build.gradle deleted file mode 100644 index d3026922ba..0000000000 --- a/aws-predictions/build.gradle +++ /dev/null @@ -1,43 +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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -dependencies { - implementation project(path: ':core') - implementation project(path: ':aws-auth-cognito') - implementation dependency.androidx.appcompat - implementation dependency.aws.comprehend - implementation dependency.aws.polly - implementation dependency.aws.rekognition - implementation dependency.aws.textract - implementation dependency.aws.translate - - testImplementation project(path: ':testutils') - testImplementation dependency.junit - testImplementation dependency.robolectric - testImplementation dependency.rxjava - - androidTestImplementation project(path: ':testutils') - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.mockkandroid - androidTestImplementation dependency.rxjava -} diff --git a/aws-predictions/build.gradle.kts b/aws-predictions/build.gradle.kts new file mode 100644 index 0000000000..3648bdec72 --- /dev/null +++ b/aws-predictions/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(dependency.androidx.appcompat) + implementation(dependency.aws.comprehend) + implementation(dependency.aws.polly) + implementation(dependency.aws.rekognition) + implementation(dependency.aws.textract) + implementation(dependency.aws.translate) + + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) + testImplementation(testDependency.robolectric) + testImplementation(dependency.rxjava) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(project(":aws-auth-cognito")) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.mockk.android) + androidTestImplementation(dependency.rxjava) +} diff --git a/aws-predictions/src/androidTest/java/com/amplifyframework/predictions/aws/TestPredictionsCategory.java b/aws-predictions/src/androidTest/java/com/amplifyframework/predictions/aws/TestPredictionsCategory.java index 513d980821..4618f86c02 100644 --- a/aws-predictions/src/androidTest/java/com/amplifyframework/predictions/aws/TestPredictionsCategory.java +++ b/aws-predictions/src/androidTest/java/com/amplifyframework/predictions/aws/TestPredictionsCategory.java @@ -20,11 +20,11 @@ import androidx.annotation.RawRes; import com.amplifyframework.AmplifyException; +import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.AmplifyConfiguration; import com.amplifyframework.core.category.CategoryConfiguration; import com.amplifyframework.core.category.CategoryType; import com.amplifyframework.predictions.PredictionsCategory; -import com.amplifyframework.predictions.aws.auth.CognitoCredentialsProvider; import java.util.Objects; diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java index beb20d7dea..1f58244538 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java @@ -20,10 +20,10 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Consumer; import com.amplifyframework.predictions.PredictionsException; import com.amplifyframework.predictions.PredictionsPlugin; -import com.amplifyframework.predictions.aws.auth.CognitoCredentialsProvider; import com.amplifyframework.predictions.aws.models.AWSVoiceType; import com.amplifyframework.predictions.aws.operation.AWSIdentifyOperation; import com.amplifyframework.predictions.aws.operation.AWSInterpretOperation; diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/auth/CognitoCredentialsProvider.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/auth/CognitoCredentialsProvider.kt deleted file mode 100644 index 75ae7d5072..0000000000 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/auth/CognitoCredentialsProvider.kt +++ /dev/null @@ -1,63 +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.predictions.aws.auth - -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import com.amplifyframework.auth.AWSCredentials -import com.amplifyframework.auth.AWSTemporaryCredentials -import com.amplifyframework.auth.cognito.AWSCognitoAuthSession -import com.amplifyframework.core.Amplify -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine - -/** - * Wrapper to provide credentials from Auth synchronously and asynchronously - */ -internal class CognitoCredentialsProvider : CredentialsProvider { - /** - * Request [Credentials] from the provider. - */ - override suspend fun getCredentials(): Credentials { - return suspendCancellableCoroutine { continuation -> - Amplify.Auth.fetchAuthSession( - { authSession -> - (authSession as? AWSCognitoAuthSession)?.awsCredentialsResult?.value?.let { - continuation.resume(it.toCredentials()) - } ?: continuation.resumeWithException( - Exception( - "Failed to get credentials. " + - "Check if you are signed in and configured identity pools correctly." - ) - ) - }, - { - continuation.resumeWithException(it) - } - ) - } - } -} - -private fun AWSCredentials.toCredentials(): Credentials { - return Credentials( - accessKeyId = this.accessKeyId, - secretAccessKey = this.secretAccessKey, - sessionToken = (this as? AWSTemporaryCredentials)?.sessionToken, - expiration = (this as? AWSTemporaryCredentials)?.expiration - ) -} diff --git a/aws-push-notifications-pinpoint-utils/build.gradle b/aws-push-notifications-pinpoint-utils/build.gradle deleted file mode 100644 index 217bb82bf6..0000000000 --- a/aws-push-notifications-pinpoint-utils/build.gradle +++ /dev/null @@ -1,51 +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. - */ - -plugins { - id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10" -} -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file('configuration/checkstyle.gradle') -apply from: rootProject.file('configuration/publishing.gradle') - -dependencies { - def lifecycleVersion = "2.4.1" - - implementation project(path: ":core") - - implementation dependency.androidx.core_ktx - implementation dependency.androidx.appcompat - implementation dependency.androidx.annotation - implementation dependency.androidx.activity - - implementation dependency.firebasemessaging - implementation dependency.kotlin.serializationJson - - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "com.google.android.material:material:1.4.0" - - testImplementation dependency.junit - testImplementation dependency.mockk - - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit -} diff --git a/aws-push-notifications-pinpoint-utils/build.gradle.kts b/aws-push-notifications-pinpoint-utils/build.gradle.kts new file mode 100644 index 0000000000..5343e392f2 --- /dev/null +++ b/aws-push-notifications-pinpoint-utils/build.gradle.kts @@ -0,0 +1,55 @@ +/* + * Copyright 2023 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. + */ + +plugins { + id("org.jetbrains.kotlin.plugin.serialization") version "1.6.10" + id("com.android.library") + id("kotlin-android") + id("org.jlleitschuh.gradle.ktlint") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + val lifecycleVersion = "2.4.1" + + implementation(project(":core")) + + implementation(dependency.androidx.core.ktx) + implementation(dependency.androidx.activity) + implementation(dependency.androidx.appcompat) + implementation(dependency.androidx.annotation) + implementation(dependency.androidx.core) + implementation(dependency.firebasemessaging) + implementation(dependency.kotlin.serializationJson) + + //noinspection GradleDependency + implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion") + //noinspection GradleDependency + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion") + //noinspection GradleDependency + implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") + //noinspection GradleDependency + implementation("com.google.android.material:material:1.4.0") + + testImplementation(testDependency.junit) + testImplementation(testDependency.mockk) + + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) +} diff --git a/aws-push-notifications-pinpoint-utils/src/main/java/com/amplifyframework/pushnotifications/pinpoint/utils/PushNotificationsConstants.kt b/aws-push-notifications-pinpoint-utils/src/main/java/com/amplifyframework/pushnotifications/pinpoint/utils/PushNotificationsConstants.kt index d17d42ecdf..810bca1a47 100644 --- a/aws-push-notifications-pinpoint-utils/src/main/java/com/amplifyframework/pushnotifications/pinpoint/utils/PushNotificationsConstants.kt +++ b/aws-push-notifications-pinpoint-utils/src/main/java/com/amplifyframework/pushnotifications/pinpoint/utils/PushNotificationsConstants.kt @@ -29,12 +29,12 @@ class PushNotificationsConstants { const val JOURNEY = "journey" // journey const val JOURNEY_ID = "journey_id" // journey_id const val JOURNEY_ACTIVITY_ID = "journey_activity_id" // journey_activity_id - const val PINPOINT_OPENAPP = "${PINPOINT_PREFIX}${OPENAPP}" // pinpoint.openApp - const val PINPOINT_URL = "${PINPOINT_PREFIX}${URL}" // pinpoint.url - const val PINPOINT_DEEPLINK = "${PINPOINT_PREFIX}${DEEPLINK}" // pinpoint.deeplink - const val PINPOINT_NOTIFICATION_TITLE = "${NOTIFICATION_PREFIX}${TITLE}" // pinpoint.notification.title + const val PINPOINT_OPENAPP = "${PINPOINT_PREFIX}$OPENAPP" // pinpoint.openApp + const val PINPOINT_URL = "${PINPOINT_PREFIX}$URL" // pinpoint.url + const val PINPOINT_DEEPLINK = "${PINPOINT_PREFIX}$DEEPLINK" // pinpoint.deeplink + const val PINPOINT_NOTIFICATION_TITLE = "${NOTIFICATION_PREFIX}$TITLE" // pinpoint.notification.title const val PINPOINT_NOTIFICATION_BODY = "${NOTIFICATION_PREFIX}body" // pinpoint.notification.body - const val PINPOINT_NOTIFICATION_IMAGEURL = "${NOTIFICATION_PREFIX}${IMAGEURL}" // pinpoint.notification.imageUrl + const val PINPOINT_NOTIFICATION_IMAGEURL = "${NOTIFICATION_PREFIX}$IMAGEURL" // pinpoint.notification.imageUrl // pinpoint.notification.silentPush const val PINPOINT_NOTIFICATION_SILENTPUSH = "${NOTIFICATION_PREFIX}silentPush" const val PINPOINT_CAMPAIGN_CAMPAIGN_ID = "${CAMPAIGN_PREFIX}campaign_id" // pinpoint.campaign.campaign_id diff --git a/aws-push-notifications-pinpoint/build.gradle b/aws-push-notifications-pinpoint/build.gradle deleted file mode 100644 index e30cba429a..0000000000 --- a/aws-push-notifications-pinpoint/build.gradle +++ /dev/null @@ -1,38 +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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file('configuration/checkstyle.gradle') -apply from: rootProject.file('configuration/publishing.gradle') - -dependencies { - implementation project(path: ':core') - implementation project(path: ':aws-auth-cognito') - implementation project(path: ':aws-analytics-pinpoint-targeting') - api project(path: ':aws-push-notifications-pinpoint-utils') - - implementation dependency.aws.pinpoint - - api dependency.firebasemessaging - implementation dependency.androidx.core_ktx - implementation dependency.androidx.appcompat - implementation dependency.kotlin.serializationJson - - testImplementation dependency.junit - - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit -} diff --git a/aws-push-notifications-pinpoint/build.gradle.kts b/aws-push-notifications-pinpoint/build.gradle.kts new file mode 100644 index 0000000000..3e63454105 --- /dev/null +++ b/aws-push-notifications-pinpoint/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") + id("org.jlleitschuh.gradle.ktlint") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(project(":aws-auth-cognito")) + implementation(project(":aws-analytics-pinpoint-targeting")) + + api(project(":aws-push-notifications-pinpoint-utils")) + api(dependency.firebasemessaging) + + implementation(dependency.aws.pinpoint) + + implementation(dependency.androidx.core.ktx) + implementation(dependency.androidx.appcompat) + implementation(dependency.kotlin.serializationJson) + + testImplementation(testDependency.junit) + + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) +} diff --git a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt index 01aa7222fe..db646114cf 100644 --- a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt +++ b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt @@ -313,7 +313,7 @@ class AWSPinpointPushNotificationsPlugin : PushNotificationsPlugin - project.group = POM_GROUP - - apply plugin: 'org.jlleitschuh.gradle.ktlint' - ktlint { - android.set(true) - } - afterEvaluate { - configureAndroidLibrary(project) - project.apply from: '../jacoco.gradle' - } - if (!project.name.contains("test")) { - apply plugin: 'org.jetbrains.dokka' - dokkaHtml { - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipEmptyPackages.set(true) - skipDeprecated.set(true) - reportUndocumented.set(true) - jdkVersion.set(8) - } - } - } - } - - apply plugin: 'org.gradle.test-retry' - - tasks.withType(Test).configureEach { - retry { - maxRetries = 9 - maxFailures = 100 - failOnPassedAfterRetry = true - } - } -} - -private void configureAndroidLibrary(Project project) { - project.ext.VERSION_NAME = project.hasProperty('VERSION_NAME') ? - project.findProperty('VERSION_NAME') : - rootProject.findProperty('VERSION_NAME') - - if (project.hasProperty('signingKeyId')) { - System.out.println("Getting signing info from protected source.") - project.ext.'signing.keyId' = findProperty('signingKeyId') - project.ext.'signing.password' = findProperty('signingPassword') - project.ext.'signing.inMemoryKey' = findProperty('signingInMemoryKey') - } - - project.android { - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdkVersion rootProject.ext.compileSdkVersion - - defaultConfig { - multiDexEnabled true - minSdkVersion project.findProperty('minSdkVersion') - targetSdkVersion rootProject.ext.targetSdkVersion - versionName project.ext.VERSION_NAME - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - testInstrumentationRunnerArguments clearPackageData: 'true' - consumerProguardFiles rootProject.file('configuration/consumer-rules.pro') - - testOptions { - animationsDisabled = true - unitTests { - includeAndroidResources = true - } - } - - buildConfigField "String", "VERSION_NAME", "\"${project.ext.VERSION_NAME}\"" - } - - lintOptions { - warningsAsErrors true - abortOnError true - enable 'UnusedResources' - enable 'NewerVersionAvailable' - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - // Needed when running integration tests. The oauth2 library uses relies on two - // dependencies (Apache's httpcore and httpclient), both of which include - // META-INF/DEPENDENCIES. Tried a couple other options to no avail. - packagingOptions { - exclude 'META-INF/DEPENDENCIES' - } - - } - - project.dependencies { - coreLibraryDesugaring dependency.android.desugartools - } -} -apply from: rootProject.file("configuration/instrumentation-tests.gradle") diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..26b05e6962 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,162 @@ +/* + * 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. + */ + +import com.android.build.gradle.LibraryExtension +import org.jetbrains.dokka.gradle.DokkaTask + +buildscript { + repositories { + google() + mavenCentral() + maven(url = "https://plugins.gradle.org/m2/") + } + + dependencies { + classpath("com.android.tools.build:gradle:7.3.1") + classpath(kotlin("gradle-plugin", version = "1.7.10")) + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.10") + classpath("com.google.gms:google-services:4.3.15") + classpath("org.jlleitschuh.gradle:ktlint-gradle:11.0.0") + classpath("org.gradle:test-retry-gradle-plugin:1.4.1") + classpath("org.jetbrains.kotlinx:kover:0.6.1") + } +} + +allprojects { + repositories { + maven(url = "https://aws.oss.sonatype.org/content/repositories/snapshots/") + google() + mavenCentral() + } + + gradle.projectsEvaluated { + tasks.withType().configureEach { + options.compilerArgs.apply { + add("-Xlint:all") + add("-Werror") + } + } + tasks.withType().configureEach { + minHeapSize = "128m" + maxHeapSize = "4g" + } + } +} + +apply(plugin = "org.jetbrains.dokka") +tasks.withType().configureEach { + outputDirectory.set(rootProject.buildDir) +} + +tasks.register("clean").configure { + delete(rootProject.buildDir) +} + +subprojects { + apply(plugin = "org.jlleitschuh.gradle.ktlint") + + configure { + android.set(true) + } + + afterEvaluate { + configureAndroid() + apply(from = "../kover.gradle") + } + + if (!name.contains("test")) { + apply(plugin = "org.jetbrains.dokka") + tasks.withType().configureEach { + dokkaSourceSets { + configureEach { + includeNonPublic.set(false) + skipEmptyPackages.set(true) + skipDeprecated.set(true) + reportUndocumented.set(true) + jdkVersion.set(8) + } + } + } + } + + apply(plugin = "org.gradle.test-retry") + + tasks.withType().configureEach { + retry { + maxRetries.set(9) + maxFailures.set(100) + failOnPassedAfterRetry.set(true) + } + } +} + +@Suppress("ExpiredTargetSdkVersion") +fun Project.configureAndroid() { + val sdkVersionName = findProperty("VERSION_NAME") ?: rootProject.findProperty("VERSION_NAME") + + if (hasProperty("signingKeyId")) { + println("Getting signing info from protected source.") + extra["signing.keyId"] = findProperty("signingKeyId") + extra["signing.password"] = findProperty("signingPassword") + extra["signing.inMemoryKey"] = findProperty("signingInMemoryKey") + } + + configure { + buildToolsVersion = "30.0.2" + compileSdk = 31 + + defaultConfig { + minSdk = 24 + targetSdk = 30 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments += "clearPackageData" to "true" + consumerProguardFiles += rootProject.file("configuration/consumer-rules.pro") + + testOptions { + animationsDisabled = true + unitTests { + isIncludeAndroidResources = true + } + } + + buildConfigField("String", "VERSION_NAME", "\"$sdkVersionName\"") + } + + lint { + warningsAsErrors = true + abortOnError = true + enable += listOf("UnusedResources", "NewerVersionAvailable") + } + + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + // Needed when running integration tests. The oauth2 library uses relies on two + // dependencies (Apache's httpcore and httpclient), both of which include + // META-INF/DEPENDENCIES. Tried a couple other options to no avail. + packagingOptions { + resources.excludes.add("META-INF/DEPENDENCIES") + } + } + + dependencies { + add("coreLibraryDesugaring", dependency.android.desugartools) + } +} + +apply(from = rootProject.file("configuration/instrumentation-tests.gradle")) diff --git a/core-kotlin/build.gradle b/core-kotlin/build.gradle deleted file mode 100644 index 3fc3d2a12e..0000000000 --- a/core-kotlin/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 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. - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -dependencies { - implementation dependency.kotlin.stdlib - implementation dependency.kotlin.coroutines - implementation dependency.androidx.core_ktx - implementation project(':core') - - testImplementation dependency.androidx.test.core - testImplementation dependency.junit - testImplementation dependency.mockk - testImplementation dependency.kotlin.test.coroutines - testImplementation project(':testmodels') -} - -afterEvaluate { - it.android.kotlinOptions.jvmTarget = '1.8' - it.android.kotlinOptions.freeCompilerArgs += [ - '-opt-in=kotlin.RequiresOptIn', - ] -} diff --git a/core-kotlin/build.gradle.kts b/core-kotlin/build.gradle.kts new file mode 100644 index 0000000000..bfa500c39a --- /dev/null +++ b/core-kotlin/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(dependency.kotlin.stdlib) + implementation(dependency.kotlin.coroutines) + implementation(dependency.androidx.core.ktx) + implementation(project(":core")) + + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockk) + testImplementation(testDependency.kotlin.test.coroutines) + testImplementation(project(":testmodels")) +} + +android.kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" +} diff --git a/core/build.gradle b/core/build.gradle deleted file mode 100644 index d74b32e3c4..0000000000 --- a/core/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023 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. - */ - -apply plugin: 'com.android.library' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") -apply plugin: 'kotlin-android' - -group = POM_GROUP - -android { - kotlinOptions{ - moduleName = "com.amplifyframework.core" - } -} - -dependencies { - implementation dependency.androidx.v4support - implementation dependency.androidx.annotation - implementation dependency.androidx.nav.fragment - implementation dependency.androidx.nav.fragmentktx - implementation dependency.androidx.nav.ui - implementation dependency.androidx.nav.uiktx - implementation dependency.androidx.security - implementation dependency.kotlin.serializationJson - - implementation dependency.aws.credentials - - testImplementation project(path: ':aws-api-appsync') // Used to reference Temporal types in tests. - testImplementation project(path: ':testmodels') - testImplementation project(path: ':testutils') - testImplementation dependency.junit - testImplementation dependency.mockito - testImplementation dependency.robolectric - testImplementation dependency.rxjava - testImplementation dependency.androidx.test.core - testImplementation dependency.jsonassert - testImplementation dependency.gson - - androidTestImplementation project(path: ':testutils') - androidTestImplementation dependency.androidx.annotation - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.runner - androidTestImplementation dependency.androidx.test.junit - androidTestImplementation dependency.androidx.test.espresso - androidTestImplementation dependency.androidx.test.navigation - androidTestImplementation dependency.androidx.test.fragment -} - -afterEvaluate { - // Disables this warning: - // warning: [classfile] MethodParameters attribute - // introduced in version 52.0 class files is ignored in - // version 51.0 class files - // Root project has -Werror, so this warning - // would fail the build, otherwise. - it.tasks.withType(JavaCompile) { - options.compilerArgs << '-Xlint:-classfile' - } -} diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000000..97a4ec0fb5 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,74 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +android { + kotlinOptions { + moduleName = "com.amplifyframework.core" + } +} + +dependencies { + implementation(dependency.androidx.v4support) + implementation(dependency.androidx.annotation) + implementation(dependency.androidx.nav.fragment) + implementation(dependency.androidx.nav.ui) + implementation(dependency.androidx.security) + implementation(dependency.kotlin.serializationJson) + + implementation(dependency.aws.credentials) + + testImplementation(project(":aws-api-appsync")) + // Used to reference Temporal types in tests. + testImplementation(project(":testmodels")) + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) + testImplementation(testDependency.robolectric) + testImplementation(dependency.rxjava) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.jsonassert) + testImplementation(dependency.gson) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(dependency.androidx.annotation) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(testDependency.androidx.test.espresso) + androidTestImplementation(testDependency.androidx.test.navigation) + androidTestImplementation(testDependency.androidx.test.fragment) +} + +afterEvaluate { + // Disables this warning: + // warning: listOf(classfile) MethodParameters attribute + // introduced in version 52.0 class files is ignored in + // version 51.0 class files + // Root project has -Werror, so this warning + // would fail the build, otherwise. + tasks.withType().configureEach { + options.compilerArgs.add("-Xlint:-classfile") + } +} diff --git a/core/src/main/java/com/amplifyframework/auth/AWSAuthSessionInternal.kt b/core/src/main/java/com/amplifyframework/auth/AWSAuthSessionInternal.kt new file mode 100644 index 0000000000..33cb2dccf8 --- /dev/null +++ b/core/src/main/java/com/amplifyframework/auth/AWSAuthSessionInternal.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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 + +import com.amplifyframework.auth.result.AuthSessionResult + +open class AWSAuthSessionInternal( + @get:JvmName("getSignedIn") + open val isSignedIn: Boolean, + open val identityIdResult: AuthSessionResult, + open val awsCredentialsResult: AuthSessionResult, + open val userSubResult: AuthSessionResult, + open val userPoolTokensResult: AuthSessionResult, +) : AuthSession(isSignedIn) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoUserPoolTokens.kt b/core/src/main/java/com/amplifyframework/auth/AWSCognitoUserPoolTokens.kt similarity index 91% rename from aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoUserPoolTokens.kt rename to core/src/main/java/com/amplifyframework/auth/AWSCognitoUserPoolTokens.kt index 24a5d1f06d..cdcb24dd49 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoUserPoolTokens.kt +++ b/core/src/main/java/com/amplifyframework/auth/AWSCognitoUserPoolTokens.kt @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -package com.amplifyframework.auth.cognito +package com.amplifyframework.auth /** * Wraps the various Cognito User Pool tokens. */ -data class AWSCognitoUserPoolTokens internal constructor( +data class AWSCognitoUserPoolTokens constructor( /** * Returns the access JWT token in its encoded string form. * @return the access JWT token in its encoded string form. diff --git a/core/src/main/java/com/amplifyframework/auth/AuthCredentialsProvider.kt b/core/src/main/java/com/amplifyframework/auth/AuthCredentialsProvider.kt index 278c1e8a3f..ec898cc843 100644 --- a/core/src/main/java/com/amplifyframework/auth/AuthCredentialsProvider.kt +++ b/core/src/main/java/com/amplifyframework/auth/AuthCredentialsProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -16,6 +16,7 @@ package com.amplifyframework.auth import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import com.amplifyframework.core.Consumer interface AuthCredentialsProvider : CredentialsProvider { /** @@ -23,4 +24,6 @@ interface AuthCredentialsProvider : CredentialsProvider { * @return identity id */ suspend fun getIdentityId(): String + + fun getAccessToken(onResult: Consumer, onFailure: Consumer) } diff --git a/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/credentials/CognitoCredentialsProvider.kt b/core/src/main/java/com/amplifyframework/auth/CognitoCredentialsProvider.kt similarity index 60% rename from aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/credentials/CognitoCredentialsProvider.kt rename to core/src/main/java/com/amplifyframework/auth/CognitoCredentialsProvider.kt index 19fb42d6ff..6da1ee0839 100644 --- a/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/credentials/CognitoCredentialsProvider.kt +++ b/core/src/main/java/com/amplifyframework/auth/CognitoCredentialsProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 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. @@ -13,37 +13,35 @@ * permissions and limitations under the License. */ -package com.amplifyframework.storage.s3.credentials +package com.amplifyframework.auth import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import com.amplifyframework.auth.AWSCredentials -import com.amplifyframework.auth.AWSTemporaryCredentials -import com.amplifyframework.auth.AuthCredentialsProvider -import com.amplifyframework.auth.AuthSession -import com.amplifyframework.auth.cognito.AWSCognitoAuthSession +import com.amplifyframework.AmplifyException import com.amplifyframework.core.Amplify +import com.amplifyframework.core.Consumer import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine /** - * Internal implementation of cognito credentials provider. - * This will be ported to core once it seems feasible to do so. + * Wrapper to provide credentials from Auth synchronously and asynchronously */ -internal class CognitoCredentialsProvider : AuthCredentialsProvider { +open class CognitoCredentialsProvider : AuthCredentialsProvider { + /** - * Request identityId from the provider. + * Request [Credentials] from the provider. */ - override suspend fun getIdentityId(): String { + override suspend fun getCredentials(): Credentials { return suspendCoroutine { continuation -> Amplify.Auth.fetchAuthSession( { authSession -> - authSession.toAWSCognitoAuthSession()?.identityIdResult?.value?.let { - continuation.resume(it) + authSession.toAWSAuthSession()?.awsCredentialsResult?.value?.let { + continuation.resume(it.toCredentials()) } ?: continuation.resumeWithException( - Exception( - "Failed to get identity ID. " + - "Check if you are signed in and configured identity pools correctly." + AuthException( + "Failed to get credentials. " + + "Check if you are signed in and configured identity pools correctly.", + AmplifyException.TODO_RECOVERY_SUGGESTION ) ) }, @@ -55,18 +53,19 @@ internal class CognitoCredentialsProvider : AuthCredentialsProvider { } /** - * Request [Credentials] from the provider. + * Request identityId from the provider. */ - override suspend fun getCredentials(): Credentials { + override suspend fun getIdentityId(): String { return suspendCoroutine { continuation -> Amplify.Auth.fetchAuthSession( { authSession -> - authSession.toAWSCognitoAuthSession()?.awsCredentialsResult?.value?.let { - continuation.resume(it.toCredentials()) + authSession.toAWSAuthSession()?.identityIdResult?.value?.let { + continuation.resume(it) } ?: continuation.resumeWithException( - Exception( - "Failed to get credentials. " + - "Check if you are signed in and configured identity pools correctly." + AuthException( + "Failed to get identity ID. " + + "Check if you are signed in and configured identity pools correctly.", + AmplifyException.TODO_RECOVERY_SUGGESTION ) ) }, @@ -76,14 +75,28 @@ internal class CognitoCredentialsProvider : AuthCredentialsProvider { ) } } -} -private fun AuthSession.toAWSCognitoAuthSession(): AWSCognitoAuthSession? { - if (this is AWSCognitoAuthSession) { - return this + override fun getAccessToken(onResult: Consumer, onFailure: Consumer) { + Amplify.Auth.fetchAuthSession( + { session -> + val tokens = session.toAWSAuthSession()?.userPoolTokensResult?.value?.accessToken + tokens?.let { onResult.accept(tokens) } + ?: onFailure.accept( + AuthException( + "Token is null", + "Token received but is null. Check if you are signed in" + ) + ) + }, + { + onFailure.accept(it) + } + ) } +} - return null +private fun AuthSession.toAWSAuthSession(): AWSAuthSessionInternal? { + return this as? AWSAuthSessionInternal } private fun AWSCredentials.toCredentials(): Credentials { diff --git a/core/src/main/java/com/amplifyframework/notifications/NotificationsCategoryConfiguration.kt b/core/src/main/java/com/amplifyframework/notifications/NotificationsCategoryConfiguration.kt index 990692536c..3395b7f9b4 100644 --- a/core/src/main/java/com/amplifyframework/notifications/NotificationsCategoryConfiguration.kt +++ b/core/src/main/java/com/amplifyframework/notifications/NotificationsCategoryConfiguration.kt @@ -17,8 +17,6 @@ package com.amplifyframework.notifications import com.amplifyframework.core.category.CategoryConfiguration import com.amplifyframework.core.category.CategoryType -import com.amplifyframework.core.category.SubCategoryType -import org.json.JSONObject /** * Configuration for Notifications category that also diff --git a/gradle.properties b/gradle.properties index 1bf935cfed..d98c3aa1e9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.jvmargs=-Xmx4g # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true -VERSION_NAME=2.1.0 +VERSION_NAME=2.2.2 POM_GROUP=com.amplifyframework POM_URL=https://github.com/aws-amplify/amplify-android diff --git a/jacoco-config.gradle b/jacoco-config.gradle deleted file mode 100644 index 5f10e02ad0..0000000000 --- a/jacoco-config.gradle +++ /dev/null @@ -1,20 +0,0 @@ -project.ext { - jacocoIgnoreList = [ - 'testutils', - 'testmodels' - ] - - // Exclude file by names, packages or types. Such files will be ignored during test coverage - // calculation - jacocoFileFilter = [ - '**/*App.*', - '**/*Application.*', - '**/*Activity.*', - '**/*Fragment.*', - '**/*View.*', - '**/*ViewGroup.*', - '**/*JsonAdapter.*', - '**/di/**', - '**/*Dagger.*' - ] -} \ No newline at end of file diff --git a/jacoco.gradle b/jacoco.gradle deleted file mode 100644 index 0220e3fca3..0000000000 --- a/jacoco.gradle +++ /dev/null @@ -1,132 +0,0 @@ -apply from: '../jacoco-config.gradle' -apply plugin: 'jacoco' - -jacoco { - toolVersion = "0.8.7" -} - -afterEvaluate { project -> - def ignoreList = jacocoIgnoreList - def projectName = project.name - if (ignoreList.contains(projectName)) { - println "Jacoco: ignoring project ${projectName}" - return false - } - setupTestExistenceValidationTask() - if (isAndroidModule(project)) { - setupAndroidReporting(project) - } else { - setupKotlinReporting() - } -} - -def setupTestExistenceValidationTask() { - task testExistenceValidation(type: TestExistenceValidation) -} - -def setupAndroidReporting(Project currentProject) { - tasks.withType(Test) { - jacoco.includeNoLocationClasses true - jacoco.excludes = ['jdk.internal.*'] - } - task jacocoTestReport( - type: JacocoReport, - dependsOn: [ - 'testExistenceValidation', - 'testDebugUnitTest' - ] - ) { - reports { - csv.enabled false - xml { - enabled true - destination file("${buildDir}/coverage-report/${currentProject.name}.xml") - } - html { - enabled true - destination file("${buildDir}/coverage-report/${currentProject.name}.html") - } - } - - final def coverageSourceDirs = [ - "$projectDir/src/main/java", - "$projectDir/src/main/kotlin" - ] - final def kotlinDebugTree = fileTree( - dir: "$buildDir/tmp/kotlin-classes/debug", - excludes: jacocoFileFilter - ) - - final def javaDebugTree = fileTree( - dir: "$buildDir/intermediates/javac/debug/classes", - excludes: jacocoFileFilter - ) - sourceDirectories.from = files(coverageSourceDirs) - classDirectories.from = files([kotlinDebugTree, javaDebugTree]) - executionData.from = fileTree( - dir: project.buildDir, - includes: ['jacoco/testDebugUnitTest.exec'] - ) - } -} - -def setupKotlinReporting() { - jacocoTestReport { - dependsOn testExistenceValidation - dependsOn test - reports { - csv.enabled false - xml { - enabled true - destination file("${buildDir}/coverage-report/${currentProject.name}.xml") - } - html.enabled false - } - } -} - -private static boolean isAndroidModule(Project project) { - def isAndroidLibrary = project.plugins.hasPlugin('com.android.library') - def isAndroidApp = project.plugins.hasPlugin('com.android.application') - return isAndroidLibrary || isAndroidApp -} - -class TestExistenceValidation extends DefaultTask { - - static final SRC_DIR = 'src' - static final JAVA_DIR = 'java' - static final TEST_DIRS = ['test', 'androidTest'] - - static final IGNORED_NAME_PATTERNS = [ - ~/^sample-.++$/ - ] - - @TaskAction - void execute() { - if (shouldSkip(project)) return - - File srcDir = new File(project.projectDir, SRC_DIR) - FileFilter filter = { it.isDirectory() } - File[] subDirs = srcDir.listFiles(filter) ?: [] - File testsDir = subDirs.find { TEST_DIRS.contains(it.name) } - if (testsDir) { - File javaTestsDir = testsDir - .listFiles(filter) - .find { it.name == JAVA_DIR } - if (javaTestsDir && javaTestsDir.list().length > 0) { - return - } - } - - throw new GradleException( - "${project.name} has no unit tests. " - ) - } - - private static boolean shouldSkip(Project project) { - def name = project.name - return IGNORED_NAME_PATTERNS - .collect { name =~ it } // convert Pattern to Matcher - .any { it.find() } - } -} \ No newline at end of file diff --git a/kover.gradle b/kover.gradle new file mode 100644 index 0000000000..48cee797a0 --- /dev/null +++ b/kover.gradle @@ -0,0 +1,62 @@ +apply plugin: 'kover' + +project.ext { + + // Exclude file by names, packages or types. Such files will be ignored during test coverage + // calculation + ignoreFileFilter = [ + '**/*App.*', + '**/*Application.*', + '**/*Activity.*', + '**/*Fragment.*', + '**/*View.*', + '**/*ViewGroup.*', + '**/*JsonAdapter.*', + '**/di/**', + '**/*Dagger.*' + ] +} + +kover{ + instrumentation { + // exclude jdk internals from instrumentation + excludeTasks.add "jdk.internal.*" + } + + xmlReport { + // set to true to run koverXmlReport task during the execution of the check task (if it exists) of the current project + onCheck.set false + + // change report file name + reportFile.set layout.buildDirectory.file("reports/kover/xml/${project.name}.xml") + overrideFilters { + // override common class filter + classes { + // override class exclusion rules + excludes.addAll ignoreFileFilter + } + } + } + + htmlReport { + // set to true to run koverMergedHtmlReport task during the execution of the check task (if it exists) of the current project + onCheck.set false + + // change report directory + reportDir.set layout.buildDirectory.dir("reports/kover/html/${project.name}/") + + overrideFilters { + // override common class filter + classes { + // override class exclusion rules + excludes.addAll ignoreFileFilter + } + } + } +} + +tasks.withType(Test) { + kover { + isDisabled = project.name.contains("test") + } +} \ No newline at end of file diff --git a/maplibre-adapter/build.gradle b/maplibre-adapter/build.gradle deleted file mode 100644 index e29585f29a..0000000000 --- a/maplibre-adapter/build.gradle +++ /dev/null @@ -1,70 +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. - */ - -plugins { - id "com.android.library" - id "kotlin-android" - id "kotlin-parcelize" -} -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -ext { - minSdkVersion = 24 -} - -android { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } -} - -dependencies { - androidTestImplementation project(path: ':maplibre-adapter') - def lifecycleVersion = "2.4.1" - - implementation project(":aws-auth-cognito") - implementation project(":aws-geo-location") - implementation project(":core") - implementation dependency.aws.signing - implementation dependency.maplibre.sdk - implementation dependency.maplibre.annotations - implementation dependency.okhttp - implementation dependency.kotlin.coroutines - - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" - //noinspection GradleDependency - implementation "com.google.android.material:material:1.4.0" - - compileOnly dependency.aws.location - - androidTestImplementation project(":testutils") - androidTestImplementation dependency.androidx.appcompat - androidTestImplementation dependency.androidx.test.junit - androidTestImplementation dependency.androidx.test.core - androidTestImplementation dependency.androidx.test.core_ktx - androidTestImplementation dependency.androidx.test.runner -} - -afterEvaluate { - it.android.kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() -} diff --git a/maplibre-adapter/build.gradle.kts b/maplibre-adapter/build.gradle.kts new file mode 100644 index 0000000000..3ab6ecd8f6 --- /dev/null +++ b/maplibre-adapter/build.gradle.kts @@ -0,0 +1,59 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") + id("kotlin-parcelize") +} +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +android { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + lint { + disable += "GradleDependency" + } +} + +dependencies { + val lifecycleVersion = "2.4.1" + + implementation(project(":aws-auth-cognito")) + implementation(project(":aws-geo-location")) + implementation(project(":core")) + implementation(dependency.aws.signing) + implementation(dependency.maplibre.sdk) + implementation(dependency.maplibre.annotations) + implementation(dependency.okhttp) + implementation(dependency.kotlin.coroutines) + + implementation(dependency.androidx.lifecycle.runtime) + implementation(dependency.google.material) + + compileOnly(dependency.aws.location) + + androidTestImplementation(project(":testutils")) + androidTestImplementation(dependency.androidx.appcompat) + androidTestImplementation(testDependency.androidx.test.junit) + androidTestImplementation(testDependency.androidx.test.core) + androidTestImplementation(testDependency.androidx.test.core.ktx) + androidTestImplementation(testDependency.androidx.test.runner) + androidTestImplementation(dependency.kotlin.coroutines.android) +} diff --git a/rxbindings/README.md b/rxbindings/README.md index fbf53809a5..ecefbf9ee9 100644 --- a/rxbindings/README.md +++ b/rxbindings/README.md @@ -24,7 +24,7 @@ library. In your module's `build.gradle`: ```gradle dependencies { // Add this line. - implementation 'com.amplifyframework:rxbindings:2.1.0' + implementation 'com.amplifyframework:rxbindings:2.2.2' } ``` diff --git a/rxbindings/build.gradle b/rxbindings/build.gradle deleted file mode 100644 index 1067f0d1a3..0000000000 --- a/rxbindings/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id "com.android.library" - id "kotlin-android" -} - -apply from: rootProject.file("configuration/checkstyle.gradle") -apply from: rootProject.file("configuration/publishing.gradle") - -group = POM_GROUP - -dependencies { - implementation project(path: ':core') - implementation dependency.androidx.annotation - implementation dependency.androidx.appcompat - implementation dependency.rxjava - - testImplementation project(path: ':testutils') - testImplementation dependency.junit - testImplementation dependency.mockito - testImplementation dependency.mockk - testImplementation dependency.androidx.test.core - testImplementation dependency.robolectric - testImplementation project(path: ':rxbindings') -} diff --git a/rxbindings/build.gradle.kts b/rxbindings/build.gradle.kts new file mode 100644 index 0000000000..931070525b --- /dev/null +++ b/rxbindings/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * 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. + */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) +apply(from = rootProject.file("configuration/publishing.gradle")) + +group = properties["POM_GROUP"].toString() + +dependencies { + implementation(project(":core")) + implementation(dependency.androidx.annotation) + implementation(dependency.androidx.appcompat) + implementation(dependency.rxjava) + + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) + testImplementation(testDependency.mockito) + testImplementation(testDependency.mockk) + testImplementation(testDependency.androidx.test.core) + testImplementation(testDependency.robolectric) + testImplementation(project(":rxbindings")) +} diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsBinding.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsBinding.java index 48d1d2bfdb..36a3ad7888 100644 --- a/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsBinding.java +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsBinding.java @@ -25,7 +25,6 @@ import com.amplifyframework.notifications.pushnotifications.PushNotificationsCategoryBehavior; import com.amplifyframework.notifications.pushnotifications.PushNotificationsException; -import java.util.Map; import java.util.Objects; import io.reactivex.rxjava3.annotations.Nullable; diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsCategoryBehavior.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsCategoryBehavior.java index 54364faac5..cc6d9df5c9 100644 --- a/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsCategoryBehavior.java +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxPushNotificationsCategoryBehavior.java @@ -20,8 +20,6 @@ import com.amplifyframework.notifications.pushnotifications.PushNotificationResult; import com.amplifyframework.notifications.pushnotifications.PushNotificationsException; -import java.util.Map; - import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Single; diff --git a/rxbindings/src/test/java/com/amplifyframework/rx/RxPushNotificationsBindingTest.java b/rxbindings/src/test/java/com/amplifyframework/rx/RxPushNotificationsBindingTest.java index 86819fd064..1a03a359d9 100644 --- a/rxbindings/src/test/java/com/amplifyframework/rx/RxPushNotificationsBindingTest.java +++ b/rxbindings/src/test/java/com/amplifyframework/rx/RxPushNotificationsBindingTest.java @@ -29,8 +29,6 @@ import org.junit.Before; import org.junit.Test; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.TimeUnit; import io.reactivex.rxjava3.observers.TestObserver; diff --git a/scripts/devicefarm-test-runner-buildspec.yml b/scripts/devicefarm-test-runner-buildspec.yml deleted file mode 100644 index f6a1ee786b..0000000000 --- a/scripts/devicefarm-test-runner-buildspec.yml +++ /dev/null @@ -1,86 +0,0 @@ -version: 0.2 -# This file is used as part of the build process implemented using AWS CodeBuild [1]. -# The CodeBuild documentation [2] provides an introduction of the service and its capabilities. In general -# terms, it allows us to configure the actions executed when a build is triggered by a commit to the -# amplify-android GitHub repository. -# -# For the purposes of this repo, we're simply running a build by using "./gradlew build" -# command and in the post-build phase, we consolidate the JUnit report files in one location. -# In the reports section, we're instructing CodeBuild to publish those reports which can be -# viewed in the AWS CodeBuild console (Future work will be to publish those to Github for easier access). -# -# [1] - https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html -# [2] - https://docs.aws.amazon.com/codebuild/latest/userguide/planning.html -env: - shell: /bin/sh -phases: - install: - runtime-versions: - nodejs: 12 - python: 3.8 - java: corretto11 - commands: - - echo 'Install phase starting' - - npm install -g xunit-viewer - - pip3 install junit-xml - ### INSTALL ANDROID 31 - - export ANDROID_TOOLS_FILENAME="commandlinetools-linux-9123335_latest.zip" - - wget https://dl.google.com/android/repository/$ANDROID_TOOLS_FILENAME -P ~ > /dev/null - - unzip ~/$ANDROID_TOOLS_FILENAME -d ~ > /dev/null 2>&1 - - mkdir -p /usr/local/android-sdk-linux/cmdline-tools - - mv ~/cmdline-tools /usr/local/android-sdk-linux/cmdline-tools/latest - - export PATH=/usr/local/android-sdk-linux/cmdline-tools/latest:/usr/local/android-sdk-linux/cmdline-tools/latest/bin:/usr/local/android-sdk-linux/platform-tools:$PATH - - export ANDROID_SDK_ROOT=/usr/local/android-sdk-linux - - yes | sdkmanager --licenses > /dev/null - - sdkmanager "platform-tools" "platforms;android-31" > /dev/null - - sdkmanager "build-tools;31.0.0" > /dev/null - ### END INSTALL ANDROID 31 - finally: - - echo 'Install phase completed.' - pre_build: - commands: - - echo 'Pre-build phase starting' - - mkdir -p build/allTests - - | - if [[ -z "${CONFIG_SOURCE_BUCKET}" ]]; then - echo 'Pulling config files from Amplify' - JAVA_HOME=$JDK_11_HOME ./gradlew pullBackendConfigFromAmplify - else - echo 'Pulling config files from S3' - ./scripts/pull_backend_config_from_s3 ${CONFIG_SOURCE_BUCKET} - fi - finally: - - echo 'Pre-build phase completed.' - build: - commands: - - echo 'Build phase starting.' - - JAVA_HOME=$JDK_11_HOME ./gradlew assembleAndroidTest - - JAVA_HOME=$JDK_11_HOME ./gradlew runTestsInDeviceFarm - finally: - - echo 'Build phase completed.' - post_build: - commands: - - echo 'Post-build phase starting' - - mkdir -p build/reports/instrumented - - xunit-viewer -r build/allTests -o build/reports/instrumented/${CODEBUILD_RESOLVED_SOURCE_VERSION}.html - finally: - - echo 'Post-build phase completed.' -reports: - amplify-android-devicefarm-tests: - files: - - '**/*' - base-directory: 'build/allTests' - discard-paths: no - file-format: JUNITXML -artifacts: - files: - - '**/*.apk' - name: AmplifyAndroidCatApks - discard-paths: yes - secondary-artifacts: - reports: - files: - - '**/*' - name: DevicefarmTestRunReport - base-directory: 'build/reports/instrumented' - discard-paths: no diff --git a/scripts/fastlane/Fastfile b/scripts/fastlane/Fastfile index 7ad67f618c..41e1393181 100644 --- a/scripts/fastlane/Fastfile +++ b/scripts/fastlane/Fastfile @@ -23,16 +23,8 @@ platform :android do |options| doc_files_to_update: ["#{project_root}/README.md", "#{project_root}/rxbindings/README.md"], release_title: 'Amplify Android', changelog_path: "#{project_root}/CHANGELOG.md", - }, - { - release_tag_prefix: 'release-kotlin_v', - gradle_properties_path: "#{project_root}/core-kotlin/gradle.properties", - doc_files_to_update: [], - release_title: 'Amplify Android Kotlin Facade', - changelog_path: "#{project_root}/core-kotlin/CHANGELOG.md", } ] } end end - diff --git a/scripts/maven-release-publisher.yml b/scripts/maven-release-publisher.yml deleted file mode 100644 index 74f85f63de..0000000000 --- a/scripts/maven-release-publisher.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: 0.2 -env: - shell: /bin/sh - secrets-manager: - ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: awsmobilesdk/android/sonatype:username - ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: awsmobilesdk/android/sonatype:password - ORG_GRADLE_PROJECT_signingPassword: awsmobilesdk/android/signing:password - ORG_GRADLE_PROJECT_signingKeyId: awsmobilesdk/android/signing:keyId - ORG_GRADLE_PROJECT_signingInMemoryKey: awsmobilesdk/android/signing:inMemoryKey -phases: - install: - runtime-versions: - java: corretto11 - commands: - - echo 'Install phase starting' - finally: - - echo 'Install phase completed.' - pre_build: - commands: - - echo 'Pre-build phase starting' - finally: - - echo 'Pre-build phase completed.' - build: - commands: - - echo 'Build phase starting.' - - | - # List all available gradle tasks, grep for the uploadArchive tasks, and then use cut to strip the - # task description and just return the name of the task, one for each module (e.g. aws-api:publish) - JAVA_HOME=$JDK_11_HOME ./gradlew clean build - for task_name in $(./gradlew tasks --all | grep ":publish " | cut -d " " -f 1); do - echo "Gradle task $task_name" - JAVA_HOME=$JDK_11_HOME ./gradlew $task_name; - done - finally: - - echo 'Build phase completed.' - post_build: - commands: - - echo 'Post-build phase starting' - finally: - - echo 'Post-build phase completed.' diff --git a/scripts/pr-builder-buildspec.yml b/scripts/pr-builder-buildspec.yml deleted file mode 100644 index 8c44300bec..0000000000 --- a/scripts/pr-builder-buildspec.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 0.2 -# This file is used as part of the build process implemented using AWS CodeBuild [1]. -# The CodeBuild documentation [2] provides an introduction of the service and its capabilities. In general -# terms, it allows us to configure the actions executed when a build is triggered by a commit to the -# amplify-android GitHub repository. -# -# For the purposes of this repo, we're simply running a build by using "./gradlew build" -# command and in the post-build phase, we consolidate the JUnit report files in one location. -# In the reports section, we're instructing CodeBuild to publish those reports which can be -# viewed in the AWS CodeBuild console (Future work will be to publish those to Github for easier access). -# -# [1] - https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html -# [2] - https://docs.aws.amazon.com/codebuild/latest/userguide/planning.html -env: - shell: /bin/sh -phases: - install: - runtime-versions: - nodejs: 12 - java: corretto11 - commands: - - echo 'Install phase starting' - ### INSTALL ANDROID 31 - - export ANDROID_TOOLS_FILENAME="commandlinetools-linux-9123335_latest.zip" - - wget https://dl.google.com/android/repository/$ANDROID_TOOLS_FILENAME -P ~ > /dev/null - - unzip ~/$ANDROID_TOOLS_FILENAME -d ~ > /dev/null 2>&1 - - mkdir -p /usr/local/android-sdk-linux/cmdline-tools - - mv ~/cmdline-tools /usr/local/android-sdk-linux/cmdline-tools/latest - - export PATH=/usr/local/android-sdk-linux/cmdline-tools/latest:/usr/local/android-sdk-linux/cmdline-tools/latest/bin:/usr/local/android-sdk-linux/platform-tools:$PATH - - export ANDROID_SDK_ROOT=/usr/local/android-sdk-linux - - yes | sdkmanager --licenses > /dev/null - - sdkmanager "platform-tools" "platforms;android-31" > /dev/null - - sdkmanager "build-tools;31.0.0" > /dev/null - ### END INSTALL ANDROID 31 - finally: - - echo 'Install phase completed.' - pre_build: - commands: - - echo 'Pre-build phase starting' - finally: - - echo 'Pre-build phase completed.' - build: - commands: - - echo 'Build phase starting.' - - JAVA_HOME=$JDK_11_HOME ./gradlew build - finally: - - echo 'Build phase completed.' - post_build: - commands: - - echo 'Post-build phase starting' - - pwd - - mkdir -p build/allTests - - cp -f ./*/build/test-results/testDebugUnitTest/*.xml ./build/allTests - finally: - - echo 'Post-build phase completed.' -reports: - amplify-android-unit-tests: - files: - - build/allTests/* - discard-paths: yes - file-format: JUNITXML diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 5884b5e5c0..0000000000 --- a/settings.gradle +++ /dev/null @@ -1,39 +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. - */ - -include ':core' - -// Plugin Modules -include ':aws-analytics-pinpoint' -include ':aws-api' -include ':aws-auth-cognito' -include ':aws-datastore' -include ':aws-geo-location' -include ':aws-predictions' -include ':aws-predictions-tensorflow' -include ':aws-push-notifications-pinpoint' -include ':aws-storage-s3' - -// Test Utilities and assets -include ':testutils' -include ':testmodels' - -// Bindings and accessory modules -include ':core-kotlin' -include ':rxbindings' -include ':aws-api-appsync' -include ':maplibre-adapter' -include ':aws-analytics-pinpoint-targeting' -include ':aws-push-notifications-pinpoint-utils' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..ffc843e7cd --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,163 @@ +/* + * Copyright 2023 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. + */ + +dependencyResolutionManagement { + repositories { + maven { + url = uri("https://aws.oss.sonatype.org/content/repositories/snapshots/") + } + google() + mavenCentral() + } + versionCatalogs { + // Versions of libraries that have both production and test code + val navigationVersion = "2.3.4" + + // Dependencies for testing + create("testDependency") { + // JUnit + library("junit", "junit:junit:4.13.2") + + // Mockito + library("mockito", "org.mockito:mockito-core:3.9.0") + library("mockitoinline", "org.mockito:mockito-inline:3.11.2") + + // MockK + version("mockk", "1.12.3") + library("mockk", "io.mockk", "mockk").versionRef("mockk") + library("mockk-android", "io.mockk", "mockk-android").versionRef("mockk") + + // Kotlin + library("kotlin-test-junit", "org.jetbrains.kotlin:kotlin-test-junit:1.5.31") + library("kotlin-test-coroutines", "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.3") + library("kotlin-test-kotlinTest", "org.jetbrains.kotlin:kotlin-test:1.7.10") + library("kotlin-reflection", "org.jetbrains.kotlin:kotlin-reflect:1.7.10") + + // AndroidX + version("navigation", navigationVersion) + library("androidx-test-core", "androidx.test:core:1.3.0") + library("androidx-test-core-ktx", "androidx.test:core-ktx:1.3.0") + library("androidx-test-runner", "androidx.test:runner:1.3.0") + library("androidx-test-junit", "androidx.test.ext:junit:1.1.2") + library("androidx-test-espresso", "androidx.test.espresso:espresso-core:3.3.0") + library("androidx-test-orchestrator", "androidx.test:orchestrator:1.3.0") + library("androidx-test-navigation", "androidx.navigation", "navigation-testing").versionRef("navigation") + library("androidx-test-fragment", "androidx.fragment:fragment-testing:1.3.1") + library("androidx-test-workmanager", "androidx.work:work-testing:2.7.1") + + // Misc + library("mockwebserver", "com.squareup.okhttp3:mockwebserver:5.0.0-alpha.9") + library("robolectric", "org.robolectric:robolectric:4.7") + library("jsonassert", "org.skyscreamer:jsonassert:1.5.0") + library("json", "org.json:json:20210307") + } + // Library dependencies + create("dependency") { + // Android Tools + library("android-desugartools", "com.android.tools:desugar_jdk_libs:1.2.0") + + // AndroidX + val nagivation = "navigation" + version(nagivation, navigationVersion) + library("androidx-v4support", "androidx.legacy:legacy-support-v4:1.0.0") + library("androidx-activity", "androidx.activity:activity:1.2.0") + library("androidx-annotation", "androidx.annotation:annotation:1.1.0") + library("androidx-appcompat", "androidx.appcompat:appcompat:1.2.0") + library("androidx-browser", "androidx.browser:browser:1.4.0") + library("androidx-core", "androidx.core:core:1.3.2") + library("androidx-core-ktx", "androidx.core:core-ktx:1.3.2") + library("androidx-workmanager", "androidx.work:work-runtime-ktx:2.7.1") + library("androidx-security", "androidx.security:security-crypto:1.0.0") + library("androidx-nav-fragment", "androidx.navigation", "navigation-fragment").versionRef(nagivation) + library("androidx-nav-ui", "androidx.navigation", "navigation-ui").versionRef(nagivation) + library("androidx-lifecycle-runtime", "androidx.lifecycle:lifecycle-runtime:2.4.1") + + // AWS + val awsKotlinSdk = "awsKotlinSdk" + version(awsKotlinSdk, "0.17.12-beta") + + library("aws-sdk-core", "com.amazonaws:aws-android-sdk-core:2.62.2") + + library("aws-credentials", "aws.smithy.kotlin:aws-credentials:0.12.6") + library("aws-ktor", "aws.smithy.kotlin:http-client-engine-ktor:0.7.7") + library("aws-signing", "aws.smithy.kotlin:aws-signing-default:0.12.6") + library("aws-cognitoidentity", "aws.sdk.kotlin", "cognitoidentity").versionRef(awsKotlinSdk) + library( + "aws-cognitoidentityprovider", + "aws.sdk.kotlin", + "cognitoidentityprovider" + ).versionRef(awsKotlinSdk) + library("aws-comprehend", "aws.sdk.kotlin", "comprehend").versionRef(awsKotlinSdk) + library("aws-location", "aws.sdk.kotlin", "location").versionRef(awsKotlinSdk) + library("aws-s3", "aws.sdk.kotlin", "s3").versionRef(awsKotlinSdk) + library("aws-pinpoint", "aws.sdk.kotlin", "pinpoint").versionRef(awsKotlinSdk) + library("aws-polly", "aws.sdk.kotlin", "polly").versionRef(awsKotlinSdk) + library("aws-rekognition", "aws.sdk.kotlin", "rekognition").versionRef(awsKotlinSdk) + library("aws-textract", "aws.sdk.kotlin", "textract").versionRef(awsKotlinSdk) + library("aws-translate", "aws.sdk.kotlin", "translate").versionRef(awsKotlinSdk) + + // Kotlin + library("kotlin-stdlib", "org.jetbrains.kotlin:kotlin-stdlib:1.7.10") + library("kotlin-coroutines", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3") + library("kotlin-coroutines-android", "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3") + library("kotlin-serializationJson", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") + library("kotlin-futures", "androidx.concurrent:concurrent-futures-ktx:1.1.0") + + // MapLibre + library("maplibre-sdk", "org.maplibre.gl:android-sdk:9.5.2") + library("maplibre-annotations", "org.maplibre.gl:android-plugin-annotation-v9:1.0.0") + + // RxJava + library("rxandroid", "io.reactivex.rxjava3:rxandroid:3.0.0") + library("rxjava", "io.reactivex.rxjava3:rxjava:3.0.6") + + // Google + library("google-material", "com.google.android.material:material:1.4.0") + library("firebasemessaging", "com.google.firebase:firebase-messaging-ktx:23.1.0") + + // Misc + library("oauth2", "com.google.auth:google-auth-library-oauth2-http:0.26.0") + library("okhttp", "com.squareup.okhttp3:okhttp:5.0.0-alpha.9") + library("gson", "com.google.code.gson:gson:2.8.9") + library("tensorflow", "org.tensorflow:tensorflow-lite:2.0.0") + library("uuidgen", "com.fasterxml.uuid:java-uuid-generator:4.0.1") + } + } +} + +include(":core") + +// Plugin Modules +include(":aws-analytics-pinpoint") +include(":aws-api") +include(":aws-auth-cognito") +include(":aws-datastore") +include(":aws-geo-location") +include(":aws-predictions") +include(":aws-predictions-tensorflow") +include(":aws-push-notifications-pinpoint") +include(":aws-storage-s3") + +// Test Utilities and assets +include(":testutils") +include(":testmodels") + +// Bindings and accessory modules +include(":core-kotlin") +include(":rxbindings") +include(":aws-api-appsync") +include(":maplibre-adapter") +include(":aws-analytics-pinpoint-targeting") +include(":aws-push-notifications-pinpoint-utils") diff --git a/testmodels/build.gradle b/testmodels/build.gradle.kts similarity index 59% rename from testmodels/build.gradle rename to testmodels/build.gradle.kts index 35b64d5727..c1eb67961d 100644 --- a/testmodels/build.gradle +++ b/testmodels/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -13,15 +13,16 @@ * permissions and limitations under the License. */ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id("com.android.library") + id("kotlin-android") +} dependencies { - implementation project(path: ':core') - implementation project(path: ':aws-api-appsync') - implementation dependency.androidx.core + implementation(project(":core")) + implementation(project(":aws-api-appsync")) + implementation(dependency.androidx.core) - testImplementation project(path: ':testutils') - testImplementation dependency.junit + testImplementation(project(":testutils")) + testImplementation(testDependency.junit) } - diff --git a/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/Phone.java b/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/Phone.java index f91dae0fb5..18002c5104 100644 --- a/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/Phone.java +++ b/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/Phone.java @@ -38,10 +38,10 @@ public final class Phone implements Model { public static final QueryField ID = field("Phone", "id"); public static final QueryField NUMBER = field("Phone", "number"); - public static final QueryField OWNED_BY = field("Phone", "ownedById"); + public static final QueryField OWNER_OF_PHONE = field("Phone", "ownerOfPhoneId"); private final @ModelField(targetType="ID", isRequired = true) String id; private final @ModelField(targetType="String", isRequired = true) String number; - private final @ModelField(targetType="Person", isRequired = true) @BelongsTo(targetName = "ownedById", type = Person.class) Person ownedBy; + private final @ModelField(targetType="Person", isRequired = true) @BelongsTo(targetName = "ownerOfPhoneId", type = Person.class) Person ownerOfPhone; private final @ModelField(targetType="Call") @HasMany(associatedWith = "id", type = Call.class) List calls = null; private @ModelField(targetType="AWSDateTime", isReadOnly = true) Temporal.DateTime createdAt; private @ModelField(targetType="AWSDateTime", isReadOnly = true) Temporal.DateTime updatedAt; @@ -58,8 +58,8 @@ public String getNumber() { return number; } - public Person getOwnedBy() { - return ownedBy; + public Person getOwnerOfPhone() { + return ownerOfPhone; } public List getCalls() { @@ -74,10 +74,10 @@ public Temporal.DateTime getUpdatedAt() { return updatedAt; } - private Phone(String id, String number, Person ownedBy) { + private Phone(String id, String number, Person ownerOfPhone) { this.id = id; this.number = number; - this.ownedBy = ownedBy; + this.ownerOfPhone = ownerOfPhone; } @Override @@ -90,7 +90,7 @@ public boolean equals(Object obj) { Phone phone = (Phone) obj; return ObjectsCompat.equals(getId(), phone.getId()) && ObjectsCompat.equals(getNumber(), phone.getNumber()) && - ObjectsCompat.equals(getOwnedBy(), phone.getOwnedBy()) && + ObjectsCompat.equals(getOwnerOfPhone(), phone.getOwnerOfPhone()) && ObjectsCompat.equals(getCreatedAt(), phone.getCreatedAt()) && ObjectsCompat.equals(getUpdatedAt(), phone.getUpdatedAt()); } @@ -101,7 +101,7 @@ public int hashCode() { return new StringBuilder() .append(getId()) .append(getNumber()) - .append(getOwnedBy()) + .append(getOwnerOfPhone()) .append(getCreatedAt()) .append(getUpdatedAt()) .toString() @@ -114,7 +114,7 @@ public String toString() { .append("Phone {") .append("id=" + String.valueOf(getId()) + ", ") .append("number=" + String.valueOf(getNumber()) + ", ") - .append("ownedBy=" + String.valueOf(getOwnedBy()) + ", ") + .append("ownerOfPhone=" + String.valueOf(getOwnerOfPhone()) + ", ") .append("createdAt=" + String.valueOf(getCreatedAt()) + ", ") .append("updatedAt=" + String.valueOf(getUpdatedAt())) .append("}") @@ -154,14 +154,14 @@ public static Phone justId(String id) { public CopyOfBuilder copyOfBuilder() { return new CopyOfBuilder(id, number, - ownedBy); + ownerOfPhone); } public interface NumberStep { - OwnedByStep number(String number); + OwnerOfPhoneStep number(String number); } - public interface OwnedByStep { - BuildStep ownedBy(Person ownedBy); + public interface OwnerOfPhoneStep { + BuildStep ownerOfPhone(Person ownerOfPhone); } @@ -171,10 +171,10 @@ public interface BuildStep { } - public static class Builder implements NumberStep, OwnedByStep, BuildStep { + public static class Builder implements NumberStep, OwnerOfPhoneStep, BuildStep { private String id; private String number; - private Person ownedBy; + private Person ownerOfPhone; @Override public Phone build() { String id = this.id != null ? this.id : UUID.randomUUID().toString(); @@ -182,19 +182,19 @@ public Phone build() { return new Phone( id, number, - ownedBy); + ownerOfPhone); } @Override - public OwnedByStep number(String number) { + public OwnerOfPhoneStep number(String number) { Objects.requireNonNull(number); this.number = number; return this; } @Override - public BuildStep ownedBy(Person ownedBy) { - this.ownedBy = ownedBy; + public BuildStep ownerOfPhone(Person ownerOfPhone) { + this.ownerOfPhone = ownerOfPhone; return this; } @@ -221,10 +221,10 @@ public BuildStep id(String id) throws IllegalArgumentException { public final class CopyOfBuilder extends Builder { - private CopyOfBuilder(String id, String number, Person ownedBy) { + private CopyOfBuilder(String id, String number, Person ownerOfPhone) { super.id(id); super.number(number) - .ownedBy(ownedBy); + .ownerOfPhone(ownerOfPhone); } @Override @@ -233,8 +233,8 @@ public CopyOfBuilder number(String number) { } @Override - public CopyOfBuilder ownedBy(Person ownedBy) { - return (CopyOfBuilder) super.ownedBy(ownedBy); + public CopyOfBuilder ownerOfPhone(Person ownerOfPhone) { + return (CopyOfBuilder) super.ownerOfPhone(ownerOfPhone); } } diff --git a/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/schema.graphql b/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/schema.graphql index def2393f69..802f32ca0d 100644 --- a/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/schema.graphql +++ b/testmodels/src/main/java/com/amplifyframework/testmodels/phonecall/schema.graphql @@ -6,8 +6,8 @@ type Person @model { type Phone @model { id: ID! number: String! - ownedById: ID! - ownedBy: Person! @connection(fields: ["ownedById"]) + ownerOfPhoneId: ID! + ownerOfPhone: Person! @connection(fields: ["ownerOfPhoneId"]) calls: [Call] @connection(fields: ["id"]) } diff --git a/testutils/build.gradle b/testutils/build.gradle deleted file mode 100644 index 1558685304..0000000000 --- a/testutils/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019 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. - */ - -plugins { - id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10" -} - -apply plugin: 'com.android.library' -apply from: rootProject.file("configuration/checkstyle.gradle") -apply plugin: 'kotlin-android' - -dependencies { - implementation project(path: ':core') - implementation dependency.junit - implementation dependency.mockito - implementation dependency.androidx.test.core - implementation dependency.rxjava - - implementation dependency.kotlin.serializationJson - implementation dependency.aws.cognitoidentity - implementation dependency.aws.cognitoidentityprovider - - implementation project(path: ':aws-auth-cognito') - - // dependency on Model/GraphQL integration classes - // remove when modules are re-organized to provide better isolation - compileOnly project(path: ':aws-api') -} diff --git a/testutils/build.gradle.kts b/testutils/build.gradle.kts new file mode 100644 index 0000000000..cfa6d606b2 --- /dev/null +++ b/testutils/build.gradle.kts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +plugins { + id("org.jetbrains.kotlin.plugin.serialization") version "1.6.10" + id("com.android.library") + id("kotlin-android") +} + +apply(from = rootProject.file("configuration/checkstyle.gradle")) + +dependencies { + implementation(project(":core")) + implementation(testDependency.junit) + implementation(testDependency.mockito) + implementation(testDependency.androidx.test.core) + implementation(dependency.rxjava) + + implementation(dependency.kotlin.serializationJson) + implementation(dependency.aws.cognitoidentity) + implementation(dependency.aws.cognitoidentityprovider) + + // dependency on Model/GraphQL integration classes + // remove when modules are re-organized to provide better isolation + compileOnly(project(":aws-api")) +} diff --git a/testutils/src/main/java/com/amplifyframework/testutils/RepeatRule.kt b/testutils/src/main/java/com/amplifyframework/testutils/RepeatRule.kt new file mode 100644 index 0000000000..482f95b438 --- /dev/null +++ b/testutils/src/main/java/com/amplifyframework/testutils/RepeatRule.kt @@ -0,0 +1,69 @@ +package com.amplifyframework.testutils + +/* + * Copyright 2023 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. + */ + +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Rule to repeatedly run a test + * usage: + * ``` + * @get:Rule + * val repeatRule = RepeatRule() + * + * @Test + * @Repeat(100) + * fun testToBeRepeated() { + * ... + * } + * ``` + */ +class RepeatRule : TestRule { + private class RepeatStatement( + private val statement: Statement, + private val repeat: Int + ) : + Statement() { + @Throws(Throwable::class) + override fun evaluate() { + for (i in 0 until repeat) { + statement.evaluate() + } + } + } + + override fun apply( + statement: Statement, + description: Description + ): Statement { + var result = statement + val repeat: Repeat = description.getAnnotation(Repeat::class.java) as Repeat + val times: Int = repeat.value + result = RepeatStatement(statement, times) + return result + } +} + +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.ANNOTATION_CLASS +) +annotation class Repeat(val value: Int = 1)