diff --git a/.github/workflows/deploy_package.yml b/.github/workflows/deploy_package.yml index ddccd83258..209bcf863a 100644 --- a/.github/workflows/deploy_package.yml +++ b/.github/workflows/deploy_package.yml @@ -11,6 +11,7 @@ on: permissions: id-token: write contents: write + actions: write jobs: build-amplify-swift: diff --git a/.github/workflows/deploy_release.yml b/.github/workflows/deploy_release.yml index 71a913672d..62996eebe5 100644 --- a/.github/workflows/deploy_release.yml +++ b/.github/workflows/deploy_release.yml @@ -7,6 +7,7 @@ on: permissions: id-token: write contents: write + actions: write jobs: release-stable: diff --git a/.github/workflows/deploy_unstable.yml b/.github/workflows/deploy_unstable.yml index f0f02c7cad..7a91b886b5 100644 --- a/.github/workflows/deploy_unstable.yml +++ b/.github/workflows/deploy_unstable.yml @@ -7,6 +7,7 @@ on: permissions: id-token: write contents: write + actions: write jobs: release-unstable: diff --git a/.github/workflows/integ_test.yml b/.github/workflows/integ_test.yml index daf6be7467..e1e08c6b41 100644 --- a/.github/workflows/integ_test.yml +++ b/.github/workflows/integ_test.yml @@ -20,7 +20,7 @@ concurrency: jobs: prepare-for-test: - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b diff --git a/.github/workflows/integ_test_analytics.yml b/.github/workflows/integ_test_analytics.yml index 8434da20ac..8dccaee2d8 100644 --- a/.github/workflows/integ_test_analytics.yml +++ b/.github/workflows/integ_test_analytics.yml @@ -1,6 +1,22 @@ name: Integration Tests | Analytics on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: analytics-integration-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/Analytics/Tests/AnalyticsHostApp scheme: AWSPinpointAnalyticsPluginIntegrationTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + xcode_path: '/Applications/Xcode_14.3.app' analytics-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' analytics-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api.yml b/.github/workflows/integ_test_api.yml index 8877438890..f4966eeccd 100644 --- a/.github/workflows/integ_test_api.yml +++ b/.github/workflows/integ_test_api.yml @@ -14,7 +14,7 @@ concurrency: jobs: prepare-for-test: - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b diff --git a/.github/workflows/integ_test_api_functional.yml b/.github/workflows/integ_test_api_functional.yml index 9d0ddcfcab..ac1db905ed 100644 --- a/.github/workflows/integ_test_api_functional.yml +++ b/.github/workflows/integ_test_api_functional.yml @@ -1,6 +1,22 @@ name: Integration Tests | API - Functional on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: api-functional-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginFunctionalTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-functional-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' api-functional-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_graphql_auth_directive.yml b/.github/workflows/integ_test_api_graphql_auth_directive.yml index 2639e66a6d..22f1fc95be 100644 --- a/.github/workflows/integ_test_api_graphql_auth_directive.yml +++ b/.github/workflows/integ_test_api_graphql_auth_directive.yml @@ -1,6 +1,17 @@ name: Integration Tests | API - GraphQL Auth Directive on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +20,8 @@ permissions: jobs: api-graphql-auth-directive-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +44,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginGraphQLAuthDirectiveTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-graphql-auth-directive-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_graphql_iam.yml b/.github/workflows/integ_test_api_graphql_iam.yml index b0aa175c33..c5cdfa6082 100644 --- a/.github/workflows/integ_test_api_graphql_iam.yml +++ b/.github/workflows/integ_test_api_graphql_iam.yml @@ -1,6 +1,17 @@ name: Integration Tests | API - GraphQL IAM on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +20,8 @@ permissions: jobs: api-graphql-iam-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +44,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginGraphQLIAMTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-graphql-iam-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_graphql_lambda_auth.yml b/.github/workflows/integ_test_api_graphql_lambda_auth.yml index cb1e3e7de5..5ed3b0cda9 100644 --- a/.github/workflows/integ_test_api_graphql_lambda_auth.yml +++ b/.github/workflows/integ_test_api_graphql_lambda_auth.yml @@ -1,6 +1,22 @@ name: Integration Tests | API - GraphQL Lambda on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: api-graphql-lambda-auth-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginGraphQLLambdaAuthTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-graphql-lambda-auth-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' api-graphql-lambda-auth-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_graphql_lazy_load.yml b/.github/workflows/integ_test_api_graphql_lazy_load.yml index bf79ca762e..e731ebe3f0 100644 --- a/.github/workflows/integ_test_api_graphql_lazy_load.yml +++ b/.github/workflows/integ_test_api_graphql_lazy_load.yml @@ -1,6 +1,17 @@ name: Integration Tests | API - GraphQL Lazy Load on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +20,8 @@ permissions: jobs: api-lazy-load-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +44,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginLazyLoadTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-lazy-load-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_graphql_user_pool.yml b/.github/workflows/integ_test_api_graphql_user_pool.yml index aaffbcb06f..75944dc9ca 100644 --- a/.github/workflows/integ_test_api_graphql_user_pool.yml +++ b/.github/workflows/integ_test_api_graphql_user_pool.yml @@ -1,6 +1,17 @@ name: Integration Tests | API - GraphQL User Pool on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +20,8 @@ permissions: jobs: api-graphql-user-pool-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +44,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginGraphQLUserPoolTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-graphql-user-pool-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_rest_iam.yml b/.github/workflows/integ_test_api_rest_iam.yml index f096b24880..125c884f2c 100644 --- a/.github/workflows/integ_test_api_rest_iam.yml +++ b/.github/workflows/integ_test_api_rest_iam.yml @@ -1,6 +1,22 @@ name: Integration Tests | API - REST IAM on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: api-rest-iam-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginRESTIAMTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-rest-iam-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' api-rest-iam-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_api_rest_user_pool.yml b/.github/workflows/integ_test_api_rest_user_pool.yml index 64134ef20e..186c457baa 100644 --- a/.github/workflows/integ_test_api_rest_user_pool.yml +++ b/.github/workflows/integ_test_api_rest_user_pool.yml @@ -1,6 +1,17 @@ name: Integration Tests | API - REST User Pool on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +20,8 @@ permissions: jobs: api-rest-user-pool-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +44,11 @@ jobs: with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp scheme: AWSAPIPluginRESTUserPoolTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' api-rest-user-pool-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_auth.yml b/.github/workflows/integ_test_auth.yml index 1a76717c21..277335edca 100644 --- a/.github/workflows/integ_test_auth.yml +++ b/.github/workflows/integ_test_auth.yml @@ -1,6 +1,27 @@ name: Integration Tests | Auth on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean + hostedui-ios: + description: '🌵 HostedUI iOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +30,8 @@ permissions: jobs: auth-integration-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +54,11 @@ jobs: with: project_path: ./AmplifyPlugins/Auth/Tests/AuthHostApp/ scheme: AuthIntegrationTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' auth-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +87,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' auth-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -90,6 +116,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' auth-ui-integration-test-iOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.hostedui-ios }} runs-on: macos-12 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_datastore.yml b/.github/workflows/integ_test_datastore.yml index 40c3584f4f..462f28e700 100644 --- a/.github/workflows/integ_test_datastore.yml +++ b/.github/workflows/integ_test_datastore.yml @@ -14,7 +14,7 @@ concurrency: jobs: prepare-for-test: - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b diff --git a/.github/workflows/integ_test_datastore_auth_cognito.yml b/.github/workflows/integ_test_datastore_auth_cognito.yml index 57b3330130..8da62dc393 100644 --- a/.github/workflows/integ_test_datastore_auth_cognito.yml +++ b/.github/workflows/integ_test_datastore_auth_cognito.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - Auth Cognito on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,8 +25,9 @@ permissions: jobs: datastore-integration-auth-cognito-test-iOS: - timeout-minutes: 30 - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -33,9 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginAuthCognitoTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-auth-cognito-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -62,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-auth-cognito-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -91,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_datastore_auth_iam.yml b/.github/workflows/integ_test_datastore_auth_iam.yml index dd0da0afe2..568834765e 100644 --- a/.github/workflows/integ_test_datastore_auth_iam.yml +++ b/.github/workflows/integ_test_datastore_auth_iam.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - Auth IAM on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-auth-iam-test-iOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginAuthIAMTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-auth-iam-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-auth-iam-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_datastore_base.yml b/.github/workflows/integ_test_datastore_base.yml index cf26b3075b..ba45454837 100644 --- a/.github/workflows/integ_test_datastore_base.yml +++ b/.github/workflows/integ_test_datastore_base.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - Base on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-test-base-iOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginIntegrationTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-test-base-tvOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-test-base-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_datastore_cpk.yml b/.github/workflows/integ_test_datastore_cpk.yml index e8e62b0fa1..1f48780ee7 100644 --- a/.github/workflows/integ_test_datastore_cpk.yml +++ b/.github/workflows/integ_test_datastore_cpk.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - CPK on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-cpk-test-iOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginCPKTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-cpk-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-cpk-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' \ No newline at end of file diff --git a/.github/workflows/integ_test_datastore_lazy_load.yml b/.github/workflows/integ_test_datastore_lazy_load.yml index 38be1366b9..ab0e5f8cff 100644 --- a/.github/workflows/integ_test_datastore_lazy_load.yml +++ b/.github/workflows/integ_test_datastore_lazy_load.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - Lazy Load on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-lazy-load-test-iOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginLazyLoadTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-lazy-load-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-lazy-load-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_datastore_multi_auth.yml b/.github/workflows/integ_test_datastore_multi_auth.yml index 4417800b48..93fc522563 100644 --- a/.github/workflows/integ_test_datastore_multi_auth.yml +++ b/.github/workflows/integ_test_datastore_multi_auth.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - Multi Auth on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-multi-auth-test-iOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginMultiAuthTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-multi-auth-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-multi-auth-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_datastore_v2.yml b/.github/workflows/integ_test_datastore_v2.yml index eb1c84d221..262a8a6aaa 100644 --- a/.github/workflows/integ_test_datastore_v2.yml +++ b/.github/workflows/integ_test_datastore_v2.yml @@ -1,6 +1,22 @@ name: Integration Tests | DataStore - TransformerV2 on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: datastore-integration-v2-test-iOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -33,11 +50,13 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: AWSDataStorePluginV2Tests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-v2-test-tvOS: - timeout-minutes: 30 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -64,9 +83,11 @@ jobs: destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest sdk: appletvsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' datastore-integration-v2-test-watchOS: - timeout-minutes: 45 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} + timeout-minutes: 60 runs-on: macos-13 environment: IntegrationTest steps: @@ -93,3 +114,4 @@ jobs: destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest sdk: watchsimulator xcode_path: '/Applications/Xcode_14.3.app' + other_flags: '-test-iterations 3 -retry-tests-on-failure' diff --git a/.github/workflows/integ_test_geo.yml b/.github/workflows/integ_test_geo.yml index b530d97d53..4ba3bcf0e6 100644 --- a/.github/workflows/integ_test_geo.yml +++ b/.github/workflows/integ_test_geo.yml @@ -1,6 +1,22 @@ name: Integration Test | Geo on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: geo-integration-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/Geo/Tests/GeoHostApp/ scheme: AWSLocationGeoPluginIntegrationTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' geo-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' geo-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/integ_test_logging.yml b/.github/workflows/integ_test_logging.yml index b745f3d142..738a5bcf75 100644 --- a/.github/workflows/integ_test_logging.yml +++ b/.github/workflows/integ_test_logging.yml @@ -1,6 +1,22 @@ name: Integration Tests | Logging on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,7 +25,8 @@ permissions: jobs: logging-integration-test-iOS: - runs-on: macos-12 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -32,8 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp scheme: AWSCloudWatchLoggingPluginIntegrationTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' logging-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -62,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' logging-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -85,6 +106,6 @@ jobs: with: project_path: ./AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp scheme: AWSCloudWatchLoggingPluginIntegrationTestsWatch - destination: platform=watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest + destination: platform=watchOS Simulator,name=Apple Watch Series 9 (45mm),OS=latest sdk: watchsimulator - xcode_path: '/Applications/Xcode_14.3.app' + xcode_path: '/Applications/Xcode_15.0.app' diff --git a/.github/workflows/integ_test_predictions.yml b/.github/workflows/integ_test_predictions.yml index d0a9b8ac3d..b661a77541 100644 --- a/.github/workflows/integ_test_predictions.yml +++ b/.github/workflows/integ_test_predictions.yml @@ -1,6 +1,22 @@ name: Integration Tests | Predictions on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,9 +25,10 @@ permissions: jobs: predictions-integration-test-iOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} continue-on-error: true timeout-minutes: 30 - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -37,8 +54,11 @@ jobs: with: project_path: ./AmplifyPlugins/Predictions/Tests/PredictionsHostApp scheme: AWSPredictionsPluginIntegrationTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' predictions-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} continue-on-error: true timeout-minutes: 30 runs-on: macos-13 @@ -72,6 +92,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' predictions-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} continue-on-error: true timeout-minutes: 30 runs-on: macos-13 diff --git a/.github/workflows/integ_test_push_notifications.yml b/.github/workflows/integ_test_push_notifications.yml index 8a545f33d7..242b28667c 100644 --- a/.github/workflows/integ_test_push_notifications.yml +++ b/.github/workflows/integ_test_push_notifications.yml @@ -1,6 +1,22 @@ name: Integration Tests | Push Notifications on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,8 +25,9 @@ permissions: jobs: push-notification-integration-test-iOS: - runs-on: macos-12 - timeout-minutes: 20 + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} + runs-on: macos-13 + timeout-minutes: 45 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -45,10 +62,13 @@ jobs: with: project_path: ./AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp scheme: PushNotificationHostApp + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' push-notification-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 - timeout-minutes: 20 + timeout-minutes: 30 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -88,8 +108,9 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' push-notification-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 - timeout-minutes: 20 + timeout-minutes: 30 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b diff --git a/.github/workflows/integ_test_storage.yml b/.github/workflows/integ_test_storage.yml index ba971a0b05..7e00e05529 100644 --- a/.github/workflows/integ_test_storage.yml +++ b/.github/workflows/integ_test_storage.yml @@ -1,6 +1,22 @@ name: Integration Tests | Storage on: workflow_dispatch: + inputs: + ios: + description: '📱 iOS' + required: true + default: true + type: boolean + tvos: + description: '📺 tvOS' + required: true + default: true + type: boolean + watchos: + description: '⌚️ watchOS' + required: true + default: true + type: boolean workflow_call: permissions: @@ -9,6 +25,7 @@ permissions: jobs: storage-integration-test-iOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.ios }} runs-on: macos-13 environment: IntegrationTest steps: @@ -32,10 +49,11 @@ jobs: with: project_path: ./AmplifyPlugins/Storage/Tests/StorageHostApp/ scheme: AWSS3StoragePluginIntegrationTests - destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest' + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' xcode_path: '/Applications/Xcode_14.3.app' storage-integration-test-tvOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.tvos }} runs-on: macos-13 environment: IntegrationTest steps: @@ -64,6 +82,7 @@ jobs: xcode_path: '/Applications/Xcode_14.3.app' storage-integration-test-watchOS: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.watchos }} runs-on: macos-13 environment: IntegrationTest steps: diff --git a/.github/workflows/notify_issue_comment.yml b/.github/workflows/notify_issue_comment.yml index b4a7adc8d1..b5b2990913 100644 --- a/.github/workflows/notify_issue_comment.yml +++ b/.github/workflows/notify_issue_comment.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions -name: Notify Comments on Pending/Closing Soon Issues +name: Notify Comments on Issues # Controls when the workflow will run on: @@ -18,7 +18,8 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest - if: ${{ !github.event.issue.pull_request && !contains(fromJSON('["palpatim", "jcjimenez", "5d", "lawmicha", "harsh62", "thisisabhash", "royjit", "atierian", "ukhan-amazon", "ruisebas", "phantumcode"]'), github.event.comment.user.login) }} + # Exclude comments in PRs and comments made from maintainers + if: ${{ !github.event.issue.pull_request && !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) }} # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -27,7 +28,7 @@ jobs: env: WEBHOOK_URL: ${{ secrets.SLACK_COMMENT_WEBHOOK_URL }} COMMENT: ${{toJson(github.event.comment.body)}} - USER: ${{github.event.issue.user.login}} + USER: ${{github.event.comment.user.login}} COMMENT_URL: ${{github.event.comment.html_url}} shell: bash run: echo $COMMENT | sed "s/\\\n/. /g; s/\\\r//g; s/[^a-zA-Z0-9 &().,:]//g" | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"comment":"{}", "commentUrl":"'$COMMENT_URL'", "user":"'$USER'"}' diff --git a/.github/workflows/notify_new_issue.yml b/.github/workflows/notify_new_issue.yml index 35292fb461..2c32e33645 100644 --- a/.github/workflows/notify_new_issue.yml +++ b/.github/workflows/notify_new_issue.yml @@ -18,6 +18,9 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest + # Exclude issues opened by maintainers + if: ${{ !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.issue.author_association) }} + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Runs a single command using the runners shell diff --git a/.github/workflows/notify_release.yml b/.github/workflows/notify_release.yml index 38b350287f..4920646cc6 100644 --- a/.github/workflows/notify_release.yml +++ b/.github/workflows/notify_release.yml @@ -4,9 +4,9 @@ name: Notify Amplify iOS Release # Controls when the workflow will run on: - # Triggers the workflow on release created (draft) or released (stable) events but only for the main branch + # Triggers the workflow on released events release: - types: [created, released] + types: [released] # Limit the GITHUB_TOKEN permissions permissions: {} @@ -25,7 +25,7 @@ jobs: env: WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }} VERSION: ${{github.event.release.html_url}} - REPO_URL: ${{github.event.repository.html_url}} + REPO_NAME: ${{github.event.repository.name}} ACTION_NAME: ${{github.event.action}} shell: bash - run: echo $VERSION | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"action":"'$ACTION_NAME'", "repo":"'$REPO_URL'", "version":"{}"}' + run: echo $VERSION | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"action":"'$ACTION_NAME'", "repo":"'$REPO_NAME'", "version":"{}"}' diff --git a/.github/workflows/run_xcodebuild_test_platforms.yml b/.github/workflows/run_xcodebuild_test_platforms.yml index 03a75c204c..b0b691a6be 100644 --- a/.github/workflows/run_xcodebuild_test_platforms.yml +++ b/.github/workflows/run_xcodebuild_test_platforms.yml @@ -32,6 +32,7 @@ env: permissions: contents: read + actions: write jobs: test-iOS: @@ -52,12 +53,25 @@ jobs: key: amplify-packages-${{ hashFiles('Package.resolved') }} restore-keys: | amplify-packages- - - name: Attempt to restore the build cache + - name: Attempt to restore the build cache for this SHA id: restore-build + timeout-minutes: 4 + continue-on-error: true uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-iOS-build-${{ github.sha }} + restore-keys: | + ${{ env.SCHEME }}-iOS-build- + - name: Attempt to restore the build cache from main + if: steps.restore-build.outputs.cache-hit != 'true' + id: restore-main-build + timeout-minutes: 4 + continue-on-error: true + uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ env.SCHEME }}-iOS-latest-build-main - name: Run iOS Test Suite id: run-tests continue-on-error: ${{ env.RETRY_ON_ERROR == 'true' }} @@ -84,14 +98,37 @@ jobs: cloned_source_packages_path: ~/Library/Developer/Xcode/DerivedData/Amplify derived_data_path: ${{ github.workspace }}/Build disable_package_resolution: true - test_without_building: true + # Only test without building when this exact SHA was cached or we did not restore the build from main. + test_without_building: ${{ steps.restore-build.outputs.cache-hit || !steps.restore-main-build.outputs.cache-hit }} other_flags: ${{ inputs.other_flags }} - - name: Save the build cache for re-runs + - name: Save the SHA build cache for re-runs if: failure() && steps.retry-tests.outcome=='failure' uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-iOS-build-${{ github.sha }} + - name: Delete the SHA build cache on re-run success + if: steps.restore-build.outputs.cache-matched-key + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-build.outputs.cache-matched-key }} + shell: bash + - name: Delete the old build cache on main + if: github.ref_name == 'main' && steps.restore-main-build.outputs.cache-hit + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-main-build.outputs.cache-primary-key }} + shell: bash + - name: Save the new build cache on main + if: github.ref_name == 'main' + uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ steps.restore-main-build.outputs.cache-primary-key }} - name: Upload Report File if: ${{ inputs.generate_coverage_report == true }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 @@ -120,12 +157,25 @@ jobs: key: amplify-packages-${{ hashFiles('Package.resolved') }} restore-keys: | amplify-packages- - - name: Attempt to restore the build cache + - name: Attempt to restore the build cache for this SHA id: restore-build + timeout-minutes: 4 + continue-on-error: true uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-macOS-build-${{ github.sha }} + restore-keys: | + ${{ env.SCHEME }}-macOS-build- + - name: Attempt to restore the build cache from main + if: steps.restore-build.outputs.cache-hit != 'true' + id: restore-main-build + timeout-minutes: 4 + continue-on-error: true + uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ env.SCHEME }}-macOS-latest-build-main - name: Run macOS Test Suite id: run-tests continue-on-error: ${{ env.RETRY_ON_ERROR == 'true' }} @@ -152,14 +202,37 @@ jobs: cloned_source_packages_path: ~/Library/Developer/Xcode/DerivedData/Amplify derived_data_path: ${{ github.workspace }}/Build disable_package_resolution: true - test_without_building: true + # Only test without building when this exact SHA was cached or we did not restore the build from main. + test_without_building: ${{ steps.restore-build.outputs.cache-hit || !steps.restore-main-build.outputs.cache-hit }} other_flags: ${{ inputs.other_flags }} - - name: Save the build cache for re-runs + - name: Save the SHA build cache for re-runs if: failure() && steps.retry-tests.outcome=='failure' uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-macOS-build-${{ github.sha }} + - name: Delete the SHA build cache on re-run success + if: steps.restore-build.outputs.cache-matched-key + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-build.outputs.cache-matched-key }} + shell: bash + - name: Delete the old build cache on main + if: github.ref_name == 'main' && steps.restore-main-build.outputs.cache-hit + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-main-build.outputs.cache-primary-key }} + shell: bash + - name: Save the new build cache on main + if: github.ref_name == 'main' + uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ steps.restore-main-build.outputs.cache-primary-key }} test-tvOS: name: ${{ inputs.scheme }} tvOS Tests @@ -179,12 +252,25 @@ jobs: key: amplify-packages-${{ hashFiles('Package.resolved') }} restore-keys: | amplify-packages- - - name: Attempt to restore the build cache + - name: Attempt to restore the build cache for this SHA id: restore-build + timeout-minutes: 4 + continue-on-error: true uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-tvOS-build-${{ github.sha }} + restore-keys: | + ${{ env.SCHEME }}-tvOS-build- + - name: Attempt to restore the build cache from main + if: steps.restore-build.outputs.cache-hit != 'true' + id: restore-main-build + timeout-minutes: 4 + continue-on-error: true + uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ env.SCHEME }}-tvOS-latest-build-main - name: Run tvOS Test Suite id: run-tests continue-on-error: ${{ env.RETRY_ON_ERROR == 'true' }} @@ -211,14 +297,37 @@ jobs: cloned_source_packages_path: ~/Library/Developer/Xcode/DerivedData/Amplify derived_data_path: ${{ github.workspace }}/Build disable_package_resolution: true - test_without_building: true + # Only test without building when this exact SHA was cached or we did not restore the build from main. + test_without_building: ${{ steps.restore-build.outputs.cache-hit || !steps.restore-main-build.outputs.cache-hit }} other_flags: ${{ inputs.other_flags }} - - name: Save the build cache for re-runs + - name: Save the SHA build cache for re-runs if: failure() && steps.retry-tests.outcome=='failure' uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-tvOS-build-${{ github.sha }} + - name: Delete the SHA build cache on re-run success + if: steps.restore-build.outputs.cache-matched-key + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-build.outputs.cache-matched-key }} + shell: bash + - name: Delete the old build cache on main + if: github.ref_name == 'main' && steps.restore-main-build.outputs.cache-hit + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-main-build.outputs.cache-primary-key }} + shell: bash + - name: Save the new build cache on main + if: github.ref_name == 'main' + uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ steps.restore-main-build.outputs.cache-primary-key }} test-watchOS: name: ${{ inputs.scheme }} watchOS Tests @@ -238,12 +347,25 @@ jobs: key: amplify-packages-${{ hashFiles('Package.resolved') }} restore-keys: | amplify-packages- - - name: Attempt to restore the build cache + - name: Attempt to restore the build cache for this SHA id: restore-build + timeout-minutes: 4 + continue-on-error: true uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-watchOS-build-${{ github.sha }} + restore-keys: | + ${{ env.SCHEME }}-watchOS-build- + - name: Attempt to restore the build cache from main + if: steps.restore-build.outputs.cache-hit != 'true' + id: restore-main-build + timeout-minutes: 4 + continue-on-error: true + uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ env.SCHEME }}-watchOS-latest-build-main - name: Run watchOS Test Suite id: run-tests continue-on-error: ${{ env.RETRY_ON_ERROR == 'true' }} @@ -270,11 +392,34 @@ jobs: cloned_source_packages_path: ~/Library/Developer/Xcode/DerivedData/Amplify derived_data_path: ${{ github.workspace }}/Build disable_package_resolution: true - test_without_building: true + # Only test without building when this exact SHA was cached or we did not restore the build from main. + test_without_building: ${{ steps.restore-build.outputs.cache-hit || !steps.restore-main-build.outputs.cache-hit }} other_flags: ${{ inputs.other_flags }} - - name: Save the build cache for re-runs + - name: Save the SHA build cache for re-runs if: failure() && steps.retry-tests.outcome=='failure' uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: ${{ github.workspace }}/Build key: ${{ env.SCHEME }}-watchOS-build-${{ github.sha }} + - name: Delete the SHA build cache on re-run success + if: steps.restore-build.outputs.cache-matched-key + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-build.outputs.cache-matched-key }} + shell: bash + - name: Delete the old build cache on main + if: github.ref_name == 'main' && steps.restore-main-build.outputs.cache-hit + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + run: | + gh cache delete ${{ steps.restore-main-build.outputs.cache-primary-key }} + shell: bash + - name: Save the new build cache on main + if: github.ref_name == 'main' + uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: ${{ github.workspace }}/Build + key: ${{ steps.restore-main-build.outputs.cache-primary-key }} diff --git a/.github/workflows/stress_test.yml b/.github/workflows/stress_test.yml index 39d6a0bc68..7988fb7123 100644 --- a/.github/workflows/stress_test.yml +++ b/.github/workflows/stress_test.yml @@ -14,7 +14,7 @@ concurrency: jobs: prepare-for-test: - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -31,7 +31,7 @@ jobs: auth-stress-test: needs: prepare-for-test - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -54,10 +54,12 @@ jobs: with: project_path: ./AmplifyPlugins/Auth/Tests/AuthHostApp/ scheme: AuthStressTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' geo-stress-test: needs: prepare-for-test - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -80,11 +82,12 @@ jobs: with: project_path: ./AmplifyPlugins/Geo/Tests/GeoHostApp/ scheme: GeoStressTests - + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' storage-stress-test: needs: prepare-for-test - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -107,10 +110,12 @@ jobs: with: project_path: ./AmplifyPlugins/Storage/Tests/StorageHostApp/ scheme: StorageStressTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' datastore-stress-test: needs: prepare-for-test - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -133,10 +138,12 @@ jobs: with: project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp scheme: DatastoreStressTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' graphql-api-stress-test: needs: prepare-for-test - runs-on: macos-12 + runs-on: macos-13 environment: IntegrationTest steps: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b @@ -158,4 +165,6 @@ jobs: uses: ./.github/composite_actions/run_xcodebuild_test with: project_path: ./AmplifyPlugins/API/Tests/APIHostApp - scheme: GraphQLAPIStressTests \ No newline at end of file + scheme: GraphQLAPIStressTests + destination: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' + xcode_path: '/Applications/Xcode_14.3.app' \ No newline at end of file diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index e7678b69d0..d329901675 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -14,6 +14,7 @@ on: permissions: contents: read + actions: write concurrency: group: ${{ inputs.identifier || github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme index d92bafde07..96d011b6cc 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme @@ -48,11 +48,6 @@ BlueprintName = "AWSCognitoAuthPluginUnitTests" ReferencedContainer = "container:"> - - - - diff --git a/Amplify/Categories/DataStore/Model/Lazy/List+Model.swift b/Amplify/Categories/DataStore/Model/Lazy/List+Model.swift index 09c3b8a3cd..aee10e9e53 100644 --- a/Amplify/Categories/DataStore/Model/Lazy/List+Model.swift +++ b/Amplify/Categories/DataStore/Model/Lazy/List+Model.swift @@ -26,6 +26,15 @@ public class List: Collection, Codable, ExpressibleByArrayLite /// The current state of lazily loaded list var loadedState: LoadedState + + /// Boolean property to check if list is loaded + public var isLoaded: Bool { + if case .loaded = loadedState { + return true + } + + return false + } /// The provider for fulfilling list behaviors let listProvider: AnyModelListProvider @@ -61,6 +70,7 @@ public class List: Collection, Codable, ExpressibleByArrayLite } } + // MARK: - Initializers public init(listProvider: AnyModelListProvider) { diff --git a/Amplify/Resources/PrivacyInfo.xcprivacy b/Amplify/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/Amplify/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift index 7ace300b74..e737479d9c 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift @@ -11,7 +11,7 @@ import Foundation struct APIKeyURLRequestInterceptor: URLRequestInterceptor { - private let userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData().description + private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib let apiKeyProvider: APIKeyProvider init(apiKeyProvider: APIKeyProvider) { diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift index d7392748e8..2d639afca4 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift @@ -13,7 +13,7 @@ struct AuthTokenURLRequestInterceptor: URLRequestInterceptor { static let AWSDateISO8601DateFormat2 = "yyyyMMdd'T'HHmmss'Z'" - private let userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData().description + private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib let authTokenProvider: AuthTokenProvider init(authTokenProvider: AuthTokenProvider) { diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift index 0a9b53fe93..4ad5159992 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift @@ -16,7 +16,7 @@ struct IAMURLRequestInterceptor: URLRequestInterceptor { let iamCredentialsProvider: IAMCredentialsProvider let region: AWSRegionType let endpointType: AWSAPICategoryPluginEndpointType - private let userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData().description + private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib init(iamCredentialsProvider: IAMCredentialsProvider, region: AWSRegionType, @@ -42,7 +42,8 @@ struct IAMURLRequestInterceptor: URLRequestInterceptor { let httpMethod = (request.httpMethod?.uppercased()) .flatMap(HttpMethodType.init(rawValue:)) ?? .get - let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? [] + let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems? + .map { ClientRuntime.URLQueryItem(name: $0.name, value: $0.value)} ?? [] let requestBuilder = SdkHttpRequestBuilder() .withHost(host) diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift index 10e66289cb..7d1bb4df11 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift @@ -21,10 +21,10 @@ class IAMAuthInterceptor: AuthInterceptorAsync { RealtimeProviderConstants.amzDate.lowercased(), RealtimeProviderConstants.iamSecurityTokenKey.lowercased()] - let authProvider: CredentialsProvider + let authProvider: CredentialsProviding let region: AWSRegionType - init(_ authProvider: CredentialsProvider, region: AWSRegionType) { + init(_ authProvider: CredentialsProviding, region: AWSRegionType) { self.authProvider = authProvider self.region = region } @@ -60,8 +60,8 @@ class IAMAuthInterceptor: AuthInterceptorAsync { guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else { return request } - let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth) - let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64) + let headerQuery = Foundation.URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth) + let payloadQuery = Foundation.URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64) urlComponents.queryItems = [headerQuery, payloadQuery] guard let signedUrl = urlComponents.url else { return request diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift index d0225d095e..a9bf16eee9 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift @@ -21,8 +21,8 @@ public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner, let subscriptionConnectionFactory: SubscriptionConnectionFactory let authService: AWSAuthServiceBehavior var apiAuthProviderFactory: APIAuthProviderFactory - private let userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData().description - + private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib + var subscriptionConnection: SubscriptionConnection? var subscriptionItem: SubscriptionItem? private var running = false @@ -203,7 +203,7 @@ final public class AWSGraphQLSubscriptionOperation: GraphQLSubscri let pluginConfig: AWSAPICategoryPluginConfiguration let subscriptionConnectionFactory: SubscriptionConnectionFactory let authService: AWSAuthServiceBehavior - private let userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData().description + private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib var subscriptionConnection: SubscriptionConnection? var subscriptionItem: SubscriptionItem? diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/AWSAPIPluginFunctionalTests.xcscheme b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/AWSAPIPluginFunctionalTests.xcscheme index 84f25600fd..b32f266b27 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/AWSAPIPluginFunctionalTests.xcscheme +++ b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/AWSAPIPluginFunctionalTests.xcscheme @@ -8,7 +8,7 @@ @@ -62,15 +62,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - - - - diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AnyModelIntegrationTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AnyModelIntegrationTests.swift index e73dde7c70..8a6397c488 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AnyModelIntegrationTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AnyModelIntegrationTests.swift @@ -61,7 +61,7 @@ class AnyModelIntegrationTests: XCTestCase { } } - wait(for: [callbackInvoked], timeout: networkTimeout) + await fulfillment(of: [callbackInvoked], timeout: networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -106,7 +106,7 @@ class AnyModelIntegrationTests: XCTestCase { createCallbackInvoked.fulfill() } - wait(for: [createCallbackInvoked], timeout: networkTimeout) + await fulfillment(of: [createCallbackInvoked], timeout: networkTimeout) let newContent = "Updated post content as of \(Date())" @@ -128,7 +128,7 @@ class AnyModelIntegrationTests: XCTestCase { } } - wait(for: [updateCallbackInvoked], timeout: networkTimeout) + await fulfillment(of: [updateCallbackInvoked], timeout: networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -171,7 +171,7 @@ class AnyModelIntegrationTests: XCTestCase { createCallbackInvoked.fulfill() } - wait(for: [createCallbackInvoked], timeout: networkTimeout) + await fulfillment(of: [createCallbackInvoked], timeout: networkTimeout) let deleteCallbackInvoked = expectation(description: "Delete callback invoked") var responseFromOperation: GraphQLResponse? @@ -187,7 +187,7 @@ class AnyModelIntegrationTests: XCTestCase { } } - wait(for: [deleteCallbackInvoked], timeout: networkTimeout) + await fulfillment(of: [deleteCallbackInvoked], timeout: networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift index 1a60870d12..44837045b1 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift @@ -36,3 +36,9 @@ class TestConfigHelper { return try Data(contentsOf: url) } } + +extension String { + var withUUID: String { + "\(self)-\(UUID().uuidString)" + } +} diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1APISwiftTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1APISwiftTests.swift index 62ee3c044c..5dacaf7714 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1APISwiftTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1APISwiftTests.swift @@ -16,7 +16,7 @@ import Amplify extension GraphQLConnectionScenario1Tests { func createTeamAPISwift() async throws -> APISwift.CreateTeam1Mutation.Data.CreateTeam1? { - let input = APISwift.CreateTeam1Input(name: "name") + let input = APISwift.CreateTeam1Input(name: "name".withUUID) let mutation = APISwift.CreateTeam1Mutation(input: input) let request = GraphQLRequest( document: APISwift.CreateTeam1Mutation.operationString, diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1Tests.swift index e92816557f..89f05f502d 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario1Tests.swift @@ -55,7 +55,7 @@ class GraphQLConnectionScenario1Tests: XCTestCase { } func testCreateAndGetProject() async throws { - guard let team = try await createTeam(name: "name"), + guard let team = try await createTeam(name: "name".withUUID), let project = try await createProject(team: team) else { XCTFail("Could not create team and a project") return @@ -76,12 +76,12 @@ class GraphQLConnectionScenario1Tests: XCTestCase { } func testUpdateProjectWithAnotherTeam() async throws { - guard let team = try await createTeam(name: "name"), + guard let team = try await createTeam(name: "name".withUUID), var project = try await createProject(team: team) else { XCTFail("Could not create a Team") return } - let anotherTeam = Team1(name: "name") + let anotherTeam = Team1(name: "name".withUUID) guard case .success(let createdAnotherTeam) = try await Amplify.API.mutate(request: .create(anotherTeam)) else { XCTFail("Failed to create another team") return @@ -96,7 +96,7 @@ class GraphQLConnectionScenario1Tests: XCTestCase { } func testDeleteAndGetProject() async throws { - guard let team = try await createTeam(name: "name"), + guard let team = try await createTeam(name: "name".withUUID), let project = try await createProject(team: team) else { XCTFail("Could not create team and a project") return @@ -124,7 +124,7 @@ class GraphQLConnectionScenario1Tests: XCTestCase { // The filter we are passing into is the ProjectTeamID, but the API doesn't have the field ProjectTeamID // so we are disabling it func testListProjectsByTeamID() async throws { - guard let team = try await createTeam(name: "name"), + guard let team = try await createTeam(name: "name".withUUID), let project = try await createProject(team: team) else { XCTFail("Could not create team and a project") return @@ -141,9 +141,9 @@ class GraphQLConnectionScenario1Tests: XCTestCase { } func testPaginatedListProjects() async throws { - let testCompleted = asyncExpectation(description: "test completed") + let testCompleted = expectation(description: "test completed") Task { - guard let team = try await createTeam(name: "name"), + guard let team = try await createTeam(name: "name".withUUID), let projecta = try await createProject(team: team), let projectb = try await createProject(team: team) else { XCTFail("Could not create team and two projects") @@ -173,9 +173,9 @@ class GraphQLConnectionScenario1Tests: XCTestCase { resultsArray.append(contentsOf: subsequentResults) } XCTAssertEqual(resultsArray.count, 2) - await testCompleted.fulfill() + testCompleted.fulfill() } - await waitForExpectations([testCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [testCompleted], timeout: TestCommonConstants.networkTimeout) } func createTeam(id: String = UUID().uuidString, name: String) async throws -> Team1? { diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario2Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario2Tests.swift index 8eb0c6e3c2..2e43f532d9 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario2Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario2Tests.swift @@ -60,7 +60,7 @@ class GraphQLConnectionScenario2Tests: XCTestCase { // 1. `teamID` and `team` // 2. With random `teamID` and `team` func testCreateAndGetProject() async throws { - guard let team = try await createTeam2(name: "name"), + guard let team = try await createTeam2(name: "name".withUUID), let project2a = try await createProject2(teamID: team.id, team: team), let project2b = try await createProject2(teamID: team.id, team: team) else { XCTFail("Could not create team and a project") @@ -93,7 +93,7 @@ class GraphQLConnectionScenario2Tests: XCTestCase { } func testUpdateProjectWithAnotherTeam() async throws { - guard let team = try await createTeam2(name: "name"), + guard let team = try await createTeam2(name: "name".withUUID), var project2 = try await createProject2(teamID: team.id, team: team) else { XCTFail("Could not create team and a project") return @@ -115,7 +115,7 @@ class GraphQLConnectionScenario2Tests: XCTestCase { } func testDeleteAndGetProject() async throws { - guard let team = try await createTeam2(name: "name"), + guard let team = try await createTeam2(name: "name".withUUID), let project2 = try await createProject2(teamID: team.id, team: team) else { XCTFail("Could not create team and a project") return @@ -141,7 +141,7 @@ class GraphQLConnectionScenario2Tests: XCTestCase { } func testListProjectsByTeamID() async throws { - guard let team = try await createTeam2(name: "name"), + guard let team = try await createTeam2(name: "name".withUUID), try await createProject2(teamID: team.id, team: team) != nil else { XCTFail("Could not create team and two projects") return @@ -159,7 +159,7 @@ class GraphQLConnectionScenario2Tests: XCTestCase { // Create two projects for the same team, then list the projects by teamID, and expect two projects // after exhausting the paginated list via `hasNextPage` and `getNextPage` func testPaginatedListProjectsByTeamID() async throws { - guard let team = try await createTeam2(name: "name"), + guard let team = try await createTeam2(name: "name".withUUID), try await createProject2(teamID: team.id, team: team) != nil, try await createProject2(teamID: team.id, team: team) != nil else { XCTFail("Could not create team and two projects") diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3APISwiftTests+Subscribe.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3APISwiftTests+Subscribe.swift index f5ecbb30b5..9820132217 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3APISwiftTests+Subscribe.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3APISwiftTests+Subscribe.swift @@ -40,12 +40,13 @@ extension GraphQLConnectionScenario3Tests { } func testOnCreateSubscriptionAPISwift() async throws { - let connectedInvoked = asyncExpectation(description: "Connection established") - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 let uuid = UUID().uuidString let uuid2 = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID let subscription = Amplify.API.subscribe(request: onCreatePost3APISwiftRequest()) Task { do { @@ -56,7 +57,7 @@ extension GraphQLConnectionScenario3Tests { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } @@ -64,7 +65,7 @@ extension GraphQLConnectionScenario3Tests { switch result { case .success(let data): if data.onCreatePost3?.id == uuid || data.onCreatePost3?.id == uuid2 { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -76,13 +77,13 @@ extension GraphQLConnectionScenario3Tests { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) guard try await createPost3APISwift(uuid, title) != nil, try await createPost3APISwift(uuid2, title) != nil else { XCTFail("Failed to create post") return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+List.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+List.swift index f21a2b9195..8edf725162 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+List.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+List.swift @@ -35,9 +35,10 @@ import XCTest extension GraphQLConnectionScenario3Tests { func testGetPostThenIterateComments() async throws { - guard let post = try await createPost(title: "title"), - try await createComment(postID: post.id, content: "content") != nil, - try await createComment(postID: post.id, content: "content") != nil else { + let commentContent = "content".withUUID + guard let post = try await createPost(title: "title".withUUID), + try await createComment(postID: post.id, content: commentContent) != nil, + try await createComment(postID: post.id, content: commentContent) != nil else { XCTFail("Could not create post and two comments") return } @@ -73,9 +74,11 @@ extension GraphQLConnectionScenario3Tests { } func testGetPostThenFetchComments() async throws { - guard let post = try await createPost(title: "title"), - try await createComment(postID: post.id, content: "content") != nil, - try await createComment(postID: post.id, content: "content") != nil else { + let commentContent = "content".withUUID + + guard let post = try await createPost(title: "title".withUUID), + try await createComment(postID: post.id, content: commentContent) != nil, + try await createComment(postID: post.id, content: commentContent) != nil else { XCTFail("Could not create post and two comments") return } @@ -110,7 +113,7 @@ extension GraphQLConnectionScenario3Tests { // Create a post and list the posts func testListPost() async throws { - guard try await createPost(title: "title") != nil else { + guard try await createPost(title: "title".withUUID) != nil else { XCTFail("Failed to ensure at least one Post to be retrieved on the listQuery") return } @@ -155,11 +158,11 @@ extension GraphQLConnectionScenario3Tests { // Create a post and a comment with that post // list the comments by postId func testListCommentsByPostID() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard try await createComment(postID: post.id, content: "content") != nil else { + guard try await createComment(postID: post.id, content: "content".withUUID) != nil else { XCTFail("Could not create comment") return } @@ -187,15 +190,16 @@ extension GraphQLConnectionScenario3Tests { /// - Then: /// - the in-memory Array is a populated with exactly two comments. func testPaginatedListCommentsByPostID() async throws { - guard let post = try await createPost(title: "title") else { + let commentContent = "content".withUUID + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard try await createComment(postID: post.id, content: "content") != nil else { + guard try await createComment(postID: post.id, content: commentContent) != nil else { XCTFail("Could not create comment") return } - guard try await createComment(postID: post.id, content: "content") != nil else { + guard try await createComment(postID: post.id, content: commentContent) != nil else { XCTFail("Could not create comment") return } @@ -232,7 +236,7 @@ extension GraphQLConnectionScenario3Tests { /// - A validation error is returned func testPaginatedListFetchValidationError() async throws { let uuid1 = UUID().uuidString - guard try await createPost(id: uuid1, title: "title") != nil else { + guard try await createPost(id: uuid1, title: "title".withUUID) != nil else { XCTFail("Failed to create post") return } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+Subscribe.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+Subscribe.swift index ed18e1cf98..926831267b 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+Subscribe.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests+Subscribe.swift @@ -17,12 +17,13 @@ import XCTest extension GraphQLConnectionScenario3Tests { func testOnCreatePostSubscriptionWithModel() async throws { - let connectedInvoked = asyncExpectation(description: "Connection established") - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 let uuid = UUID().uuidString let uuid2 = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID let subscription = Amplify.API.subscribe(request: .subscription(of: Post3.self, type: .onCreate)) Task { do { @@ -33,7 +34,7 @@ extension GraphQLConnectionScenario3Tests { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } @@ -41,8 +42,7 @@ extension GraphQLConnectionScenario3Tests { switch result { case .success(let post): if post.id == uuid || post.id == uuid2 { - - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -54,19 +54,20 @@ extension GraphQLConnectionScenario3Tests { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let post = Post3(id: uuid, title: title) _ = try await Amplify.API.mutate(request: .create(post)) let post2 = Post3(id: uuid2, title: title) _ = try await Amplify.API.mutate(request: .create(post2)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnUpdatePostSubscriptionWithModel() async throws { - let connectingInvoked = AsyncExpectation(description: "Connection connecting") - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked") + let connectingInvoked = expectation(description: "Connection connecting") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.assertForOverFulfill = false let subscription = Amplify.API.subscribe(request: .subscription(of: Post3.self, type: .onUpdate)) Task { @@ -76,14 +77,14 @@ extension GraphQLConnectionScenario3Tests { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + connectingInvoked.fulfill() case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } case .data: - await progressInvoked.fulfill() + progressInvoked.fulfill() } } } catch { @@ -91,22 +92,22 @@ extension GraphQLConnectionScenario3Tests { } } - await waitForExpectations([connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID let post = Post3(id: uuid, title: title) _ = try await Amplify.API.mutate(request: .create(post)) _ = try await Amplify.API.mutate(request: .update(post)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnDeletePostSubscriptionWithModel() async throws { - let connectingInvoked = AsyncExpectation(description: "Connection connecting") - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked") + let connectingInvoked = expectation(description: "Connection connecting") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") let subscription = Amplify.API.subscribe(request: .subscription(of: Post3.self, type: .onDelete)) Task { @@ -116,24 +117,24 @@ extension GraphQLConnectionScenario3Tests { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + connectingInvoked.fulfill() case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } case .data: - await progressInvoked.fulfill() + progressInvoked.fulfill() } } } catch { XCTFail("Unexpected subscription failure") } } - await waitForExpectations([connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID guard let post = try await createPost(id: uuid, title: title) else { XCTFail("Failed to create post") @@ -145,13 +146,13 @@ extension GraphQLConnectionScenario3Tests { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnCreateCommentSubscriptionWithModel() async throws { - let connectingInvoked = AsyncExpectation(description: "Connection connecting") - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.assertForOverFulfill = false let subscription = Amplify.API.subscribe(request: .subscription(of: Comment3.self, type: .onCreate)) Task { do { @@ -160,24 +161,24 @@ extension GraphQLConnectionScenario3Tests { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } case .data: - await progressInvoked.fulfill() + progressInvoked.fulfill() } } } catch { XCTFail("Unexpected subscription failure") } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: 30) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID guard let createdPost = try await createPost(id: uuid, title: title) else { XCTFail("Failed to create post") @@ -189,6 +190,6 @@ extension GraphQLConnectionScenario3Tests { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: 30) } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests.swift index c68c600a4e..e8fb12f625 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario3Tests.swift @@ -57,7 +57,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { } func testQuerySinglePost() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Failed to set up test") return } @@ -77,7 +77,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { // Create a post and a comment for the post // Retrieve the comment and ensure that the comment is associated with the correct post func testCreatAndGetComment() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } @@ -105,7 +105,8 @@ class GraphQLConnectionScenario3Tests: XCTestCase { // Update the existing comment to point to the other post // Expect that the queried comment is associated with the other post func testUpdateComment() async throws { - guard let post = try await createPost(title: "title") else { + let postTitle = "title".withUUID + guard let post = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } @@ -113,7 +114,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { XCTFail("Could not create comment") return } - guard let anotherPost = try await createPost(title: "title") else { + guard let anotherPost = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } @@ -130,7 +131,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { func testUpdateExistingPost() async throws { let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID guard var post = try await createPost(id: uuid, title: title) else { XCTFail("Failed to ensure at least one Post to be retrieved on the listQuery") return @@ -149,7 +150,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { func testDeleteExistingPost() async throws { let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) - let title = testMethodName + "Title" + let title = testMethodName + "Title".withUUID guard let post = try await createPost(id: uuid, title: title) else { XCTFail("Could not create post") return @@ -174,7 +175,7 @@ class GraphQLConnectionScenario3Tests: XCTestCase { // Delete the comment and then query for the comment // Expected query should return `nil` comment func testDeleteAndGetComment() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift index cb4c3ac7a5..26428342e8 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift @@ -57,11 +57,11 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testCreateCommentAndGetCommentWithPost() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard let comment = try await createComment(content: "content", post: post) else { + guard let comment = try await createComment(content: "content".withUUID, post: post) else { XCTFail("Could not create comment") return } @@ -81,16 +81,18 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testGetPostThenFetchComments() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard try await createComment(content: "content", post: post) != nil else { + + let commentContent = "comment".withUUID + guard try await createComment(content: commentContent, post: post) != nil else { XCTFail("Could not create comment") return } - guard try await createComment(content: "content", post: post) != nil else { + guard try await createComment(content: commentContent, post: post) != nil else { XCTFail("Could not create comment") return } @@ -118,7 +120,7 @@ class GraphQLConnectionScenario4Tests: XCTestCase { case .failure(let response): XCTFail("Failed with: \(response)") } - wait(for: [getPostCompleted, fetchCommentsCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getPostCompleted, fetchCommentsCompleted], timeout: TestCommonConstants.networkTimeout) guard var subsequentResults = results else { XCTFail("Could not get first results") return @@ -134,15 +136,16 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testUpdateComment() async throws { - guard let post = try await createPost(title: "title") else { + let postTitle = "title".withUUID + guard let post = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } - guard var comment = try await createComment(content: "content", post: post) else { + guard var comment = try await createComment(content: "content".withUUID, post: post) else { XCTFail("Could not create comment") return } - guard let anotherPost = try await createPost(title: "title") else { + guard let anotherPost = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } @@ -157,11 +160,11 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testDeleteAndGetComment() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard let comment = try await createComment(content: "content", post: post) else { + guard let comment = try await createComment(content: "content".withUUID, post: post) else { XCTFail("Could not create comment") return } @@ -186,11 +189,11 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testListCommentsByPostID() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard try await createComment(content: "content", post: post) != nil else { + guard try await createComment(content: "content".withUUID, post: post) != nil else { XCTFail("Could not create comment") return } @@ -205,9 +208,10 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } func testPaginatedListCommentsByPostID() async throws { - guard let post = try await createPost(title: "title"), - try await createComment(content: "content", post: post) != nil, - try await createComment(content: "content", post: post) != nil else { + let commentContent = "content".withUUID + guard let post = try await createPost(title: "title".withUUID), + try await createComment(content: commentContent, post: post) != nil, + try await createComment(content: commentContent, post: post) != nil else { XCTFail("Could not create post and two comments") return } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario5Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario5Tests.swift index 71998c9364..7fa611e950 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario5Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario5Tests.swift @@ -69,11 +69,11 @@ class GraphQLConnectionScenario5Tests: XCTestCase { } func testListPostEditorByPost() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard let user = try await createUser(username: "username") else { + guard let user = try await createUser(username: "username".withUUID) else { XCTFail("Could not create user") return } @@ -92,11 +92,11 @@ class GraphQLConnectionScenario5Tests: XCTestCase { } func testListPostEditorByUser() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard let user = try await createUser(username: "username") else { + guard let user = try await createUser(username: "username".withUUID) else { XCTFail("Could not create user") return } @@ -118,11 +118,11 @@ class GraphQLConnectionScenario5Tests: XCTestCase { // Get the post and fetch the PostEditors for that post // The Posteditor contains the user which is connected the post func testGetPostThenFetchPostEditorsToRetrieveUser() async throws { - guard let post = try await createPost(title: "title") else { + guard let post = try await createPost(title: "title".withUUID) else { XCTFail("Could not create post") return } - guard let user = try await createUser(username: "username") else { + guard let user = try await createUser(username: "username".withUUID) else { XCTFail("Could not create user") return } @@ -152,7 +152,7 @@ class GraphQLConnectionScenario5Tests: XCTestCase { case .failure(let response): XCTFail("Failed with: \(response)") } - wait(for: [getPostCompleted, fetchPostEditorCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getPostCompleted, fetchPostEditorCompleted], timeout: TestCommonConstants.networkTimeout) guard var subsequentResults = results else { XCTFail("Could not get first results") return @@ -177,11 +177,12 @@ class GraphQLConnectionScenario5Tests: XCTestCase { // Get the user and fetch the PostEditors for that user // The PostEditors should contain the two posts `post1` and `post2` func testGetUserThenFetchPostEditorsToRetrievePosts() async throws { - guard let post1 = try await createPost(title: "title") else { + let postTitle = "title".withUUID + guard let post1 = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } - guard let post2 = try await createPost(title: "title") else { + guard let post2 = try await createPost(title: postTitle) else { XCTFail("Could not create post") return } @@ -219,7 +220,7 @@ class GraphQLConnectionScenario5Tests: XCTestCase { case .failure(let response): XCTFail("Failed with: \(response)") } - wait(for: [getUserCompleted, fetchPostEditorCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getUserCompleted, fetchPostEditorCompleted], timeout: TestCommonConstants.networkTimeout) guard var subsequentResults = results else { XCTFail("Could not get first results") diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario6Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario6Tests.swift index 1e640c08cf..d53eebfbdb 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario6Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario6Tests.swift @@ -64,11 +64,12 @@ class GraphQLConnectionScenario6Tests: XCTestCase { } func testGetBlogThenFetchPostsThenFetchComments() async throws { - guard let blog = try await createBlog(name: "name"), - let post1 = try await createPost(title: "title", blog: blog), + let commentContent = "content".withUUID + guard let blog = try await createBlog(name: "name".withUUID), + let post1 = try await createPost(title: "title".withUUID, blog: blog), try await createPost(title: "title", blog: blog) != nil, - let comment1post1 = try await createComment(post: post1, content: "content"), - let comment2post1 = try await createComment(post: post1, content: "content") else { + let comment1post1 = try await createComment(post: post1, content: commentContent), + let comment2post1 = try await createComment(post: post1, content: commentContent) else { XCTFail("Could not create blog, posts, and comments") return } @@ -93,7 +94,7 @@ class GraphQLConnectionScenario6Tests: XCTestCase { fetchPostCompleted.fulfill() case .failure(let response): XCTFail("Failed with: \(response)") } - wait(for: [getBlogCompleted, fetchPostCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getBlogCompleted, fetchPostCompleted], timeout: TestCommonConstants.networkTimeout) let allPosts = try await getAll(list: resultPosts) XCTAssertEqual(allPosts.count, 2) @@ -109,7 +110,7 @@ class GraphQLConnectionScenario6Tests: XCTestCase { try await comments.fetch() resultComments = comments fetchCommentsCompleted.fulfill() - wait(for: [fetchCommentsCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [fetchCommentsCompleted], timeout: TestCommonConstants.networkTimeout) let allComments = try await getAll(list: resultComments) XCTAssertEqual(allComments.count, 2) XCTAssertTrue(allComments.contains(where: { (comment) -> Bool in diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift index b7a890feac..770f598a7a 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift @@ -273,8 +273,9 @@ class GraphQLModelBasedTests: XCTestCase { } func testOnCreatePostSubscriptionWithModel() async throws { - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 let uuid = UUID().uuidString let uuid2 = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) @@ -289,7 +290,7 @@ class GraphQLModelBasedTests: XCTestCase { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } @@ -297,7 +298,7 @@ class GraphQLModelBasedTests: XCTestCase { switch result { case .success(let post): if post.id == uuid || post.id == uuid2 { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -309,20 +310,21 @@ class GraphQLModelBasedTests: XCTestCase { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let post = Post(id: uuid, title: title, content: "content", createdAt: .now()) _ = try await Amplify.API.mutate(request: .create(post)) let post2 = Post(id: uuid2, title: title, content: "content", createdAt: .now()) _ = try await Amplify.API.mutate(request: .create(post2)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnUpdatePostSubscriptionWithModel() async throws { - let connectingInvoked = AsyncExpectation(description: "Connection connecting") - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked") - + let connectingInvoked = expectation(description: "Connection connecting") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.assertForOverFulfill = false + let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onUpdate)) Task { do { @@ -331,14 +333,14 @@ class GraphQLModelBasedTests: XCTestCase { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + connectingInvoked.fulfill() case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } case .data: - await progressInvoked.fulfill() + progressInvoked.fulfill() } } } catch { @@ -346,7 +348,7 @@ class GraphQLModelBasedTests: XCTestCase { } } - await waitForExpectations([connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) @@ -355,13 +357,13 @@ class GraphQLModelBasedTests: XCTestCase { _ = try await Amplify.API.mutate(request: .create(post)) _ = try await Amplify.API.mutate(request: .update(post)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnDeletePostSubscriptionWithModel() async throws { - let connectingInvoked = AsyncExpectation(description: "Connection connecting") - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked") + let connectingInvoked = expectation(description: "Connection connecting") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onDelete)) Task { @@ -371,14 +373,14 @@ class GraphQLModelBasedTests: XCTestCase { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + connectingInvoked.fulfill() case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } case .data: - await progressInvoked.fulfill() + progressInvoked.fulfill() } } } catch { @@ -386,7 +388,7 @@ class GraphQLModelBasedTests: XCTestCase { } } - await waitForExpectations([connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectingInvoked, connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) @@ -395,12 +397,13 @@ class GraphQLModelBasedTests: XCTestCase { _ = try await Amplify.API.mutate(request: .create(post)) _ = try await Amplify.API.mutate(request: .delete(post)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } func testOnCreateCommentSubscriptionWithModel() async throws { - let connectedInvoked = AsyncExpectation(description: "Connection established") - let progressInvoked = AsyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 let uuid = UUID().uuidString let uuid2 = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) @@ -415,7 +418,7 @@ class GraphQLModelBasedTests: XCTestCase { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } @@ -423,7 +426,7 @@ class GraphQLModelBasedTests: XCTestCase { switch result { case .success(let comment): if comment.id == uuid || comment.id == uuid2 { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -435,14 +438,14 @@ class GraphQLModelBasedTests: XCTestCase { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let post = Post(id: uuid, title: title, content: "content", createdAt: .now()) _ = try await Amplify.API.mutate(request: .create(post)) let comment = Comment(id: uuid, content: "content", createdAt: .now(), post: post) _ = try await Amplify.API.mutate(request: .create(comment)) let comment2 = Comment(id: uuid2, content: "content", createdAt: .now(), post: post) _ = try await Amplify.API.mutate(request: .create(comment2)) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) } // MARK: Helpers diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLScalarTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLScalarTests.swift index e9926dc024..45593c79d9 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLScalarTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLScalarTests.swift @@ -41,7 +41,7 @@ class GraphQLScalarTests: GraphQLTestBase { } func testScalarContainer() async throws { - let container = ScalarContainer(myString: "myString", + let container = ScalarContainer(myString: "myString".withUUID, myInt: 1, myDouble: 1.0, myBool: true, @@ -114,9 +114,9 @@ class GraphQLScalarTests: GraphQLTestBase { func testListStringContainer() async throws { let container = ListStringContainer( - test: "test", + test: "test".withUUID, nullableString: nil, - stringList: ["value1"], + stringList: ["value1".withUUID], stringNullableList: [], nullableStringList: [], nullableStringNullableList: nil) @@ -150,9 +150,9 @@ class GraphQLScalarTests: GraphQLTestBase { func testListContainerWithNil() async throws { let container = ListStringContainer( - test: "test", + test: "test".withUUID, nullableString: nil, - stringList: ["value1"], + stringList: ["value1".withUUID], stringNullableList: nil, nullableStringList: [nil], nullableStringNullableList: nil) diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift index e2ffa9433c..d5f0d43668 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift @@ -60,7 +60,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -118,7 +118,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -190,7 +190,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -263,7 +263,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -296,7 +296,7 @@ class GraphQLSyncBasedTests: XCTestCase { } } - wait(for: [conditionalFailedError], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [conditionalFailedError], timeout: TestCommonConstants.networkTimeout) } // Given: A newly created post @@ -327,7 +327,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [firstUpdateSuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [firstUpdateSuccess], timeout: TestCommonConstants.networkTimeout) var responseFromOperation: GraphQLResponse>? let secondUpdateFailed = expectation( @@ -344,7 +344,7 @@ class GraphQLSyncBasedTests: XCTestCase { XCTFail("\(apiError)") } } - wait(for: [secondUpdateFailed], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [secondUpdateFailed], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -392,7 +392,7 @@ class GraphQLSyncBasedTests: XCTestCase { } } - wait(for: [conflictUnhandledError], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [conflictUnhandledError], timeout: TestCommonConstants.networkTimeout) } // Given: Two newly created posts @@ -432,7 +432,7 @@ class GraphQLSyncBasedTests: XCTestCase { print(error) } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) guard let response = responseFromOperation else { XCTAssertNotNil(responseFromOperation) @@ -517,16 +517,16 @@ class GraphQLSyncBasedTests: XCTestCase { }) XCTAssertNotNil(operation) - wait(for: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) guard createPost(id: uuid, title: title) != nil else { XCTFail("Failed to create post") return } - wait(for: [progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) operation.cancel() - wait(for: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) XCTAssertTrue(operation.isFinished) } @@ -558,7 +558,7 @@ class GraphQLSyncBasedTests: XCTestCase { print(error) } }) - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncCustomPrimaryKeyTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncCustomPrimaryKeyTests.swift index 1d1133df41..114c7ad3c6 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncCustomPrimaryKeyTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncCustomPrimaryKeyTests.swift @@ -122,7 +122,7 @@ class GraphQLSyncCustomPrimaryKeyTests: XCTestCase { print(error) } }) - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -156,7 +156,7 @@ class GraphQLSyncCustomPrimaryKeyTests: XCTestCase { XCTFail("\(error)") } } - wait(for: [querySuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [querySuccess], timeout: TestCommonConstants.networkTimeout) return querySyncResult } @@ -184,7 +184,7 @@ class GraphQLSyncCustomPrimaryKeyTests: XCTestCase { XCTFail("\(error)") } } - wait(for: [updateSuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [updateSuccess], timeout: TestCommonConstants.networkTimeout) return updateSyncResult } @@ -212,7 +212,7 @@ class GraphQLSyncCustomPrimaryKeyTests: XCTestCase { XCTFail("\(error)") } } - wait(for: [deleteSuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteSuccess], timeout: TestCommonConstants.networkTimeout) return deleteSyncResult } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLIAMTests/GraphQLWithIAMIntegrationTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLIAMTests/GraphQLWithIAMIntegrationTests.swift index 48fa5afd1f..a100bc9b29 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLIAMTests/GraphQLWithIAMIntegrationTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLIAMTests/GraphQLWithIAMIntegrationTests.swift @@ -135,9 +135,10 @@ class GraphQLWithIAMIntegrationTests: XCTestCase { } func onCreateTodoTest() async throws { - let connectedInvoked = asyncExpectation(description: "Connection established") - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) - let disconnectedInvoked = asyncExpectation(description: "Connection disconnected") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 + let disconnectedInvoked = expectation(description: "Connection disconnected") let subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) let uuid = UUID().uuidString let uuid2 = UUID().uuidString @@ -150,15 +151,15 @@ class GraphQLWithIAMIntegrationTests: XCTestCase { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: - await disconnectedInvoked.fulfill() + disconnectedInvoked.fulfill() } case .data(let result): switch result { case .success(let todo): if todo.id == uuid || todo.id == uuid2 { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -167,12 +168,12 @@ class GraphQLWithIAMIntegrationTests: XCTestCase { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) _ = try await createTodo(id: uuid, name: name) _ = try await createTodo(id: uuid2, name: name) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) subscription.cancel() - await waitForExpectations([disconnectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked], timeout: TestCommonConstants.networkTimeout) } // MARK: - Helpers diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLLambdaAuthTests/GraphQLWithLambdaAuthIntegrationTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLLambdaAuthTests/GraphQLWithLambdaAuthIntegrationTests.swift index 4230b0f83b..5be0f9d63a 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLLambdaAuthTests/GraphQLWithLambdaAuthIntegrationTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLLambdaAuthTests/GraphQLWithLambdaAuthIntegrationTests.swift @@ -77,7 +77,7 @@ class GraphQLWithLambdaAuthIntegrationTests: XCTestCase { /// - The operation completes successfully with no errors and a list of todos in response /// func testQueryTodos() async { - let completeInvoked = asyncExpectation(description: "request completed") + let completeInvoked = expectation(description: "request completed") let request = GraphQLRequest.list(Todo.self) let sink = Amplify.Publisher.create { try await Amplify.API.query(request: request) @@ -85,12 +85,12 @@ class GraphQLWithLambdaAuthIntegrationTests: XCTestCase { if case let .failure(error) = $0 { XCTFail("Query failure with error \(error)") } - Task { await completeInvoked.fulfill() } + completeInvoked.fulfill() } receiveValue: { XCTAssertNotNil($0) } XCTAssertNotNil(sink) - await waitForExpectations([completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) } /// A subscription to onCreate todo should receive an event for each create Todo mutation API called @@ -102,14 +102,15 @@ class GraphQLWithLambdaAuthIntegrationTests: XCTestCase { /// - The subscription should receive mutation events corresponding to the API calls performed. /// func testOnCreateTodoSubscription() async throws { - let connectedInvoked = asyncExpectation(description: "Connection established") + let connectedInvoked = expectation(description: "Connection established") let uuid = UUID().uuidString let uuid2 = UUID().uuidString let name = String("\(#function)".dropLast(2)) let subscriptions = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 Task { for try await event in subscriptions { @@ -119,13 +120,13 @@ class GraphQLWithLambdaAuthIntegrationTests: XCTestCase { case .connecting, .disconnected: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() } case .data(let result): switch result { case .success(let todo): if todo.id == uuid || todo.id == uuid2 { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") @@ -133,11 +134,11 @@ class GraphQLWithLambdaAuthIntegrationTests: XCTestCase { } } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: 30) try await createTodo(id: uuid, name: name) try await createTodo(id: uuid2, name: name) - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: 30) } // MARK: - Helpers diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/Base/AsyncExpectation.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/Base/AsyncExpectation.swift index fb6feb3a49..9b3bd39a7a 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/Base/AsyncExpectation.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/Base/AsyncExpectation.swift @@ -110,7 +110,7 @@ extension XCTestCase { /// Use this method to create ``AsyncExpectation`` instances that can be /// fulfilled when asynchronous tasks in your tests complete. /// - /// To fulfill an expectation that was created with `asyncExpectation(description:)`, + /// To fulfill an expectation that was created with `expectation(description:)`, /// call the expectation's `fulfill()` method when the asynchronous task in your /// test has completed. /// @@ -118,10 +118,10 @@ extension XCTestCase { /// - description: A string to display in the test log for this expectation, to help diagnose failures. /// - isInverted: Indicates that the expectation is not intended to happen. /// - expectedFulfillmentCount: The number of times fulfill() must be called before the expectation is completely fulfilled. (default = 1) - public func asyncExpectation(description: String, + public func expectation(description: String, isInverted: Bool = false, expectedFulfillmentCount: Int = 1) -> AsyncExpectation { - AsyncExpectation(description: description, + expectation(description: description, isInverted: isInverted, expectedFulfillmentCount: expectedFulfillmentCount) } @@ -148,7 +148,7 @@ public enum AsyncTesting { public static func expectation(description: String, isInverted: Bool = false, expectedFulfillmentCount: Int = 1) -> AsyncExpectation { - AsyncExpectation(description: description, + expectation(description: description, isInverted: isInverted, expectedFulfillmentCount: expectedFulfillmentCount) } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/GraphQLWithUserPoolIntegrationTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/GraphQLWithUserPoolIntegrationTests.swift index 9913c63825..b69d85b123 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/GraphQLWithUserPoolIntegrationTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGraphQLUserPoolTests/GraphQLWithUserPoolIntegrationTests.swift @@ -356,9 +356,10 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { /// The user is not signed in so establishing the subscription will fail with an unauthorized error. func testOnCreateSubscriptionUnauthorized() async throws { Amplify.Logging.logLevel = .verbose - let connectingInvoked = asyncExpectation(description: "Connecting invoked") - let connectedInvoked = asyncExpectation(description: "Connection established", isInverted: true) - let completedInvoked = asyncExpectation(description: "Completed invoked") + let connectingInvoked = expectation(description: "Connecting invoked") + let connectedInvoked = expectation(description: "Connection established") + connectedInvoked.isInverted = true + let completedInvoked = expectation(description: "Completed invoked") let request = GraphQLRequest(document: OnCreateTodoSubscription.document, variables: nil, responseType: OnCreateTodoSubscription.Data.self) @@ -371,9 +372,9 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { case .connection(let state): switch state { case .connecting: - await connectingInvoked.fulfill() + connectingInvoked.fulfill() case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: break } @@ -383,11 +384,11 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { } } catch { if let apiError = error as? APIError, apiError.isUnauthorized() { - await completedInvoked.fulfill() + completedInvoked.fulfill() } } } - await waitForExpectations([connectingInvoked, connectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectingInvoked, connectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: A successful subscription is created for CreateTodo's @@ -395,10 +396,11 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { /// Then: The subscription handler is called and Todo object is returned func testOnCreateTodoSubscription() async throws { try await createAuthenticatedUser() - let connectedInvoked = asyncExpectation(description: "Connection established") - let disconnectedInvoked = asyncExpectation(description: "Connection disconnected") - let completedInvoked = asyncExpectation(description: "Completed invoked") - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let disconnectedInvoked = expectation(description: "Connection disconnected") + let completedInvoked = expectation(description: "Completed invoked") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 let request = GraphQLRequest(document: OnCreateTodoSubscription.document, variables: nil, responseType: OnCreateTodoSubscription.Data.self) @@ -411,18 +413,18 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: - await disconnectedInvoked.fulfill() + disconnectedInvoked.fulfill() } case .data: - Task { await progressInvoked.fulfill() } + progressInvoked.fulfill() } } - await completedInvoked.fulfill() + completedInvoked.fulfill() } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) let name = testMethodName + "Name" @@ -439,9 +441,9 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) subscriptions.cancel() - await waitForExpectations([disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: A subscription is created for UpdateTodo's @@ -449,10 +451,12 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { /// Then: The subscription handler is called and Todo object is returned func testOnUpdateTodoSubscription() async throws { try await createAuthenticatedUser() - let connectedInvoked = asyncExpectation(description: "Connection established") - let disconnectedInvoked = asyncExpectation(description: "Connection disconnected") - let completedInvoked = asyncExpectation(description: "Completed invoked") - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: 2) + let connectedInvoked = expectation(description: "Connection established") + let disconnectedInvoked = expectation(description: "Connection disconnected") + let completedInvoked = expectation(description: "Completed invoked") + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = 2 + let request = GraphQLRequest(document: OnUpdateTodoSubscription.document, variables: nil, responseType: OnUpdateTodoSubscription.Data.self) @@ -465,17 +469,17 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { case .connecting: break case .connected: - Task { await connectedInvoked.fulfill() } + connectedInvoked.fulfill() case .disconnected: - Task { await disconnectedInvoked.fulfill() } + disconnectedInvoked.fulfill() } case .data: - Task { await progressInvoked.fulfill() } + progressInvoked.fulfill() } } - await completedInvoked.fulfill() + completedInvoked.fulfill() } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) let name = testMethodName + "Name" @@ -496,9 +500,9 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) subscriptions.cancel() - await waitForExpectations([disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: A subscription is created for DeleteTodo @@ -506,10 +510,10 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { /// Then: The subscription handler is called and Todo object is returned func testOnDeleteTodoSubscription() async throws { try await createAuthenticatedUser() - let connectedInvoked = asyncExpectation(description: "Connection established") - let disconnectedInvoked = asyncExpectation(description: "Connection disconnected") - let completedInvoked = asyncExpectation(description: "Completed invoked") - let progressInvoked = asyncExpectation(description: "progress invoked") + let connectedInvoked = expectation(description: "Connection established") + let disconnectedInvoked = expectation(description: "Connection disconnected") + let completedInvoked = expectation(description: "Completed invoked") + let progressInvoked = expectation(description: "progress invoked") let request = GraphQLRequest(document: OnDeleteTodoSubscription.document, variables: nil, responseType: OnDeleteTodoSubscription.Data.self) @@ -522,17 +526,17 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { case .connecting: break case .connected: - Task { await connectedInvoked.fulfill() } + connectedInvoked.fulfill() case .disconnected: - Task { await disconnectedInvoked.fulfill() } + disconnectedInvoked.fulfill() } case .data: - Task { await progressInvoked.fulfill() } + progressInvoked.fulfill() } } - await completedInvoked.fulfill() + completedInvoked.fulfill() } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) let name = testMethodName + "Name" @@ -548,10 +552,10 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) subscriptions.cancel() - await waitForExpectations([disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) } func testCreateMultipleSubscriptions() async throws { @@ -648,7 +652,7 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { } func createTodoSubscription() async -> AmplifyAsyncThrowingSequence> { - let connectedInvoked = asyncExpectation(description: "Connection established") + let connectedInvoked = expectation(description: "Connection established") let request = GraphQLRequest(document: OnCreateTodoSubscription.document, variables: nil, responseType: OnCreateTodoSubscription.Data.self) @@ -659,7 +663,7 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { case .connection(let state): switch state { case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() default: break } @@ -668,7 +672,7 @@ class GraphQLWithUserPoolIntegrationTests: XCTestCase { } } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) return subscriptions } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginIGraphQLAuthDirectiveTests/GraphQLAuthDirectiveIntegrationTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginIGraphQLAuthDirectiveTests/GraphQLAuthDirectiveIntegrationTests.swift index 9c1446ee73..d3d73f8061 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginIGraphQLAuthDirectiveTests/GraphQLAuthDirectiveIntegrationTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginIGraphQLAuthDirectiveTests/GraphQLAuthDirectiveIntegrationTests.swift @@ -145,7 +145,7 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { self.assertNotAuthenticated(error) failureInvoked.fulfill() } - wait(for: [failureInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [failureInvoked], timeout: TestCommonConstants.networkTimeout) } /// - Given: An API backend as per README.md with SocialNote schema @@ -168,17 +168,17 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { /// - When: An unauthrorized syncQuery request is made /// - Then: The request should fail func testUnauthorizedSyncQuery() async throws { - let failureInvoked = asyncExpectation(description: "failure invoked") + let failureInvoked = expectation(description: "failure invoked") let request = GraphQLRequest.syncQuery(modelType: SocialNote.self, limit: 1) do { _ = try await Amplify.API.query(request: request) XCTFail("Should not have completed successfully") } catch (let error as APIError){ self.assertNotAuthenticated(error) - await failureInvoked.fulfill() + failureInvoked.fulfill() } - await waitForExpectations([failureInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [failureInvoked], timeout: TestCommonConstants.networkTimeout) } /// An authorized user should not subscribe to mutation events @@ -187,8 +187,8 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { /// - Then: The request should succeed and the user should receive mutation events func testOnCreateSubscriptionOnlyWhenSignedIntoUserPool() async throws { try await signIn(username: user1.username, password: user1.password) - let connectedInvoked = asyncExpectation(description: "Connection established") - let progressInvoked = asyncExpectation(description: "Progress invoked") + let connectedInvoked = expectation(description: "Connection established") + let progressInvoked = expectation(description: "Progress invoked") let request = GraphQLRequest.subscription(to: SocialNote.self, subscriptionType: .onCreate) let subscription = Amplify.API.subscribe(request: request) @@ -198,12 +198,12 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { switch subscriptionEvent { case .connection(let state): if case .connected = state { - await connectedInvoked.fulfill() + connectedInvoked.fulfill() } case .data(let graphQLResponse): switch graphQLResponse { case .success: - await progressInvoked.fulfill() + progressInvoked.fulfill() case .failure(let error): XCTFail(error.errorDescription) } @@ -214,7 +214,7 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) do { _ = try await createNote(content: "owner created content") @@ -222,7 +222,7 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { XCTFail("Owner should be able to successfully create a note: \(error)") } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) subscription.cancel() } @@ -278,18 +278,18 @@ class GraphQLAuthDirectiveIntegrationTests: XCTestCase { } func syncQuery() async throws -> SyncQueryResult { - let syncQueryInvoked = asyncExpectation(description: "note was sync queried") + let syncQueryInvoked = expectation(description: "note was sync queried") var resultOptional: SyncQueryResult? let request = GraphQLRequest.syncQuery(modelType: SocialNote.self, limit: 1) let queryResult = try await Amplify.API.query(request: request) switch queryResult { case .success(let data): resultOptional = data - await syncQueryInvoked.fulfill() + syncQueryInvoked.fulfill() case .failure(let error): XCTFail("Got failed, error: \(error)") } - await waitForExpectations([syncQueryInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [syncQueryInvoked], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { fatalError("Failed to sync query note") } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/GraphQLLazyLoadBaseTest.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/GraphQLLazyLoadBaseTest.swift index 1587a757e3..219d3d549f 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/GraphQLLazyLoadBaseTest.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/GraphQLLazyLoadBaseTest.swift @@ -191,15 +191,15 @@ class GraphQLLazyLoadBaseTest: XCTestCase { of modelType: M.Type, type: GraphQLSubscriptionType, verifyChange: @escaping (M) async throws -> Bool - ) async throws -> (AsyncExpectation, AmplifyAsyncThrowingSequence>) { - let connected = asyncExpectation(description: "Subscription connected") - let eventReceived = asyncExpectation(description: "\(type.rawValue) received") + ) async throws -> (XCTestExpectation, AmplifyAsyncThrowingSequence>) { + let connected = expectation(description: "Subscription connected") + let eventReceived = expectation(description: "\(type.rawValue) received") let subscription = Amplify.API.subscribe(request: .subscription(of: modelType, type: type)) Task { for try await subscriptionEvent in subscription { if subscriptionEvent.isConnected() { - await connected.fulfill() + connected.fulfill() } if let error = subscriptionEvent.extractError() { @@ -209,12 +209,12 @@ class GraphQLLazyLoadBaseTest: XCTestCase { if let data = subscriptionEvent.extractData(), try await verifyChange(data) { - await eventReceived.fulfill() + eventReceived.fulfill() } } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) return (eventReceived, subscription) } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift index b429374f27..1b2e2a9838 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift @@ -301,8 +301,8 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4V2Models()) let post = Post(title: "title") try await mutate(.create(post)) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedComment = asyncExpectation(description: "onCreatedComment received") + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") let subscription = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate)) Task { do { @@ -311,14 +311,14 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdComment): log.verbose("Successfully got createdComment from subscription: \(createdComment)") assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) - await onCreatedComment.fulfill() + onCreatedComment.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -329,10 +329,10 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let comment = Comment(content: "content", post: post) try await mutate(.create(comment)) - await waitForExpectations([onCreatedComment], timeout: 10) + await fulfillment(of: [onCreatedComment], timeout: 10) subscription.cancel() } @@ -342,8 +342,8 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4V2Models()) let post = Post(title: "title") try await mutate(.create(post)) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedComment = asyncExpectation(description: "onCreatedComment received") + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate, includes: { comment in [comment.post]})) @@ -354,14 +354,14 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdComment): log.verbose("Successfully got createdComment from subscription: \(createdComment)") assertLazyReference(createdComment._post, state: .loaded(model: post)) - await onCreatedComment.fulfill() + onCreatedComment.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -372,10 +372,10 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 20) + await fulfillment(of: [connected], timeout: 20) let comment = Comment(content: "content", post: post) try await mutate(.create(comment, includes: { comment in [comment.post] })) - await waitForExpectations([onCreatedComment], timeout: 20) + await fulfillment(of: [onCreatedComment], timeout: 20) subscriptionIncludes.cancel() } @@ -383,8 +383,8 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4V2Models()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate)) Task { do { @@ -393,14 +393,14 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdPost): log.verbose("Successfully got createdPost from subscription: \(createdPost)") assertList(createdPost.comments!, state: .isNotLoaded(associatedIdentifiers: [post.id], associatedFields: ["post"])) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -411,9 +411,9 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post)) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscription.cancel() } @@ -421,8 +421,8 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4V2Models()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate, includes: { post in [post.comments]})) @@ -433,14 +433,14 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdPost): log.verbose("Successfully got createdPost from subscription: \(createdPost)") assertList(createdPost.comments!, state: .isLoaded(count: 0)) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -451,9 +451,9 @@ final class GraphQLLazyLoadPostComment4V2Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post, includes: { post in [post.comments]})) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscriptionIncludes.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/DefaultPK/GraphQLLazyLoadDefaultPKTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/DefaultPK/GraphQLLazyLoadDefaultPKTests.swift index 017e546030..a530aacb2e 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/DefaultPK/GraphQLLazyLoadDefaultPKTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/DefaultPK/GraphQLLazyLoadDefaultPKTests.swift @@ -118,7 +118,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { } try await mutate(.create(child)) - await waitForExpectations([onCreate], timeout: 10) + await fulfillment(of: [onCreate], timeout: 10) subscription.cancel() } @@ -146,7 +146,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.create(parent)) - await waitForExpectations([onCreate], timeout: 10) + await fulfillment(of: [onCreate], timeout: 10) subscription.cancel() } @@ -171,7 +171,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { var updatingChild = child updatingChild.content = UUID().uuidString try await mutate(.update(updatingChild)) - await waitForExpectations([onUpdate], timeout: 10) + await fulfillment(of: [onUpdate], timeout: 10) subscription.cancel() } @@ -208,7 +208,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { var updatingParent = parent updatingParent.content = UUID().uuidString try await mutate(.update(updatingParent)) - await waitForExpectations([onUpdate], timeout: 10) + await fulfillment(of: [onUpdate], timeout: 10) subscription.cancel() } @@ -230,7 +230,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.delete(child)) - await waitForExpectations([onDelete], timeout: 10) + await fulfillment(of: [onDelete], timeout: 10) subscription.cancel() } @@ -261,7 +261,7 @@ final class GraphQLLazyLoadDefaultPKTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.create(parent)) try await mutate(.delete(parent)) - await waitForExpectations([onDelete], timeout: 10) + await fulfillment(of: [onDelete], timeout: 10) subscription.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/HasOneParentChild/GraphQLLazyLoadHasOneTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/HasOneParentChild/GraphQLLazyLoadHasOneTests.swift index 86311ce52c..25f8a575b1 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/HasOneParentChild/GraphQLLazyLoadHasOneTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL12/HasOneParentChild/GraphQLLazyLoadHasOneTests.swift @@ -113,7 +113,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { newChild.identifier == child.identifier } try await mutate(.create(child)) - await waitForExpectations([onCreate], timeout: 10) + await fulfillment(of: [onCreate], timeout: 10) subscription.cancel() } @@ -139,7 +139,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.create(parent)) - await waitForExpectations([onCreate], timeout: 10) + await fulfillment(of: [onCreate], timeout: 10) subscription.cancel() } @@ -163,7 +163,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { var updatingChild = child updatingChild.content = UUID().uuidString try await mutate(.update(updatingChild)) - await waitForExpectations([onUpdate], timeout: 10) + await fulfillment(of: [onUpdate], timeout: 10) subscription.cancel() } @@ -197,7 +197,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { updatingParent.hasOneParentChildId = anotherChild.id try await mutate(.create(anotherChild)) try await mutate(.update(updatingParent)) - await waitForExpectations([onUpdate], timeout: 10) + await fulfillment(of: [onUpdate], timeout: 10) subscription.cancel() } @@ -219,7 +219,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.delete(child)) - await waitForExpectations([onDelete], timeout: 10) + await fulfillment(of: [onDelete], timeout: 10) subscription.cancel() } @@ -248,7 +248,7 @@ final class GraphQLLazyLoadHasOneTests: GraphQLLazyLoadBaseTest { try await mutate(.create(child)) try await mutate(.create(parent)) try await mutate(.delete(parent)) - await waitForExpectations([onDelete], timeout: 10) + await fulfillment(of: [onDelete], timeout: 10) subscription.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift index af57158bc3..b3ae40b05c 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift @@ -273,8 +273,8 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase await setup(withModels: PostCommentWithCompositeKeyModels()) let post = Post(title: "title") try await mutate(.create(post)) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedComment = asyncExpectation(description: "onCreatedComment received") + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") let subscription = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate)) Task { do { @@ -283,7 +283,7 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { @@ -291,7 +291,7 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase log.verbose("Successfully got createdComment from subscription: \(createdComment)") assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), .init(name: "title", value: post.title)])) - await onCreatedComment.fulfill() + onCreatedComment.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -302,10 +302,10 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let comment = Comment(content: "content", post: post) try await mutate(.create(comment)) - await waitForExpectations([onCreatedComment], timeout: 10) + await fulfillment(of: [onCreatedComment], timeout: 10) subscription.cancel() } @@ -315,8 +315,8 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase await setup(withModels: PostCommentWithCompositeKeyModels()) let post = Post(title: "title") try await mutate(.create(post)) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedComment = asyncExpectation(description: "onCreatedComment received") + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate, includes: { comment in [comment.post]})) @@ -327,14 +327,14 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdComment): log.verbose("Successfully got createdComment from subscription: \(createdComment)") assertLazyReference(createdComment._post, state: .loaded(model: post)) - await onCreatedComment.fulfill() + onCreatedComment.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -345,10 +345,10 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase } } - await waitForExpectations([connected], timeout: 20) + await fulfillment(of: [connected], timeout: 20) let comment = Comment(content: "content", post: post) try await mutate(.create(comment, includes: { comment in [comment.post] })) - await waitForExpectations([onCreatedComment], timeout: 20) + await fulfillment(of: [onCreatedComment], timeout: 20) subscriptionIncludes.cancel() } @@ -356,8 +356,8 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase await setup(withModels: PostCommentWithCompositeKeyModels()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate)) Task { do { @@ -366,14 +366,14 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdPost): log.verbose("Successfully got createdPost from subscription: \(createdPost)") assertList(createdPost.comments!, state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], associatedFields: ["post"])) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -384,9 +384,9 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post)) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscription.cancel() } @@ -394,8 +394,8 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase await setup(withModels: PostCommentWithCompositeKeyModels()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate, includes: { post in [post.comments]})) @@ -406,14 +406,14 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdPost): log.verbose("Successfully got createdPost from subscription: \(createdPost)") assertList(createdPost.comments!, state: .isLoaded(count: 0)) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -424,9 +424,9 @@ final class GraphQLLazyLoadPostCommentWithCompositeKeyTests: GraphQLLazyLoadBase } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post, includes: { post in [post.comments]})) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscriptionIncludes.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL7/GraphQLLazyLoadPostComment4Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL7/GraphQLLazyLoadPostComment4Tests.swift index 85b6a3d55f..4743a822f9 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL7/GraphQLLazyLoadPostComment4Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL7/GraphQLLazyLoadPostComment4Tests.swift @@ -171,8 +171,8 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4Models()) let post = Post(title: "title") try await mutate(.create(post)) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedComment = asyncExpectation(description: "onCreatedComment received") + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") let subscription = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate)) Task { do { @@ -181,7 +181,7 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { @@ -189,7 +189,7 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { log.verbose("Successfully got createdComment from subscription: \(createdComment)") XCTAssertEqual(createdComment.post4CommentsPostId, post.postId) XCTAssertEqual(createdComment.post4CommentsTitle, post.title) - await onCreatedComment.fulfill() + onCreatedComment.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -200,10 +200,10 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let comment = Comment(content: "content", post: post) try await mutate(.create(comment)) - await waitForExpectations([onCreatedComment], timeout: 10) + await fulfillment(of: [onCreatedComment], timeout: 10) subscription.cancel() } @@ -211,8 +211,8 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4Models()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate)) Task { do { @@ -221,7 +221,7 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { @@ -231,7 +231,7 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { post.title], associatedFields: ["post4CommentsPostId", "post4CommentsTitle"])) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -242,9 +242,9 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post)) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscription.cancel() } @@ -252,8 +252,8 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { await setup(withModels: PostComment4Models()) let post = Post(title: "title") - let connected = asyncExpectation(description: "subscription connected") - let onCreatedPost = asyncExpectation(description: "onCreatedPost received") + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate, includes: { post in [post.comments]})) @@ -264,14 +264,14 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdPost): log.verbose("Successfully got createdPost from subscription: \(createdPost)") assertList(createdPost.comments!, state: .isLoaded(count: 0)) - await onCreatedPost.fulfill() + onCreatedPost.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -282,9 +282,9 @@ final class GraphQLLazyLoadPostComment4Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) try await mutate(.create(post, includes: { post in [post.comments]})) - await waitForExpectations([onCreatedPost], timeout: 10) + await fulfillment(of: [onCreatedPost], timeout: 10) subscriptionIncludes.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL8/GraphQLLazyLoadProjectTeam5Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL8/GraphQLLazyLoadProjectTeam5Tests.swift index 28c7f3339e..d6fc3c3de6 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL8/GraphQLLazyLoadProjectTeam5Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL8/GraphQLLazyLoadProjectTeam5Tests.swift @@ -236,8 +236,8 @@ class GraphQLLazyLoadProjectTeam5Tests: GraphQLLazyLoadBaseTest { func testSubscribeToTeam() async throws { await setup(withModels: ProjectTeam5Models()) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedTeam = asyncExpectation(description: "onCreate received") + let connected = expectation(description: "subscription connected") + let onCreatedTeam = expectation(description: "onCreate received") let subscription = Amplify.API.subscribe(request: .subscription(of: Team.self, type: .onCreate)) Task { do { @@ -246,13 +246,13 @@ class GraphQLLazyLoadProjectTeam5Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdTeam): log.verbose("Successfully got createdTeam from subscription: \(createdTeam)") - await onCreatedTeam.fulfill() + onCreatedTeam.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -263,18 +263,19 @@ class GraphQLLazyLoadProjectTeam5Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let team = Team(teamId: UUID().uuidString, name: "name") let savedTeam = try await mutate(.create(team)) - await waitForExpectations([onCreatedTeam], timeout: 10) + _ = savedTeam + await fulfillment(of: [onCreatedTeam], timeout: 10) subscription.cancel() } func testSubscribeProject() async throws { await setup(withModels: ProjectTeam5Models()) - let connected = asyncExpectation(description: "subscription connected") - let onCreated = asyncExpectation(description: "onCreate received") + let connected = expectation(description: "subscription connected") + let onCreated = expectation(description: "onCreate received") let subscription = Amplify.API.subscribe(request: .subscription(of: Project.self, type: .onCreate)) Task { do { @@ -283,13 +284,13 @@ class GraphQLLazyLoadProjectTeam5Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let created): log.verbose("Successfully got model from subscription: \(created)") - await onCreated.fulfill() + onCreated.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -300,13 +301,14 @@ class GraphQLLazyLoadProjectTeam5Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let project = Project(projectId: UUID().uuidString, name: "name") let savedProject = try await mutate(.create(project)) - - await waitForExpectations([onCreated], timeout: 10) + _ = savedProject + + await fulfillment(of: [onCreated], timeout: 10) subscription.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL9/GraphQLLazyLoadProjectTeam6Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL9/GraphQLLazyLoadProjectTeam6Tests.swift index f71706caf8..f3f89ae3b2 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL9/GraphQLLazyLoadProjectTeam6Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/LL9/GraphQLLazyLoadProjectTeam6Tests.swift @@ -207,8 +207,8 @@ class GraphQLLazyLoadProjectTeam6Tests: GraphQLLazyLoadBaseTest { func testSubscribeToTeam() async throws { await setup(withModels: ProjectTeam6Models()) - let connected = asyncExpectation(description: "subscription connected") - let onCreatedTeam = asyncExpectation(description: "onCreate received") + let connected = expectation(description: "subscription connected") + let onCreatedTeam = expectation(description: "onCreate received") let subscription = Amplify.API.subscribe(request: .subscription(of: Team.self, type: .onCreate)) Task { do { @@ -217,13 +217,13 @@ class GraphQLLazyLoadProjectTeam6Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let createdTeam): log.verbose("Successfully got createdTeam from subscription: \(createdTeam)") - await onCreatedTeam.fulfill() + onCreatedTeam.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -234,18 +234,18 @@ class GraphQLLazyLoadProjectTeam6Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let team = Team(teamId: UUID().uuidString, name: "name") let savedTeam = try await mutate(.create(team)) - await waitForExpectations([onCreatedTeam], timeout: 10) + await fulfillment(of: [onCreatedTeam], timeout: 10) subscription.cancel() } func testSubscribeProject() async throws { await setup(withModels: ProjectTeam6Models()) - let connected = asyncExpectation(description: "subscription connected") - let onCreated = asyncExpectation(description: "onCreate received") + let connected = expectation(description: "subscription connected") + let onCreated = expectation(description: "onCreate received") let subscription = Amplify.API.subscribe(request: .subscription(of: Project.self, type: .onCreate)) Task { do { @@ -254,13 +254,13 @@ class GraphQLLazyLoadProjectTeam6Tests: GraphQLLazyLoadBaseTest { case .connection(let subscriptionConnectionState): log.verbose("Subscription connect state is \(subscriptionConnectionState)") if case .connected = subscriptionConnectionState { - await connected.fulfill() + connected.fulfill() } case .data(let result): switch result { case .success(let created): log.verbose("Successfully got model from subscription: \(created)") - await onCreated.fulfill() + onCreated.fulfill() case .failure(let error): XCTFail("Got failed result with \(error.errorDescription)") } @@ -271,13 +271,14 @@ class GraphQLLazyLoadProjectTeam6Tests: GraphQLLazyLoadBaseTest { } } - await waitForExpectations([connected], timeout: 10) + await fulfillment(of: [connected], timeout: 10) let project = Project(projectId: UUID().uuidString, name: "name") let savedProject = try await mutate(.create(project)) - - await waitForExpectations([onCreated], timeout: 10) + _ = savedProject + + await fulfillment(of: [onCreated], timeout: 10) subscription.cancel() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/GraphQLAPIStressTests/GraphQLAPIStressTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/GraphQLAPIStressTests/GraphQLAPIStressTests.swift index 82380d8410..2746a5fea3 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/GraphQLAPIStressTests/GraphQLAPIStressTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/GraphQLAPIStressTests/GraphQLAPIStressTests.swift @@ -66,10 +66,14 @@ final class APIStressTests: XCTestCase { /// - When: I create 50 subsciptions on createPost mutation and then create a Post /// - Then: Subscriptions should receive connected, disconnected and progress events correctly func testMultipleSubscriptions() async throws { - let connectedInvoked = asyncExpectation(description: "Connection established", expectedFulfillmentCount: concurrencyLimit) - let disconnectedInvoked = asyncExpectation(description: "Connection disconnected", expectedFulfillmentCount: concurrencyLimit) - let completedInvoked = asyncExpectation(description: "Completed invoked", expectedFulfillmentCount: concurrencyLimit) - let progressInvoked = asyncExpectation(description: "progress invoked", expectedFulfillmentCount: concurrencyLimit) + let connectedInvoked = expectation(description: "Connection established") + connectedInvoked.expectedFulfillmentCount = concurrencyLimit + let disconnectedInvoked = expectation(description: "Connection disconnected") + disconnectedInvoked.expectedFulfillmentCount = concurrencyLimit + let completedInvoked = expectation(description: "Completed invoked") + completedInvoked.expectedFulfillmentCount = concurrencyLimit + let progressInvoked = expectation(description: "progress invoked") + progressInvoked.expectedFulfillmentCount = concurrencyLimit let uuid = UUID().uuidString let testMethodName = String("\(#function)".dropLast(2)) @@ -87,29 +91,29 @@ final class APIStressTests: XCTestCase { case .connecting: break case .connected: - await connectedInvoked.fulfill() + connectedInvoked.fulfill() case .disconnected: - await disconnectedInvoked.fulfill() + disconnectedInvoked.fulfill() } case .data(let result): switch result { case .success(let post): if post.id == uuid { - await progressInvoked.fulfill() + progressInvoked.fulfill() } case .failure(let error): XCTFail("\(error)") } } } - await completedInvoked.fulfill() + completedInvoked.fulfill() } await sequenceActor.append(sequence: subscription) } } - await waitForExpectations([connectedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [connectedInvoked], timeout: TestCommonConstants.networkTimeout) let sequenceCount = await sequenceActor.sequences.count XCTAssertEqual(sequenceCount, concurrencyLimit) @@ -119,7 +123,7 @@ final class APIStressTests: XCTestCase { return } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { @@ -127,15 +131,15 @@ final class APIStressTests: XCTestCase { } } - await waitForExpectations([disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout) } /// - Given: APIPlugin configured with valid configuration and schema /// - When: I create 50 posts simultaneously /// - Then: Operation should succeed func testMultipleCreateMutations() async throws { - let postCreateExpectation = asyncExpectation(description: "Post was created successfully", - expectedFulfillmentCount: concurrencyLimit) + let postCreateExpectation = expectation(description: "Post was created successfully") + postCreateExpectation.expectedFulfillmentCount = concurrencyLimit DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { let id = UUID().uuidString @@ -144,21 +148,21 @@ final class APIStressTests: XCTestCase { XCTAssertNotNil(post) XCTAssertEqual(id, post?.id) XCTAssertEqual(title, post?.title) - await postCreateExpectation.fulfill() + postCreateExpectation.fulfill() } } - await waitForExpectations([postCreateExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postCreateExpectation], timeout: TestCommonConstants.networkTimeout) } /// - Given: APIPlugin configured with valid configuration and schema and 50 posts saved /// - When: I update 50 post simultaneously /// - Then: Operation should succeed func testMultipleUpdateMutations() async throws { - let postCreateExpectation = asyncExpectation(description: "Post was created successfully", - expectedFulfillmentCount: concurrencyLimit) - let postUpdateExpectation = asyncExpectation(description: "Post was updated successfully", - expectedFulfillmentCount: concurrencyLimit) + let postCreateExpectation = expectation(description: "Post was created successfully") + postCreateExpectation.expectedFulfillmentCount = concurrencyLimit + let postUpdateExpectation = expectation(description: "Post was updated successfully") + postUpdateExpectation.expectedFulfillmentCount = concurrencyLimit let postActor = PostActor() DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in @@ -170,11 +174,11 @@ final class APIStressTests: XCTestCase { XCTAssertEqual(id, post?.id) XCTAssertEqual(title, post?.title) await postActor.append(post: post!) - await postCreateExpectation.fulfill() + postCreateExpectation.fulfill() } } - await waitForExpectations([postCreateExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postCreateExpectation], timeout: TestCommonConstants.networkTimeout) DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { @@ -184,21 +188,23 @@ final class APIStressTests: XCTestCase { XCTAssertNotNil(updatedPost) XCTAssertEqual(post.id, updatedPost.id) XCTAssertEqual(post.title, updatedPost.title) - await postUpdateExpectation.fulfill() + postUpdateExpectation.fulfill() } } - await waitForExpectations([postUpdateExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postUpdateExpectation], timeout: TestCommonConstants.networkTimeout) } /// - Given: APIPlugin configured with valid configuration, schema and 50 posts saved /// - When: I delete 50 post simultaneously /// - Then: Operation should succeed func testMultipleDeleteMutations() async throws { - let postCreateExpectation = asyncExpectation(description: "Post was created successfully", - expectedFulfillmentCount: concurrencyLimit) - let postDeleteExpectation = asyncExpectation(description: "Post was deleted successfully", - expectedFulfillmentCount: concurrencyLimit) + let postCreateExpectation = expectation(description: "Post was created successfully") + postCreateExpectation.expectedFulfillmentCount = concurrencyLimit + + let postDeleteExpectation = expectation(description: "Post was deleted successfully") + postDeleteExpectation.expectedFulfillmentCount = concurrencyLimit + let postActor = PostActor() DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in @@ -210,11 +216,11 @@ final class APIStressTests: XCTestCase { XCTAssertEqual(id, post?.id) XCTAssertEqual(title, post?.title) await postActor.append(post: post!) - await postCreateExpectation.fulfill() + postCreateExpectation.fulfill() } } - await waitForExpectations([postCreateExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postCreateExpectation], timeout: TestCommonConstants.networkTimeout) DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { @@ -223,21 +229,23 @@ final class APIStressTests: XCTestCase { XCTAssertNotNil(deletedPost) XCTAssertEqual(post.id, deletedPost.id) XCTAssertEqual(post.title, deletedPost.title) - await postDeleteExpectation.fulfill() + postDeleteExpectation.fulfill() } } - await waitForExpectations([postDeleteExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postDeleteExpectation], timeout: TestCommonConstants.networkTimeout) } /// - Given: APIPlugin configured with valid configuration, schema and 50 posts saved /// - When: I query for 50 posts simultaneously /// - Then: Operation should succeed func testMultipleQueryByID() async throws { - let postCreateExpectation = asyncExpectation(description: "Post was created successfully", - expectedFulfillmentCount: concurrencyLimit) - let postQueryExpectation = asyncExpectation(description: "Post was deleted successfully", - expectedFulfillmentCount: concurrencyLimit) + let postCreateExpectation = expectation(description: "Post was created successfully") + postCreateExpectation.expectedFulfillmentCount = concurrencyLimit + + let postQueryExpectation = expectation(description: "Post was deleted successfully") + postQueryExpectation.expectedFulfillmentCount = concurrencyLimit + let postActor = PostActor() DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in @@ -249,11 +257,11 @@ final class APIStressTests: XCTestCase { XCTAssertEqual(id, post?.id) XCTAssertEqual(title, post?.title) await postActor.append(post: post!) - await postCreateExpectation.fulfill() + postCreateExpectation.fulfill() } } - await waitForExpectations([postCreateExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postCreateExpectation], timeout: TestCommonConstants.networkTimeout) DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { @@ -270,11 +278,11 @@ final class APIStressTests: XCTestCase { XCTAssertNotNil(queriedPost) XCTAssertEqual(post.id, queriedPost.id) XCTAssertEqual(post.title, queriedPost.title) - await postQueryExpectation.fulfill() + postQueryExpectation.fulfill() } } - await waitForExpectations([postQueryExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [postQueryExpectation], timeout: TestCommonConstants.networkTimeout) } actor PostActor { diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift index e197b7e0bb..0116c9c206 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift @@ -108,14 +108,14 @@ class AppSyncListProviderTests: XCTestCase { func testLoadedStateLoadSuccess() async throws { let elements = [Post4(title: "title"), Post4(title: "title")] let listProvider = AppSyncListProvider(elements: elements) - let loadCompleted = asyncExpectation(description: "Load Completed") + let loadCompleted = expectation(description: "Load Completed") Task { let posts = try await listProvider.load() XCTAssertEqual(posts.count, 2) - await loadCompleted.fulfill() + loadCompleted.fulfill() } - await waitForExpectations([loadCompleted], timeout: 1) + await fulfillment(of: [loadCompleted], timeout: 1) } func testNotLoadedStateLoadSuccess() async throws { @@ -144,13 +144,13 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadCompleted = asyncExpectation(description: "Load Completed") + let loadCompleted = expectation(description: "Load Completed") Task { _ = try await provider.load() - await loadCompleted.fulfill() + loadCompleted.fulfill() } - await waitForExpectations([loadCompleted], timeout: 1) + await fulfillment(of: [loadCompleted], timeout: 1) guard case .loaded(let elements, let nextToken, let filterOptional) = provider.loadedState else { XCTFail("Should be loaded") @@ -181,7 +181,7 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadCompleted = asyncExpectation(description: "Load Completed") + let loadCompleted = expectation(description: "Load Completed") Task { do { _ = try await provider.load() @@ -196,10 +196,10 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - await loadCompleted.fulfill() + loadCompleted.fulfill() } } - await waitForExpectations([loadCompleted], timeout: 1) + await fulfillment(of: [loadCompleted], timeout: 1) } func testNotLoadedStateLoadWithCompletionSuccess() async { @@ -228,13 +228,13 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadComplete = asyncExpectation(description: "Load completed") + let loadComplete = expectation(description: "Load completed") Task { _ = try await provider.load() - await loadComplete.fulfill() + loadComplete.fulfill() } - await waitForExpectations([loadComplete], timeout: 1) + await fulfillment(of: [loadComplete], timeout: 1) guard case .loaded(let elements, let nextToken, let filterOptional) = provider.loadedState else { XCTFail("Should be loaded") @@ -265,7 +265,7 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadComplete = asyncExpectation(description: "Load completed") + let loadComplete = expectation(description: "Load completed") Task { do { _ = try await provider.load() @@ -276,10 +276,10 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Unexpected error \(error)") return } - await loadComplete.fulfill() + loadComplete.fulfill() } } - await waitForExpectations([loadComplete], timeout: 1) + await fulfillment(of: [loadComplete], timeout: 1) guard case .notLoaded = provider.loadedState else { XCTFail("Should not be loaded") return @@ -301,7 +301,7 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadComplete = asyncExpectation(description: "Load completed") + let loadComplete = expectation(description: "Load completed") Task { do { @@ -313,11 +313,11 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Unexpected error \(error)") return } - await loadComplete.fulfill() + loadComplete.fulfill() } } - await waitForExpectations([loadComplete], timeout: 1) + await fulfillment(of: [loadComplete], timeout: 1) guard case .notLoaded = provider.loadedState else { XCTFail("Should not be loaded") return @@ -350,7 +350,7 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Should not be loaded") return } - let loadComplete = asyncExpectation(description: "Load completed") + let loadComplete = expectation(description: "Load completed") Task { do { _ = try await provider.load() @@ -360,11 +360,11 @@ class AppSyncListProviderTests: XCTestCase { XCTFail("Unexpected error \(error)") return } - await loadComplete.fulfill() + loadComplete.fulfill() } } - await waitForExpectations([loadComplete], timeout: 1) + await fulfillment(of: [loadComplete], timeout: 1) guard case .notLoaded = provider.loadedState else { XCTFail("Should not be loaded") return diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift index 3df0708918..b54fe33c70 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift @@ -110,8 +110,8 @@ class AWSGraphQLSubscriptionOperationCancelTests: XCTestCase { valueListener: valueListener, completionListener: completionListener ) - await waitForExpectations(timeout: 5) - + await fulfillment(of: [receivedValueConnecting], timeout: 5) + let receivedCompletion = expectation(description: "Received completion") let receivedFailure = expectation(description: "Received failure") receivedFailure.isInverted = true @@ -145,7 +145,11 @@ class AWSGraphQLSubscriptionOperationCancelTests: XCTestCase { operation.cancel() XCTAssert(operation.isCancelled) - await waitForExpectations(timeout: 5) + + await fulfillment( + of: [receivedCompletion, receivedFailure, receivedValueDisconnected], + timeout: 5 + ) } func testFailureOnConnection() async { @@ -184,7 +188,12 @@ class AWSGraphQLSubscriptionOperationCancelTests: XCTestCase { valueListener: valueListener, completionListener: completionListener ) - await waitForExpectations(timeout: 0.3) + + await fulfillment( + of: [receivedCompletion, receivedFailure, receivedValue], + timeout: 0.3 + ) + XCTAssert(operation.isFinished) } @@ -221,7 +230,11 @@ class AWSGraphQLSubscriptionOperationCancelTests: XCTestCase { valueListener: valueListener, completionListener: nil ) - await waitForExpectations(timeout: 5) + await fulfillment( + of: [receivedValue, connectionCreation], + timeout: 5 + ) + let receivedFailure = expectation(description: "Received failure") receivedFailure.isInverted = true let receivedCompletion = expectation(description: "Received completion") @@ -237,6 +250,9 @@ class AWSGraphQLSubscriptionOperationCancelTests: XCTestCase { operation.cancel() XCTAssert(operation.isCancelled) - await waitForExpectations(timeout: 5) + await fulfillment( + of: [receivedCompletion, receivedFailure], + timeout: 5 + ) } } diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift index 7f510e2e97..583886dff3 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift @@ -84,10 +84,11 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase { variables: nil, responseType: JSONValue.self) - let receivedValueConnecting = asyncExpectation(description: "Received value for connecting") - let receivedValueDisconnected = asyncExpectation(description: "Received value for disconnected") - let receivedCompletion = asyncExpectation(description: "Received completion") - let receivedFailure = asyncExpectation(description: "Received failure", isInverted: true) + let receivedValueConnecting = expectation(description: "Received value for connecting") + let receivedValueDisconnected = expectation(description: "Received value for disconnected") + let receivedCompletion = expectation(description: "Received completion") + let receivedFailure = expectation(description: "Received failure") + receivedFailure.isInverted = true let subscriptionEvents = apiPlugin.subscribe(request: request) Task { do { @@ -96,9 +97,9 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase { case .connection(let state): switch state { case .connecting: - await receivedValueConnecting.fulfill() + receivedValueConnecting.fulfill() case .disconnected: - await receivedValueDisconnected.fulfill() + receivedValueDisconnected.fulfill() default: XCTFail("Unexpected value on value listener: \(state)") } @@ -106,14 +107,14 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase { XCTFail("Unexpected value on on value listener: \(subscriptionEvent)") } } - await receivedCompletion.fulfill() + receivedCompletion.fulfill() } catch { - await receivedFailure.fulfill() + receivedFailure.fulfill() } } - await waitForExpectations([receivedValueConnecting]) + await fulfillment(of: [receivedValueConnecting], timeout: 1) subscriptionEvents.cancel() - await waitForExpectations([receivedValueDisconnected, receivedCompletion, receivedFailure]) + await fulfillment(of: [receivedValueDisconnected, receivedCompletion, receivedFailure], timeout: 1) } func testFailureOnConnection() async { @@ -128,29 +129,31 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase { variables: nil, responseType: JSONValue.self) - let receivedCompletion = asyncExpectation(description: "Received completion", isInverted: true) - let receivedFailure = asyncExpectation(description: "Received failure") - let receivedValue = asyncExpectation(description: "Received value for connecting", isInverted: true) + let receivedCompletion = expectation(description: "Received completion") + receivedCompletion.isInverted = true + let receivedFailure = expectation(description: "Received failure") + let receivedValue = expectation(description: "Received value for connecting") + receivedValue.isInverted = true let subscriptionEvents = apiPlugin.subscribe(request: request) Task { do { for try await _ in subscriptionEvents { - await receivedValue.fulfill() + receivedValue.fulfill() } - await receivedCompletion.fulfill() + receivedCompletion.fulfill() } catch { - await receivedFailure.fulfill() + receivedFailure.fulfill() } } - await waitForExpectations([receivedValue, receivedFailure, receivedCompletion], timeout: 0.3) + await fulfillment(of: [receivedValue, receivedFailure, receivedCompletion], timeout: 0.3) } func testCallingCancelWhileCreatingConnectionShouldCallCompletionListener() async { - let connectionCreation = asyncExpectation(description: "connection factory called") + let connectionCreation = expectation(description: "connection factory called") let mockSubscriptionConnectionFactory = MockSubscriptionConnectionFactory(onGetOrCreateConnection: { _, _, _, _, _ in - Task { await connectionCreation.fulfill() } + connectionCreation.fulfill() return MockSubscriptionConnection(onSubscribe: { (_, _, eventHandler) -> SubscriptionItem in let item = SubscriptionItem(requestString: "", variables: nil, eventHandler: { _, _ in }) @@ -167,23 +170,28 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase { variables: nil, responseType: JSONValue.self) - let receivedValue = asyncExpectation(description: "Received value for connecting", expectedFulfillmentCount: 1) - let receivedFailure = asyncExpectation(description: "Received failure", isInverted: true) - let receivedCompletion = asyncExpectation(description: "Received completion") + let receivedValue = expectation(description: "Received value for connecting") + receivedValue.expectedFulfillmentCount = 1 + receivedValue.assertForOverFulfill = false + + let receivedFailure = expectation(description: "Received failure") + receivedFailure.isInverted = true + + let receivedCompletion = expectation(description: "Received completion") let subscriptionEvents = apiPlugin.subscribe(request: request) Task { do { for try await _ in subscriptionEvents { - await receivedValue.fulfill() + receivedValue.fulfill() } - await receivedCompletion.fulfill() + receivedCompletion.fulfill() } catch { - await receivedFailure.fulfill() + receivedFailure.fulfill() } } - await waitForExpectations([receivedValue, connectionCreation], timeout: 5) + await fulfillment(of: [receivedValue, connectionCreation], timeout: 5) subscriptionEvents.cancel() - await waitForExpectations([receivedFailure, receivedCompletion], timeout: 5) + await fulfillment(of: [receivedFailure, receivedCompletion], timeout: 5) } } diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeCombineTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeCombineTests.swift index 7513e57244..8fbc7bcbbc 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeCombineTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeCombineTests.swift @@ -19,18 +19,18 @@ class GraphQLSubscribeCombineTests: OperationTestBase { var sink: AnyCancellable? // Setup expectations - var onSubscribeInvoked: AsyncExpectation! - var receivedCompletionSuccess: AsyncExpectation! - var receivedCompletionFailure: AsyncExpectation! - + var onSubscribeInvoked: XCTestExpectation! + var receivedCompletionSuccess: XCTestExpectation! + var receivedCompletionFailure: XCTestExpectation! + // Subscription state expectations - var receivedStateValueConnecting: AsyncExpectation! - var receivedStateValueConnected: AsyncExpectation! - var receivedStateValueDisconnected: AsyncExpectation! + var receivedStateValueConnecting: XCTestExpectation! + var receivedStateValueConnected: XCTestExpectation! + var receivedStateValueDisconnected: XCTestExpectation! // Subscription item expectations - var receivedDataValueSuccess: AsyncExpectation! - var receivedDataValueError: AsyncExpectation! + var receivedDataValueSuccess: XCTestExpectation! + var receivedDataValueError: XCTestExpectation! // Handles to the subscription item and event handler used to make mock calls into the // subscription system @@ -43,22 +43,22 @@ class GraphQLSubscribeCombineTests: OperationTestBase { override func setUp() async throws { try await super.setUp() - onSubscribeInvoked = asyncExpectation(description: "onSubscribeInvoked") + onSubscribeInvoked = expectation(description: "onSubscribeInvoked") - receivedCompletionSuccess = asyncExpectation(description: "receivedStateCompletionSuccess") - receivedCompletionFailure = asyncExpectation(description: "receivedStateCompletionFailure") - receivedStateValueConnecting = asyncExpectation(description: "receivedStateValueConnecting") - receivedStateValueConnected = asyncExpectation(description: "receivedStateValueConnected") - receivedStateValueDisconnected = asyncExpectation(description: "receivedStateValueDisconnected") + receivedCompletionSuccess = expectation(description: "receivedStateCompletionSuccess") + receivedCompletionFailure = expectation(description: "receivedStateCompletionFailure") + receivedStateValueConnecting = expectation(description: "receivedStateValueConnecting") + receivedStateValueConnected = expectation(description: "receivedStateValueConnected") + receivedStateValueDisconnected = expectation(description: "receivedStateValueDisconnected") - receivedDataValueSuccess = asyncExpectation(description: "receivedDataValueSuccess") - receivedDataValueError = asyncExpectation(description: "receivedDataValueError") + receivedDataValueSuccess = expectation(description: "receivedDataValueSuccess") + receivedDataValueError = expectation(description: "receivedDataValueError") try setUpMocksAndSubscriptionItems() } func waitForSubscriptionExpectations() async { - await waitForExpectations([receivedCompletionSuccess, + await fulfillment(of: [receivedCompletionSuccess, receivedCompletionFailure, receivedStateValueConnecting, receivedStateValueConnected, @@ -68,20 +68,14 @@ class GraphQLSubscribeCombineTests: OperationTestBase { } func testHappyPath() async throws { - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueError.isInverted = true let testJSON: JSONValue = ["foo": true] let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)! try await subscribe(expecting: testJSON) - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -92,17 +86,12 @@ class GraphQLSubscribeCombineTests: OperationTestBase { } func testConnectionWithNoData() async throws { - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -112,17 +101,14 @@ class GraphQLSubscribeCombineTests: OperationTestBase { } func testConnectionError() async throws { - await receivedCompletionSuccess.setShouldTrigger(false) - await receivedCompletionFailure.setShouldTrigger(true) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(false) - await receivedStateValueDisconnected.setShouldTrigger(false) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionSuccess.isInverted = true + receivedStateValueConnected.isInverted = true + receivedStateValueDisconnected.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.failed("Error"), subscriptionItem) @@ -132,17 +118,11 @@ class GraphQLSubscribeCombineTests: OperationTestBase { func testDecodingError() async throws { let testData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(true) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -155,18 +135,12 @@ class GraphQLSubscribeCombineTests: OperationTestBase { func testMultipleSuccessValues() async throws { let testJSON: JSONValue = ["foo": true] let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueSuccess.setExpectedFulfillmentCount(2) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueError.isInverted = true + receivedDataValueSuccess.expectedFulfillmentCount = 2 try await subscribe(expecting: testJSON) - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -180,18 +154,11 @@ class GraphQLSubscribeCombineTests: OperationTestBase { func testMixedSuccessAndErrorValues() async throws { let successfulTestData = #"{"data": {"foo": true}}"# .data(using: .utf8)! let invalidTestData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueSuccess.setExpectedFulfillmentCount(2) - await receivedDataValueError.setShouldTrigger(true) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.expectedFulfillmentCount = 2 try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -219,7 +186,7 @@ class GraphQLSubscribeCombineTests: OperationTestBase { self.subscriptionItem = item self.subscriptionEventHandler = eventHandler - Task { await self.onSubscribeInvoked.fulfill() } + self.onSubscribeInvoked.fulfill() return item } @@ -247,20 +214,20 @@ class GraphQLSubscribeCombineTests: OperationTestBase { sink = Amplify.Publisher.create(subscription).sink { completion in switch completion { case .failure: - Task { await self.receivedCompletionFailure.fulfill() } + self.receivedCompletionFailure.fulfill() case .finished: - Task { await self.receivedCompletionSuccess.fulfill() } + self.receivedCompletionSuccess.fulfill() } } receiveValue: { subscriptionEvent in switch subscriptionEvent { case .connection(let connectionState): switch connectionState { case .connecting: - Task { await self.receivedStateValueConnecting.fulfill() } + self.receivedStateValueConnecting.fulfill() case .connected: - Task { await self.receivedStateValueConnected.fulfill() } + self.receivedStateValueConnected.fulfill() case .disconnected: - Task { await self.receivedStateValueDisconnected.fulfill() } + self.receivedStateValueDisconnected.fulfill() } case .data(let result): switch result { @@ -268,9 +235,9 @@ class GraphQLSubscribeCombineTests: OperationTestBase { if let expectedValue = expectedValue { XCTAssertEqual(actualValue, expectedValue) } - Task { await self.receivedDataValueSuccess.fulfill() } + self.receivedDataValueSuccess.fulfill() case .failure: - Task { await self.receivedDataValueError.fulfill() } + self.receivedDataValueError.fulfill() } } } diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift index 33899e92c0..b637a816f9 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift @@ -17,18 +17,18 @@ import AppSyncRealTimeClient class GraphQLSubscribeTasksTests: OperationTestBase { // Setup expectations - var onSubscribeInvoked: AsyncExpectation! - var receivedCompletionSuccess: AsyncExpectation! - var receivedCompletionFailure: AsyncExpectation! - + var onSubscribeInvoked: XCTestExpectation! + var receivedCompletionSuccess: XCTestExpectation! + var receivedCompletionFailure: XCTestExpectation! + // Subscription state expectations - var receivedStateValueConnecting: AsyncExpectation! - var receivedStateValueConnected: AsyncExpectation! - var receivedStateValueDisconnected: AsyncExpectation! + var receivedStateValueConnecting: XCTestExpectation! + var receivedStateValueConnected: XCTestExpectation! + var receivedStateValueDisconnected: XCTestExpectation! // Subscription item expectations - var receivedDataValueSuccess: AsyncExpectation! - var receivedDataValueError: AsyncExpectation! + var receivedDataValueSuccess: XCTestExpectation! + var receivedDataValueError: XCTestExpectation! // Handles to the subscription item and event handler used to make mock calls into the // subscription system @@ -42,45 +42,44 @@ class GraphQLSubscribeTasksTests: OperationTestBase { override func setUp() async throws { try await super.setUp() - onSubscribeInvoked = asyncExpectation(description: "onSubscribeInvoked") + onSubscribeInvoked = expectation(description: "onSubscribeInvoked") - receivedCompletionSuccess = asyncExpectation(description: "receivedStateCompletionSuccess") - receivedCompletionFailure = asyncExpectation(description: "receivedStateCompletionFailure") - receivedStateValueConnecting = asyncExpectation(description: "receivedStateValueConnecting") - receivedStateValueConnected = asyncExpectation(description: "receivedStateValueConnected") - receivedStateValueDisconnected = asyncExpectation(description: "receivedStateValueDisconnected") + receivedCompletionSuccess = expectation(description: "receivedStateCompletionSuccess") + receivedCompletionFailure = expectation(description: "receivedStateCompletionFailure") + receivedStateValueConnecting = expectation(description: "receivedStateValueConnecting") + receivedStateValueConnected = expectation(description: "receivedStateValueConnected") + receivedStateValueDisconnected = expectation(description: "receivedStateValueDisconnected") - receivedDataValueSuccess = asyncExpectation(description: "receivedDataValueSuccess") - receivedDataValueError = asyncExpectation(description: "receivedDataValueError") + receivedDataValueSuccess = expectation(description: "receivedDataValueSuccess") + receivedDataValueError = expectation(description: "receivedDataValueError") try setUpMocksAndSubscriptionItems() } func waitForSubscriptionExpectations() async { - await waitForExpectations([receivedCompletionSuccess, - receivedCompletionFailure, - receivedStateValueConnecting, - receivedStateValueConnected, - receivedStateValueDisconnected, - receivedDataValueSuccess, - receivedDataValueError], timeout: 0.05) + await fulfillment( + of: [ + receivedCompletionSuccess, + receivedCompletionFailure, + receivedStateValueConnecting, + receivedStateValueConnected, + receivedStateValueDisconnected, + receivedDataValueSuccess, + receivedDataValueError + ], + timeout: 0.05 + ) } func testHappyPath() async throws { - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueError.isInverted = true let testJSON: JSONValue = ["foo": true] let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)! try await subscribe(expecting: testJSON) - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -91,17 +90,12 @@ class GraphQLSubscribeTasksTests: OperationTestBase { } func testConnectionWithNoData() async throws { - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -111,17 +105,14 @@ class GraphQLSubscribeTasksTests: OperationTestBase { } func testConnectionErrorWithLimitExceeded() async throws { - await receivedCompletionSuccess.setShouldTrigger(false) - await receivedCompletionFailure.setShouldTrigger(true) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(false) - await receivedStateValueDisconnected.setShouldTrigger(false) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionSuccess.isInverted = true + receivedStateValueConnected.isInverted = true + receivedStateValueDisconnected.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.failed(ConnectionProviderError.limitExceeded(nil)), subscriptionItem) @@ -130,17 +121,14 @@ class GraphQLSubscribeTasksTests: OperationTestBase { } func testConnectionErrorWithSubscriptionError() async throws { - await receivedCompletionSuccess.setShouldTrigger(false) - await receivedCompletionFailure.setShouldTrigger(true) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(false) - await receivedStateValueDisconnected.setShouldTrigger(false) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionSuccess.isInverted = true + receivedStateValueConnected.isInverted = true + receivedStateValueDisconnected.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.failed(ConnectionProviderError.subscription("", nil)), subscriptionItem) @@ -149,17 +137,14 @@ class GraphQLSubscribeTasksTests: OperationTestBase { } func testConnectionErrorWithConnectionUnauthorizedError() async throws { - await receivedCompletionSuccess.setShouldTrigger(false) - await receivedCompletionFailure.setShouldTrigger(true) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(false) - await receivedStateValueDisconnected.setShouldTrigger(false) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionSuccess.isInverted = true + receivedStateValueConnected.isInverted = true + receivedStateValueDisconnected.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.failed(ConnectionProviderError.unauthorized), subscriptionItem) @@ -168,17 +153,14 @@ class GraphQLSubscribeTasksTests: OperationTestBase { } func testConnectionErrorWithConnectionProviderConnectionError() async throws { - await receivedCompletionSuccess.setShouldTrigger(false) - await receivedCompletionFailure.setShouldTrigger(true) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(false) - await receivedStateValueDisconnected.setShouldTrigger(false) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionSuccess.isInverted = true + receivedStateValueConnected.isInverted = true + receivedStateValueDisconnected.isInverted = true + receivedDataValueSuccess.isInverted = true + receivedDataValueError.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.failed(ConnectionProviderError.connection), subscriptionItem) @@ -188,17 +170,11 @@ class GraphQLSubscribeTasksTests: OperationTestBase { func testDecodingError() async throws { let testData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - - await receivedDataValueSuccess.setShouldTrigger(false) - await receivedDataValueError.setShouldTrigger(true) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.isInverted = true try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -211,18 +187,13 @@ class GraphQLSubscribeTasksTests: OperationTestBase { func testMultipleSuccessValues() async throws { let testJSON: JSONValue = ["foo": true] let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueSuccess.setExpectedFulfillmentCount(2) - await receivedDataValueError.setShouldTrigger(false) + receivedCompletionFailure.isInverted = true + receivedDataValueError.isInverted = true + receivedDataValueSuccess.expectedFulfillmentCount = 2 try await subscribe(expecting: testJSON) - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -236,18 +207,12 @@ class GraphQLSubscribeTasksTests: OperationTestBase { func testMixedSuccessAndErrorValues() async throws { let successfulTestData = #"{"data": {"foo": true}}"# .data(using: .utf8)! let invalidTestData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)! - await receivedCompletionSuccess.setShouldTrigger(true) - await receivedCompletionFailure.setShouldTrigger(false) - await receivedStateValueConnecting.setShouldTrigger(true) - await receivedStateValueConnected.setShouldTrigger(true) - await receivedStateValueDisconnected.setShouldTrigger(true) - await receivedDataValueSuccess.setShouldTrigger(true) - await receivedDataValueSuccess.setExpectedFulfillmentCount(2) - await receivedDataValueError.setShouldTrigger(true) + receivedCompletionFailure.isInverted = true + receivedDataValueSuccess.expectedFulfillmentCount = 2 try await subscribe() - await waitForExpectations([onSubscribeInvoked], timeout: 0.05) + await fulfillment(of: [onSubscribeInvoked], timeout: 0.05) subscriptionEventHandler(.connection(.connecting), subscriptionItem) subscriptionEventHandler(.connection(.connected), subscriptionItem) @@ -275,7 +240,7 @@ class GraphQLSubscribeTasksTests: OperationTestBase { self.subscriptionItem = item self.subscriptionEventHandler = eventHandler - Task { await self.onSubscribeInvoked.fulfill() } + self.onSubscribeInvoked.fulfill() return item } @@ -307,11 +272,11 @@ class GraphQLSubscribeTasksTests: OperationTestBase { case .connection(let connectionState): switch connectionState { case .connecting: - await self.receivedStateValueConnecting.fulfill() + self.receivedStateValueConnecting.fulfill() case .connected: - await self.receivedStateValueConnected.fulfill() + self.receivedStateValueConnected.fulfill() case .disconnected: - await self.receivedStateValueDisconnected.fulfill() + self.receivedStateValueDisconnected.fulfill() } case .data(let result): switch result { @@ -319,21 +284,21 @@ class GraphQLSubscribeTasksTests: OperationTestBase { if let expectedValue = expectedValue { XCTAssertEqual(actualValue, expectedValue) } - await self.receivedDataValueSuccess.fulfill() + self.receivedDataValueSuccess.fulfill() case .failure: - await self.receivedDataValueError.fulfill() + self.receivedDataValueError.fulfill() } } } - await self.receivedCompletionSuccess.fulfill() + self.receivedCompletionSuccess.fulfill() } catch { if let apiError = error as? APIError, let expectedError = expectedCompletionFailureError { XCTAssertEqual(apiError, expectedError) } - await self.receivedCompletionFailure.fulfill() + self.receivedCompletionFailure.fulfill() } } } diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Support/Decode/GraphQLResponseDecoder+DecodeDataTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Support/Decode/GraphQLResponseDecoder+DecodeDataTests.swift index c58fd5c3fe..332fcd44fd 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Support/Decode/GraphQLResponseDecoder+DecodeDataTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Support/Decode/GraphQLResponseDecoder+DecodeDataTests.swift @@ -111,14 +111,14 @@ extension GraphQLResponseDecoderTests { let result = try decoder.decodeToResponseType(graphQLData) XCTAssertNotNil(result) - let fetchCompleted = asyncExpectation(description: "Fetch completed") + let fetchCompleted = expectation(description: "Fetch completed") Task { try await result.fetch() XCTAssertEqual(result.count, 2) XCTAssertFalse(result.hasNextPage()) - await fetchCompleted.fulfill() + fetchCompleted.fulfill() } - await waitForExpectations([fetchCompleted], timeout: 1.0) + await fulfillment(of: [fetchCompleted], timeout: 1.0) } func testDecodeToResponseTypeForCodable() throws { diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Extensions/SdkError+Analytics.swift b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Extensions/SdkError+Analytics.swift deleted file mode 100644 index 34725ba1c5..0000000000 --- a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Extensions/SdkError+Analytics.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import ClientRuntime -import Foundation -@_spi(InternalAWSPinpoint) import InternalAWSPinpoint - -extension SdkError { - var analyticsError: AnalyticsError { - return .unknown( - isConnectivityError ? AWSPinpointErrorConstants.deviceOffline.errorDescription : errorDescription, - rootError ?? self - ) - } -} diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AWSPinpoint+AnalyticsErrorConvertible.swift b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AWSPinpoint+AnalyticsErrorConvertible.swift new file mode 100644 index 0000000000..42c5464f9e --- /dev/null +++ b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AWSPinpoint+AnalyticsErrorConvertible.swift @@ -0,0 +1,53 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSPinpoint +import ClientRuntime + +extension AWSPinpoint.BadRequestException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.ForbiddenException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.InternalServerErrorException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.MethodNotAllowedException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.NotFoundException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.PayloadTooLargeException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} + +extension AWSPinpoint.TooManyRequestsException: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + .unknown(properties.message ?? "", self) + } +} diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorConvertible.swift b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorConvertible.swift new file mode 100644 index 0000000000..e21c889309 --- /dev/null +++ b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorConvertible.swift @@ -0,0 +1,19 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +protocol AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { get } +} + +extension AnalyticsError: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + self + } +} diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorHelper.swift b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorHelper.swift index 28af56447f..da6a74fe7b 100644 --- a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorHelper.swift +++ b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/AnalyticsErrorHelper.swift @@ -5,22 +5,20 @@ // SPDX-License-Identifier: Apache-2.0 // -import Amplify -import AWSPinpoint -import ClientRuntime import Foundation +import Amplify +import AwsCommonRuntimeKit -class AnalyticsErrorHelper { +enum AnalyticsErrorHelper { static func getDefaultError(_ error: Error) -> AnalyticsError { - if let sdkError = error as? SdkError{ - return sdkError.analyticsError + switch error { + case let error as AnalyticsErrorConvertible: + return error.analyticsError + case let error as AuthError: + return .configuration(error.errorDescription, error.recoverySuggestion, error) + default: + return getDefaultError(error as NSError) } - - if let analyticsError = error as? AnalyticsError { - return analyticsError - } - - return getDefaultError(error as NSError) } static func getDefaultError(_ error: NSError) -> AnalyticsError { diff --git a/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/CommonRunTimeError+AnalyticsErrorConvertible.swift b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/CommonRunTimeError+AnalyticsErrorConvertible.swift new file mode 100644 index 0000000000..b65918f472 --- /dev/null +++ b/AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin/Support/Utils/CommonRunTimeError+AnalyticsErrorConvertible.swift @@ -0,0 +1,23 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +@_spi(InternalAWSPinpoint) import InternalAWSPinpoint +import AwsCommonRuntimeKit + +extension CommonRunTimeError: AnalyticsErrorConvertible { + var analyticsError: AnalyticsError { + switch self { + case .crtError(let crtError): + let errorDescription = isConnectivityError + ? AWSPinpointErrorConstants.deviceOffline.errorDescription + : crtError.message + return .unknown(errorDescription, self) + } + } +} diff --git a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AWSPinpointAnalyticsPluginIntegrationTests/AWSPinpointAnalyticsPluginIntegrationTests.swift b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AWSPinpointAnalyticsPluginIntegrationTests/AWSPinpointAnalyticsPluginIntegrationTests.swift index b55e96cb47..81bf50c52e 100644 --- a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AWSPinpointAnalyticsPluginIntegrationTests/AWSPinpointAnalyticsPluginIntegrationTests.swift +++ b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AWSPinpointAnalyticsPluginIntegrationTests/AWSPinpointAnalyticsPluginIntegrationTests.swift @@ -42,7 +42,6 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let userId = "userId" let identifyUserEvent = expectation(description: "Identify User event was received on the hub plugin") _ = Amplify.Hub.listen(to: .analytics, isIncluded: nil) { payload in - print(payload) if payload.eventName == HubPayload.EventName.Analytics.identifyUser { guard let data = payload.data as? (String, AnalyticsUserProfile?) else { XCTFail("Missing data") @@ -72,7 +71,7 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { properties: properties) Amplify.Analytics.identifyUser(userId: userId, userProfile: userProfile) - await waitForExpectations(timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [identifyUserEvent], timeout: TestCommonConstants.networkTimeout) // Remove userId from the current endpoint let endpointClient = endpointClient() @@ -99,7 +98,7 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { /// Given: Analytics plugin /// When: An analytics event is recorded and flushed /// Then: Flush Hub event is received - func testRecordEventsAreFlushed() { + func testRecordEventsAreFlushed() async { let onlineExpectation = expectation(description: "Device is online") let networkMonitor = NWPathMonitor() networkMonitor.pathUpdateHandler = { newPath in @@ -134,17 +133,17 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let event = BasicAnalyticsEvent(name: "eventName", properties: properties) Amplify.Analytics.record(event: event) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) Amplify.Analytics.flushEvents() - wait(for: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: Analytics plugin /// When: An analytics event is recorded and flushed after the plugin is enabled /// Then: Flush Hub event is received - func testRecordsAreFlushedWhenPluginEnabled() { + func testRecordsAreFlushedWhenPluginEnabled() async { let onlineExpectation = expectation(description: "Device is online") let networkMonitor = NWPathMonitor() networkMonitor.pathUpdateHandler = { newPath in @@ -182,17 +181,17 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let event = BasicAnalyticsEvent(name: "eventName", properties: properties) Amplify.Analytics.record(event: event) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) Amplify.Analytics.flushEvents() - wait(for: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: Analytics plugin /// When: An analytics event is recorded and flushed after the plugin is disabled /// Then: Flush Hub event is not received - func testRecordsAreNotFlushedWhenPluginDisabled() { + func testRecordsAreNotFlushedWhenPluginDisabled() async { let onlineExpectation = expectation(description: "Device is online") let networkMonitor = NWPathMonitor() networkMonitor.pathUpdateHandler = { newPath in @@ -224,16 +223,17 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let event = BasicAnalyticsEvent(name: "eventName", properties: properties) Amplify.Analytics.record(event: event) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) Amplify.Analytics.flushEvents() - wait(for: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) } /// Given: Analytics plugin /// When: An analytics event is recorded and flushed with global properties registered /// Then: Flush Hub event is received with global properties - func testRegisterGlobalProperties() { + func testRegisterGlobalProperties() async throws { + throw XCTSkip("Race condition - registerGlobalProperties does async work in a Task") let onlineExpectation = expectation(description: "Device is online") let networkMonitor = NWPathMonitor() networkMonitor.pathUpdateHandler = { newPath in @@ -277,16 +277,17 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let event = BasicAnalyticsEvent(name: "eventName", properties: properties) Amplify.Analytics.record(event: event) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) Amplify.Analytics.flushEvents() - wait(for: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) } - + /// Given: Analytics plugin /// When: An analytics event is recorded and flushed with global properties registered and then unregistered /// Then: Flush Hub event is received without global properties - func testUnRegisterGlobalProperties() { + func testUnRegisterGlobalProperties() async throws { + throw XCTSkip("Race condition - unregisterGlobalProperties does async work in a Task") let onlineExpectation = expectation(description: "Device is online") let networkMonitor = NWPathMonitor() networkMonitor.pathUpdateHandler = { newPath in @@ -331,10 +332,10 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { let event = BasicAnalyticsEvent(name: "eventName", properties: properties) Amplify.Analytics.record(event: event) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) Amplify.Analytics.flushEvents() - wait(for: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [flushEventsInvoked], timeout: TestCommonConstants.networkTimeout) } func testGetEscapeHatch() throws { @@ -346,9 +347,7 @@ class AWSPinpointAnalyticsPluginIntergrationTests: XCTestCase { } let awsPinpoint = pinpointAnalyticsPlugin.getEscapeHatch() XCTAssertNotNil(awsPinpoint) - } - - + } private func plugin() -> AWSPinpointAnalyticsPlugin { guard let plugin = try? Amplify.Analytics.getPlugin(for: "awsPinpointAnalyticsPlugin"), diff --git a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2ef4d7901c..6203f7a6b6 100644 --- a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift", "state" : { - "revision" : "6feec6c3787877807aa9a00fad09591b96752376", - "version" : "0.6.1" + "revision" : "997904873945e074aaf5c51ea968d9a84684525a", + "version" : "0.13.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift.git", "state" : { - "revision" : "24bae88a2391fe75da8a940a544d1ef6441f5321", - "version" : "0.13.0" + "revision" : "ace826dbfe96e7e3103fe7f45f815b8a590bcf21", + "version" : "0.26.0" } }, { @@ -57,10 +57,10 @@ { "identity" : "smithy-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/smithy-swift", + "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "7b28da158d92cd06a3549140d43b8fbcf64a94a6", - "version" : "0.15.0" + "revision" : "eed3f3d8e5aa704fcd60bb227b0fc89bf3328c42", + "version" : "0.30.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MaxDesiatov/XMLCoder.git", "state" : { - "revision" : "b1e944cbd0ef33787b13f639a5418d55b3bed501", - "version" : "0.17.1" + "revision" : "80b4a1646399b8e4e0ce80711653476a85bd5e37", + "version" : "0.17.0" } } ], diff --git a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsStressTests/AnalyticsStressTests.swift b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsStressTests/AnalyticsStressTests.swift index b594aa73e1..308dcfd1fb 100644 --- a/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsStressTests/AnalyticsStressTests.swift +++ b/AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsStressTests/AnalyticsStressTests.swift @@ -49,24 +49,22 @@ final class AnalyticsStressTests: XCTestCase { } networkMonitor.start(queue: DispatchQueue(label: "AWSPinpointAnalyticsPluginIntergrationTests.NetworkMonitor")) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) - let recordExpectation = asyncExpectation(description: "Records are successfully recorded", - expectedFulfillmentCount: concurrencyLimit) + let recordExpectation = expectation(description: "Records are successfully recorded") + recordExpectation.expectedFulfillmentCount = concurrencyLimit for eventNumber in 0...concurrencyLimit { - Task { - let properties = ["eventPropertyStringKey1": "eventProperyStringValue1", - "eventPropertyStringKey2": "eventProperyStringValue2", - "eventPropertyStringKey3": "eventProperyStringValue3", - "eventPropertyStringKey4": "eventProperyStringValue4", - "eventPropertyStringKey5": "eventProperyStringValue5"] as [String: AnalyticsPropertyValue] - let event = BasicAnalyticsEvent(name: "eventName" + String(eventNumber), properties: properties) - Amplify.Analytics.record(event: event) - await recordExpectation.fulfill() - } + let properties = ["eventPropertyStringKey1": "eventProperyStringValue1", + "eventPropertyStringKey2": "eventProperyStringValue2", + "eventPropertyStringKey3": "eventProperyStringValue3", + "eventPropertyStringKey4": "eventProperyStringValue4", + "eventPropertyStringKey5": "eventProperyStringValue5"] as [String: AnalyticsPropertyValue] + let event = BasicAnalyticsEvent(name: "eventName" + String(eventNumber), properties: properties) + Amplify.Analytics.record(event: event) + recordExpectation.fulfill() } - await waitForExpectations([recordExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [recordExpectation], timeout: TestCommonConstants.networkTimeout) } /// - Given: Analytics plugin configured with valid configuration @@ -82,10 +80,11 @@ final class AnalyticsStressTests: XCTestCase { } networkMonitor.start(queue: DispatchQueue(label: "AWSPinpointAnalyticsPluginIntergrationTests.NetworkMonitor")) - wait(for: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [onlineExpectation], timeout: TestCommonConstants.networkTimeout) - let recordExpectation = asyncExpectation(description: "Records are successfully recorded", - expectedFulfillmentCount: concurrencyLimit) + let recordExpectation = expectation(description: "Records are successfully recorded") + recordExpectation.expectedFulfillmentCount = concurrencyLimit + for eventNumber in 0...concurrencyLimit { Task { let properties = ["eventPropertyStringKey1": "eventProperyStringValue1", @@ -110,11 +109,11 @@ final class AnalyticsStressTests: XCTestCase { "eventPropertyBoolKey5": true] as [String: AnalyticsPropertyValue] let event = BasicAnalyticsEvent(name: "eventName" + String(eventNumber), properties: properties) Amplify.Analytics.record(event: event) - await recordExpectation.fulfill() + recordExpectation.fulfill() } } - await waitForExpectations([recordExpectation], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [recordExpectation], timeout: TestCommonConstants.networkTimeout) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift index ae80ff67f2..2fb118bdfa 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift @@ -12,7 +12,9 @@ import AWSCognitoIdentity import AWSCognitoIdentityProvider import AWSPluginsCore import ClientRuntime -@_spi(FoundationClientEngine) import AWSPluginsCore +import AWSClientRuntime +@_spi(PluginHTTPClientEngine) import AWSPluginsCore +@_spi(InternalHttpEngineProxy) import AWSPluginsCore extension AWSCognitoAuthPlugin { @@ -87,29 +89,17 @@ extension AWSCognitoAuthPlugin { switch authConfiguration { case .userPools(let userPoolConfig), .userPoolsAndIdentityPools(let userPoolConfig, _): let configuration = try CognitoIdentityProviderClient.CognitoIdentityProviderClientConfiguration( - endpointResolver: userPoolConfig.endpoint?.resolver, - frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(), - region: userPoolConfig.region + region: userPoolConfig.region, + serviceSpecific: .init(endpointResolver: userPoolConfig.endpoint?.resolver) ) if var httpClientEngineProxy = httpClientEngineProxy { - let httpClientEngine: HttpClientEngine - #if os(iOS) || os(macOS) - // networking goes through CRT - httpClientEngine = configuration.httpClientEngine - #else - // networking goes through Foundation - httpClientEngine = FoundationClientEngine() - #endif - httpClientEngineProxy.target = httpClientEngine - configuration.httpClientEngine = httpClientEngineProxy + httpClientEngineProxy.target = baseClientEngine(for: configuration) + configuration.httpClientEngine = UserAgentSettingClientEngine( + target: httpClientEngineProxy + ) } else { - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - configuration.httpClientEngine = FoundationClientEngine() - #endif + configuration.httpClientEngine = .userAgentEngine(for: configuration) } return CognitoIdentityProviderClient(config: configuration) @@ -122,16 +112,9 @@ extension AWSCognitoAuthPlugin { switch authConfiguration { case .identityPools(let identityPoolConfig), .userPoolsAndIdentityPools(_, let identityPoolConfig): let configuration = try CognitoIdentityClient.CognitoIdentityClientConfiguration( - frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(), region: identityPoolConfig.region ) - - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - configuration.httpClientEngine = FoundationClientEngine() - #endif + configuration.httpClientEngine = .userAgentEngine(for: configuration) return CognitoIdentityClient(config: configuration) default: diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/FetchIdentity/FetchAuthIdentityId.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/FetchIdentity/FetchAuthIdentityId.swift index da2a09c174..97ffabda08 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/FetchIdentity/FetchAuthIdentityId.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/FetchIdentity/FetchAuthIdentityId.swift @@ -65,12 +65,7 @@ struct FetchAuthIdentityId: Action { } func isNotAuthorizedError(_ error: Error) -> Bool { - - if let getIdError: GetIdOutputError = error.internalAWSServiceError(), - case .notAuthorizedException = getIdError { - return true - } - return false + error is AWSCognitoIdentity.NotAuthorizedException } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift index 6c6107578f..c556bf89d4 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift @@ -37,17 +37,8 @@ struct InformSessionError: Action { } func isNotAuthorizedError(_ error: Error) -> Bool { - - - if let serviceError: GetCredentialsForIdentityOutputError = error.internalAWSServiceError(), - case .notAuthorizedException = serviceError { - return true - } - if let serviceError: InitiateAuthOutputError = error.internalAWSServiceError(), - case .notAuthorizedException = serviceError { - return true - } - return false + error is AWSCognitoIdentity.NotAuthorizedException + || error is AWSCognitoIdentityProvider.NotAuthorizedException } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SRPAuth/VerifyPasswordSRP.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SRPAuth/VerifyPasswordSRP.swift index ec5ccf0ade..969b2b1c93 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SRPAuth/VerifyPasswordSRP.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SRPAuth/VerifyPasswordSRP.swift @@ -110,11 +110,7 @@ struct VerifyPasswordSRP: Action { return false } - if let serviceError: RespondToAuthChallengeOutputError = error.internalAWSServiceError(), - case .resourceNotFoundException = serviceError { - return true - } - return false + return error is AWSCognitoIdentityProvider.ResourceNotFoundException } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/VerifySignInChallenge.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/VerifySignInChallenge.swift index ac19a29d86..5761ac2d06 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/VerifySignInChallenge.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/VerifySignInChallenge.swift @@ -89,11 +89,7 @@ struct VerifySignInChallenge: Action { return false } - if let serviceError: RespondToAuthChallengeOutputError = error.internalAWSServiceError(), - case .resourceNotFoundException = serviceError { - return true - } - return false + return error is AWSCognitoIdentityProvider.ResourceNotFoundException } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift index 6c46eceb40..59d799e963 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift @@ -76,24 +76,6 @@ public struct AWSAuthCognitoSession: AuthSession, } -/// Internal Helpers for managing session tokens -internal extension AWSAuthCognitoSession { - func areTokensExpiring(in seconds: TimeInterval? = nil) -> Bool { - - guard let tokens = try? userPoolTokensResult.get(), - let idTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(), - let accessTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(), - let idTokenExpiration = idTokenClaims["exp"]?.doubleValue, - let accessTokenExpiration = accessTokenClaims["exp"]?.doubleValue else { - return true - } - - // If the session expires < X minutes return it - return (Date(timeIntervalSince1970: idTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending && - Date(timeIntervalSince1970: accessTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending) - } -} - extension AWSAuthCognitoSession: Equatable { public static func == (lhs: AWSAuthCognitoSession, rhs: AWSAuthCognitoSession) -> Bool { switch (lhs.getCognitoTokens(), rhs.getCognitoTokens()) { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift index af7d80f96a..c5f4daed06 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift @@ -65,10 +65,10 @@ public struct AWSCognitoUserPoolTokens: AuthCognitoTokens { case (.some(let idTokenValue), .none): expirationDoubleValue = idTokenValue case (.none, .none): - expirationDoubleValue = 0 + expirationDoubleValue = Date().timeIntervalSince1970 } - self.expiration = Date().addingTimeInterval(TimeInterval((expirationDoubleValue ?? 0))) + self.expiration = Date(timeIntervalSince1970: TimeInterval(expirationDoubleValue)) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c69ba3b3a --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCognitoIdentity+AuthErrorConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCognitoIdentity+AuthErrorConvertible.swift new file mode 100644 index 0000000000..dd16b203a0 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCognitoIdentity+AuthErrorConvertible.swift @@ -0,0 +1,116 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSCognitoIdentity +import AWSClientRuntime + +// AWSCognitoIdentity +extension AWSCognitoIdentity.ExternalServiceException: AuthErrorConvertible { + var fallbackDescription: String { "External service threw error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.externalServiceException, + AWSCognitoAuthError.externalServiceException + ) + } +} + +extension AWSCognitoIdentity.InternalErrorException: AuthErrorConvertible { + var fallbackDescription: String { "Internal exception occurred" } + + var authError: AuthError { + .unknown(properties.message ?? fallbackDescription) + } +} + +// AWSCognitoIdentity +extension AWSCognitoIdentity.InvalidIdentityPoolConfigurationException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid IdentityPool Configuration error." } + + var authError: AuthError { + .configuration( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.configurationError + ) + } +} + +extension AWSCognitoIdentity.InvalidParameterException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid parameter error" } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidParameterError, + AWSCognitoAuthError.invalidParameter + ) + } +} + +extension AWSCognitoIdentity.NotAuthorizedException: AuthErrorConvertible { + var fallbackDescription: String { "Not authorized error." } + + var authError: AuthError { + .notAuthorized( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.notAuthorizedError + ) + } +} + +extension AWSCognitoIdentity.ResourceConflictException: AuthErrorConvertible { + var fallbackDescription: String { "Resource conflict error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.resourceConflictException, + AWSCognitoAuthError.resourceConflictException + ) + } +} + +extension AWSCognitoIdentity.ResourceNotFoundException: AuthErrorConvertible { + var fallbackDescription: String { "Resource not found error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.resourceNotFoundError, + AWSCognitoAuthError.resourceNotFound + ) + } +} + +extension AWSCognitoIdentity.TooManyRequestsException: AuthErrorConvertible { + var fallbackDescription: String { "Too many requests error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.tooManyRequestError, + AWSCognitoAuthError.requestLimitExceeded + ) + } +} + + +extension AWSCognitoIdentity.LimitExceededException: AuthErrorConvertible { + var fallbackDescription: String { "Too many requests error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.limitExceededException, + AWSCognitoAuthError.limitExceededException + ) + } +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCongnitoIdentityProvider+AuthErrorConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCongnitoIdentityProvider+AuthErrorConvertible.swift new file mode 100644 index 0000000000..6179940724 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCongnitoIdentityProvider+AuthErrorConvertible.swift @@ -0,0 +1,361 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSCognitoIdentityProvider +import AWSClientRuntime + +extension ForbiddenException: AuthErrorConvertible { + var fallbackDescription: String { "Access to the requested resource is forbidden" } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.forbiddenError + ) + } +} + +extension InternalErrorException: AuthErrorConvertible { + var fallbackDescription: String { "Internal exception occurred" } + + var authError: AuthError { + .unknown(properties.message ?? fallbackDescription) + } +} + +extension InvalidParameterException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid parameter error" } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidParameterError, + AWSCognitoAuthError.invalidParameter + ) + } +} + +extension InvalidPasswordException: AuthErrorConvertible { + var fallbackDescription: String { "Encountered invalid password." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidPasswordError, + AWSCognitoAuthError.invalidPassword + ) + } +} + +extension LimitExceededException: AuthErrorConvertible { + var fallbackDescription: String { "Limit exceeded error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.limitExceededError, + AWSCognitoAuthError.limitExceeded + ) + } +} + +extension NotAuthorizedException: AuthErrorConvertible { + var fallbackDescription: String { "Not authorized error." } + + var authError: AuthError { + .notAuthorized( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.notAuthorizedError + ) + } +} + +extension PasswordResetRequiredException: AuthErrorConvertible { + var fallbackDescription: String { "Password reset required error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.passwordResetRequired, + AWSCognitoAuthError.passwordResetRequired + ) + } +} + +extension ResourceNotFoundException: AuthErrorConvertible { + var fallbackDescription: String { "Resource not found error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.resourceNotFoundError, + AWSCognitoAuthError.resourceNotFound + ) + } +} + +extension TooManyRequestsException: AuthErrorConvertible { + var fallbackDescription: String { "Too many requests error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.tooManyRequestError, + AWSCognitoAuthError.requestLimitExceeded + ) + } +} + +extension UserNotConfirmedException: AuthErrorConvertible { + var fallbackDescription: String { "User not confirmed error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.userNotConfirmedError, + AWSCognitoAuthError.userNotConfirmed + ) + } +} + +extension UserNotFoundException: AuthErrorConvertible { + var fallbackDescription: String { "User not found error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.userNotFoundError, + AWSCognitoAuthError.userNotFound + ) + } +} + +extension CodeMismatchException: AuthErrorConvertible { + var fallbackDescription: String { "Provided code does not match what the server was expecting." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.codeMismatchError, + AWSCognitoAuthError.codeMismatch + ) + } +} + +extension InvalidLambdaResponseException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid lambda response error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.lambdaError, + AWSCognitoAuthError.lambda + ) + } +} + + +extension ExpiredCodeException: AuthErrorConvertible { + var fallbackDescription: String { "Provided code has expired." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.codeExpiredError, + AWSCognitoAuthError.codeExpired + ) + } +} + +extension TooManyFailedAttemptsException: AuthErrorConvertible { + var fallbackDescription: String { "Too many failed attempts error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.tooManyFailedError, + AWSCognitoAuthError.failedAttemptsLimitExceeded + ) + } +} + +extension UnexpectedLambdaException: AuthErrorConvertible { + var fallbackDescription: String { "Unexpected lambda error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.lambdaError, + AWSCognitoAuthError.lambda + ) + } +} + +extension UserLambdaValidationException: AuthErrorConvertible { + var fallbackDescription: String { "User lambda validation error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.lambdaError, + AWSCognitoAuthError.lambda + ) + } +} + +extension AliasExistsException: AuthErrorConvertible { + var fallbackDescription: String { "Alias exists error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.aliasExistsError, + AWSCognitoAuthError.aliasExists + ) + } +} + +extension InvalidUserPoolConfigurationException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid UserPool Configuration error." } + + var authError: AuthError { + .configuration( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.configurationError + ) + } +} + +extension CodeDeliveryFailureException: AuthErrorConvertible { + var fallbackDescription: String { "Code Delivery Failure error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.codeDeliveryError, + AWSCognitoAuthError.codeDelivery + ) + } +} + +extension InvalidEmailRoleAccessPolicyException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid email role access policy error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidEmailRoleError, + AWSCognitoAuthError.emailRole + ) + } +} + + +extension InvalidSmsRoleAccessPolicyException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid SMS Role Access Policy error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidSMSRoleError, + AWSCognitoAuthError.smsRole + ) + } +} + +extension InvalidSmsRoleTrustRelationshipException: AuthErrorConvertible { + var fallbackDescription: String { "Invalid SMS Role Trust Relationship error." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.invalidSMSRoleError, + AWSCognitoAuthError.smsRole + ) + } +} + +extension MFAMethodNotFoundException: AuthErrorConvertible { + var fallbackDescription: String { "Amazon Cognito cannot find a multi-factor authentication (MFA) method." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.mfaMethodNotFoundError, + AWSCognitoAuthError.mfaMethodNotFound + ) + } +} + +extension SoftwareTokenMFANotFoundException: AuthErrorConvertible { + var fallbackDescription: String { "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool." } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.softwareTokenNotFoundError, + AWSCognitoAuthError.softwareTokenMFANotEnabled + ) + } +} + +extension UsernameExistsException: AuthErrorConvertible { + var fallbackDescription: String { "Username exists error" } + + var authError: AuthError { + .service( + properties.message ?? fallbackDescription, + AuthPluginErrorConstants.userNameExistsError, + AWSCognitoAuthError.usernameExists + ) + } +} + +extension AWSCognitoIdentityProvider.ConcurrentModificationException: AuthErrorConvertible { + var fallbackDescription: String { "Concurrent modification error" } + + var authError: AuthError { + .service( + message ?? fallbackDescription, + AuthPluginErrorConstants.concurrentModificationException + ) + } +} + + +extension AWSCognitoIdentityProvider.EnableSoftwareTokenMFAException: AuthErrorConvertible { + var fallbackDescription: String { "Unable to enable software token MFA" } + + var authError: AuthError { + .service( + message ?? fallbackDescription, + AuthPluginErrorConstants.softwareTokenNotFoundError, + AWSCognitoAuthError.softwareTokenMFANotEnabled + ) + } +} + +extension AWSClientRuntime.UnknownAWSHTTPServiceError: AuthErrorConvertible { + var fallbackDescription: String { "" } + + var authError: AuthError { + .unknown( + """ + Unknown service error occured with: + - status: \(httpResponse.statusCode) + - message: \(message ?? fallbackDescription) + """, + self + ) + } +} + + + + diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AssociateSoftwareTokenOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AssociateSoftwareTokenOutputError+AuthError.swift deleted file mode 100644 index 4adf7e6a0c..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AssociateSoftwareTokenOutputError+AuthError.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import AWSCognitoIdentityProvider - -extension AssociateSoftwareTokenOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .concurrentModificationException(let concurrentModificationException): - return .service( - concurrentModificationException.message ?? "Concurrent modification error", - AuthPluginErrorConstants.concurrentModificationException) - case .forbiddenException(let forbiddenException): - return .service( - forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - case .internalErrorException(let internalErrorException): - return .unknown( - internalErrorException.message ?? "Internal exception occurred") - case .invalidParameterException(let invalidParameterException): - return .service( - invalidParameterException.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let notAuthorizedException): - return .notAuthorized( - notAuthorizedException.message ?? "Not authorized Error", - AuthPluginErrorConstants.notAuthorizedError, - nil) - case .resourceNotFoundException(let resourceNotFoundException): - return AuthError.service( - resourceNotFoundException.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .softwareTokenMFANotFoundException(let exception): - return AuthError.service( - exception.message ?? "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool.", - AuthPluginErrorConstants.softwareTokenNotFoundError, - AWSCognitoAuthError.mfaMethodNotFound) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AuthErrorConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AuthErrorConvertible.swift index 34b5a80ba1..e811b761d9 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AuthErrorConvertible.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AuthErrorConvertible.swift @@ -11,7 +11,6 @@ import Amplify /// A type that can be represented as an AuthError /// protocol AuthErrorConvertible { - var authError: AuthError { get } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ChangePasswordOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ChangePasswordOutputError+AuthError.swift deleted file mode 100644 index 0aec3eeb86..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ChangePasswordOutputError+AuthError.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ChangePasswordOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return AuthError.service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let exception): - return AuthError.service(exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - case .invalidPasswordException(let exception): - return AuthError.service(exception.message ?? "Encountered invalid password.", - AuthPluginErrorConstants.invalidPasswordError, - AWSCognitoAuthError.invalidPassword) - case .limitExceededException(let limitExceededException): - return .service(limitExceededException.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded) - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ClientError+AuthErrorConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ClientError+AuthErrorConvertible.swift new file mode 100644 index 0000000000..942c410f70 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ClientError+AuthErrorConvertible.swift @@ -0,0 +1,33 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import ClientRuntime + +extension ClientError: AuthErrorConvertible { + var fallbackDescription: String { "Client Error" } + + var authError: AuthError { + switch self { + case .pathCreationFailed(let message), + .queryItemCreationFailed(let message), + .serializationFailed(let message), + .dataNotFound(let message): + return .service(message, "", self) + + case .authError(let message): + return .notAuthorized( + message, + "Check if you are authorized to perform the request" + ) + + case .unknownError(let message): + return AuthError.unknown(message, self) + } + } +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/CommonRunTimeError+AuthErrorConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/CommonRunTimeError+AuthErrorConvertible.swift new file mode 100644 index 0000000000..8244b277a5 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/CommonRunTimeError+AuthErrorConvertible.swift @@ -0,0 +1,42 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AwsCommonRuntimeKit +import AwsCIo +import AwsCHttp + +private let connectivityErrorCodes: Set = [ + AWS_ERROR_HTTP_CONNECTION_CLOSED.rawValue, + AWS_ERROR_HTTP_SERVER_CLOSED.rawValue, + AWS_IO_DNS_INVALID_NAME.rawValue, + AWS_IO_DNS_NO_ADDRESS_FOR_HOST.rawValue, + AWS_IO_DNS_QUERY_FAILED.rawValue, + AWS_IO_SOCKET_CONNECT_ABORTED.rawValue, + AWS_IO_SOCKET_CONNECTION_REFUSED.rawValue, + AWS_IO_SOCKET_CLOSED.rawValue, + AWS_IO_SOCKET_NETWORK_DOWN.rawValue, + AWS_IO_SOCKET_NO_ROUTE_TO_HOST.rawValue, + AWS_IO_SOCKET_NOT_CONNECTED.rawValue, + AWS_IO_SOCKET_TIMEOUT.rawValue, + AWS_IO_TLS_NEGOTIATION_TIMEOUT.rawValue, + UInt32(AWS_HTTP_STATUS_CODE_408_REQUEST_TIMEOUT.rawValue) +] + +extension CommonRunTimeError: AuthErrorConvertible { + var authError: AuthError { + let error: CRTError + switch self { case .crtError(let crtError): error = crtError } + + if connectivityErrorCodes.contains(UInt32(error.code)) { + return .service(error.name, error.message, AWSCognitoAuthError.network) + } else { + return .unknown("\(error.name) - \(error.message)", self) + } + } +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmForgotPasswordOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmForgotPasswordOutputError+AuthError.swift deleted file mode 100644 index 9f004cb7a7..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmForgotPasswordOutputError+AuthError.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ConfirmForgotPasswordOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeMismatchException(let exception): - return AuthError.service(exception.message ?? "Provided code does not match what the server was expecting.", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch) - case .expiredCodeException(let exception): - return AuthError.service(exception.message ?? "Provided code has expired.", - AuthPluginErrorConstants.codeExpiredError, - AWSCognitoAuthError.codeExpired) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidLambdaResponseException(let exception): - return .service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidPasswordException(let exception): - return AuthError.service(exception.message ?? "Invalid password error", - AuthPluginErrorConstants.invalidPasswordError, - AWSCognitoAuthError.invalidPassword) - case .limitExceededException(let exception): - return .service(exception.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyFailedAttemptsException(let exception): - return AuthError.service(exception.message ?? "Too many failed attempts error", - AuthPluginErrorConstants.tooManyFailedError, - AWSCognitoAuthError.failedAttemptsLimitExceeded) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return .service(exception.message ?? "Unexpected lambda error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userLambdaValidationException(let exception): - return .service(exception.message ?? "User lambda validation error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmSignUpOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmSignUpOutputError+AuthError.swift deleted file mode 100644 index 1a29be5c77..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ConfirmSignUpOutputError+AuthError.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation -import AWSCognitoIdentityProvider - -extension ConfirmSignUpOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .aliasExistsException(let exception): - - return .service( - exception.message ?? "Alias exists error", - AuthPluginErrorConstants.aliasExistsError, - AWSCognitoAuthError.aliasExists - ) - case .codeMismatchException(let exception): - - return .service( - exception.message ?? "Code mismatch error", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch - ) - case .expiredCodeException(let exception): - - return .service( - exception.message ?? "Expired code error", - AuthPluginErrorConstants.codeExpiredError, - AWSCognitoAuthError.codeExpired - ) - case .limitExceededException(let exception): - - return .service( - exception.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded - ) - case .tooManyFailedAttemptsException(let exception): - - return .service( - exception.message ?? "Too many failed attempts error", - AuthPluginErrorConstants.tooManyFailedError, - AWSCognitoAuthError.requestLimitExceeded - ) - case .userNotFoundException(let exception): - - return .service( - exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound - ) - case .internalErrorException(let exception): - - return .unknown( - exception.message ?? "Internal exception occurred" - ) - case .invalidLambdaResponseException(let exception): - - return .service( - exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .invalidParameterException(let exception): - - return .service( - exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter - ) - case .notAuthorizedException(let exception): - - return .notAuthorized( - exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .resourceNotFoundException(let exception): - - return .service( - exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound - ) - case .tooManyRequestsException(let exception): - - return .service( - exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded - ) - case .unexpectedLambdaException(let exception): - - return .service( - exception.message ?? "Unexpected lambda error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userLambdaValidationException(let exception): - - return .service( - exception.message ?? "User lambda validation error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/DeleteUserOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/DeleteUserOutputError+AuthError.swift deleted file mode 100644 index a858393471..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/DeleteUserOutputError+AuthError.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension DeleteUserOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authrozied error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return AuthError.service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let exception): - return AuthError.service(exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgetDeviceOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgetDeviceOutputError+AuthError.swift deleted file mode 100644 index 8208febe99..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgetDeviceOutputError+AuthError.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ForgetDeviceOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidUserPoolConfigurationException(let exception): - return .configuration(exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let exception): - return .notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return .service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return .service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgotPasswordOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgotPasswordOutputError+AuthError.swift deleted file mode 100644 index 7e844880d4..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ForgotPasswordOutputError+AuthError.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ForgotPasswordOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeDeliveryFailureException(let exception): - return .service( - exception.message ?? "Code Delivery Failure error", - AuthPluginErrorConstants.codeDeliveryError, - AWSCognitoAuthError.codeDelivery - ) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidEmailRoleAccessPolicyException(let exception): - return .service( - exception.message ?? "Invalid email role access policy error", - AuthPluginErrorConstants.invalidEmailRoleError, - AWSCognitoAuthError.emailRole - ) - case .invalidLambdaResponseException(let exception): - return .service( - exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .limitExceededException(let exception): - return .service( - exception.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded - ) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return .service( - exception.message ?? "Unexpected lambda error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userLambdaValidationException(let exception): - return .service( - exception.message ?? "User lambda validation error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetCredentialsForIdentityOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetCredentialsForIdentityOutputError+AuthError.swift deleted file mode 100644 index 1a26155124..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetCredentialsForIdentityOutputError+AuthError.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentity -import Amplify - -extension GetCredentialsForIdentityOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .externalServiceException(let externalServiceException): - return .service(externalServiceException.message ?? "External service threw error", - AuthPluginErrorConstants.externalServiceException, - AWSCognitoAuthError.externalServiceException) - case .internalErrorException(let internalErrorException): - return .unknown(internalErrorException.message ?? "Internal exception occurred") - case .invalidIdentityPoolConfigurationException(let invalidIdentityPoolConfigurationException): - return AuthError.configuration(invalidIdentityPoolConfigurationException.message ?? "Invalid IdentityPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .invalidParameterException(let invalidParameterException): - return AuthError.service(invalidParameterException.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let notAuthorizedException): - return .notAuthorized(notAuthorizedException.message ?? "Not authorized Error", - AuthPluginErrorConstants.notAuthorizedError, - nil) - case .resourceConflictException(let resourceConflictException): - return .service(resourceConflictException.message ?? "Resource conflict error", - AuthPluginErrorConstants.resourceConflictException, - AWSCognitoAuthError.resourceConflictException) - case .resourceNotFoundException(let resourceNotFoundException): - return AuthError.service(resourceNotFoundException.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let tooManyRequestsException): - return .service(tooManyRequestsException.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetIdOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetIdOutputError+AuthError.swift deleted file mode 100644 index 37be0a6d7f..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetIdOutputError+AuthError.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentity -import Amplify - -extension GetIdOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .externalServiceException(let externalServiceException): - return .service(externalServiceException.message ?? "External service threw error", - AuthPluginErrorConstants.externalServiceException, - AWSCognitoAuthError.externalServiceException) - case .internalErrorException(let internalErrorException): - return .unknown(internalErrorException.message ?? "Internal exception occurred") - case .invalidParameterException(let invalidParameterException): - return .service(invalidParameterException.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .limitExceededException(let limitExceededException): - return .service(limitExceededException.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededException, - AWSCognitoAuthError.limitExceededException) - case .notAuthorizedException(let notAuthorizedException): - return .notAuthorized(notAuthorizedException.message ?? "Not authorized Error", - AuthPluginErrorConstants.notAuthorizedError, - nil) - case .resourceConflictException(let resourceConflictException): - return .service(resourceConflictException.message ?? "Resource conflict error", - AuthPluginErrorConstants.resourceConflictException, - AWSCognitoAuthError.resourceConflictException) - case .resourceNotFoundException(let resourceNotFoundException): - return AuthError.service(resourceNotFoundException.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let tooManyRequestsException): - return .service(tooManyRequestsException.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserAttributeVerificationCodeOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserAttributeVerificationCodeOutputError+AuthError.swift deleted file mode 100644 index 073837cb7d..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserAttributeVerificationCodeOutputError+AuthError.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension GetUserAttributeVerificationCodeOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .codeDeliveryFailureException(let exception): - return .service(exception.message ?? "Code Delivery Failure error", - AuthPluginErrorConstants.codeDeliveryError, - AWSCognitoAuthError.codeDelivery) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidEmailRoleAccessPolicyException(let exception): - return .service(exception.message ?? "Invalid email role access policy error", - AuthPluginErrorConstants.invalidEmailRoleError, - AWSCognitoAuthError.emailRole) - case .invalidLambdaResponseException(let exception): - return .service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .invalidParameterException(let exception): - return .service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return .service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return .service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .limitExceededException(let limitExceededException): - return .service(limitExceededException.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded) - case .notAuthorizedException(let exception): - return .notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return .service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return .service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userLambdaValidationException(let exception): - return .service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let userNotFoundException): - return .service(userNotFoundException.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserOutputError+AuthError.swift deleted file mode 100644 index bc364ab427..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/GetUserOutputError+AuthError.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension GetUserOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return .service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let exception): - return .notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return .service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let userNotFoundException): - return .service(userNotFoundException.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/InitiateAuthOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/InitiateAuthOutputError+AuthError.swift deleted file mode 100644 index e4e648af2a..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/InitiateAuthOutputError+AuthError.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension InitiateAuthOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidLambdaResponseException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .invalidUserPoolConfigurationException(let exception): - return AuthError.configuration(exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return AuthError.service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userLambdaValidationException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userNotConfirmedException(let exception): - return AuthError.service(exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ListDevicesOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ListDevicesOutputError+AuthError.swift deleted file mode 100644 index 42af26acf9..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ListDevicesOutputError+AuthError.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ListDevicesOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidUserPoolConfigurationException(let exception): - return .configuration(exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let exception): - return .notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return .service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return .service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ResendConfirmationCodeOutputError+Error.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ResendConfirmationCodeOutputError+Error.swift deleted file mode 100644 index 2abc4185bb..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/ResendConfirmationCodeOutputError+Error.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension ResendConfirmationCodeOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeDeliveryFailureException(let exception): - return .service( - exception.message ?? "Code Delivery Failure error", - AuthPluginErrorConstants.codeDeliveryError, - AWSCognitoAuthError.codeDelivery - ) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidEmailRoleAccessPolicyException(let exception): - return .service( - exception.message ?? "Invalid email role access policy error", - AuthPluginErrorConstants.invalidEmailRoleError, - AWSCognitoAuthError.emailRole - ) - case .invalidLambdaResponseException(let exception): - return .service( - exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .limitExceededException(let exception): - return .service( - exception.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded - ) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return .service( - exception.message ?? "Unexpected lambda error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userLambdaValidationException(let exception): - return .service( - exception.message ?? "User lambda validation error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/RespondToAuthChallengeOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/RespondToAuthChallengeOutputError+AuthError.swift deleted file mode 100644 index eae169e4e6..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/RespondToAuthChallengeOutputError+AuthError.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension RespondToAuthChallengeOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .aliasExistsException(let exception): - return AuthError.service(exception.message ?? "An account with this email or phone already exists.", - AuthPluginErrorConstants.aliasExistsError, - AWSCognitoAuthError.aliasExists) - case .codeMismatchException(let exception): - return AuthError.service(exception.message ?? "Provided code does not match what the server was expecting.", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch) - case .expiredCodeException(let exception): - return AuthError.service(exception.message ?? "Provided code has expired.", - AuthPluginErrorConstants.codeExpiredError, - AWSCognitoAuthError.codeExpired) - case .invalidPasswordException(let exception): - return AuthError.service(exception.message ?? "Encountered invalid password.", - AuthPluginErrorConstants.invalidPasswordError, - AWSCognitoAuthError.invalidPassword) - case .mFAMethodNotFoundException(let exception): - return AuthError.service(exception.message ?? "Amazon Cognito cannot find a multi-factor authentication (MFA) method.", - AuthPluginErrorConstants.mfaMethodNotFoundError, - AWSCognitoAuthError.mfaMethodNotFound) - case .softwareTokenMFANotFoundException(let exception): - return AuthError.service(exception.message ?? "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool.", - AuthPluginErrorConstants.softwareTokenNotFoundError, - AWSCognitoAuthError.softwareTokenMFANotEnabled) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidLambdaResponseException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole) - case .invalidUserPoolConfigurationException(let exception): - return AuthError.configuration(exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authrozied error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return AuthError.service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userLambdaValidationException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userNotConfirmedException(let exception): - return AuthError.service(exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SdkError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SdkError+AuthError.swift deleted file mode 100644 index 5cea0f5d2e..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SdkError+AuthError.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import Amplify -import ClientRuntime - -extension SdkError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - - case .service(let serviceError, _): - if let authErrorMappable = serviceError as? AuthErrorConvertible { - return authErrorMappable.authError - } else if let otherError = serviceError as? Error { - return AuthError.service(otherError.localizedDescription, "", otherError) - } else { - return AuthError.unknown(String(describing: serviceError)) - } - case .client(let clientError, let httpResponse): - return convertToAuthError(clientError: clientError, httpResponse: httpResponse) - case .unknown(let unknownError): - return AuthError.unknown("An unknown error occured, check the underlying error for more details", - unknownError) - } - } - - func convertToAuthError(clientError: ClientError, - httpResponse: HttpResponse? = nil) -> AuthError { - switch clientError { - case .networkError(let error): - return AuthError.service(error.localizedDescription, - """ - Check your network connection, retry when the network is available. - HTTP Response stauts code: \(String(describing: httpResponse?.statusCode)) - """, - AWSCognitoAuthError.network) - - case .crtError(let cRTError): - return AuthError.service(cRTError.localizedDescription, - "Check the underlying error for more details", - cRTError) - - case .pathCreationFailed(let message): - return AuthError.service(message, "", clientError) - - case .queryItemCreationFailed(let message): - return AuthError.service(message, "", clientError) - - case .serializationFailed(let message): - return AuthError.service(message, "", clientError) - - case .deserializationFailed(let error): - return AuthError.service(error.localizedDescription, - "", - error) - - case .dataNotFound(let message): - return AuthError.service(message, "", clientError) - - case .authError(let message): - return AuthError.notAuthorized(message, "Check if you are authorized to perform the request") - - case .retryError(let error): - if let authError = error as? AuthErrorConvertible { - return authError.authError - } else { - return AuthError.service( - error.localizedDescription, - """ - Check your network connection, retry when the network is available. - HTTP Response stauts code: \(String(describing: httpResponse?.statusCode)) - """, - AWSCognitoAuthError.network) - - } - - case .unknownError(let message): - return AuthError.unknown(message, clientError) - } - } - -} - -extension Error { - func internalAWSServiceError() -> E? { - if let internalError = self as? E { - return internalError - } - - if let sdkError = self as? SdkError { - return sdkError.internalAWSServiceError() - } - return nil - } -} - -extension SdkError { - - func internalAWSServiceError() -> E? { - switch self { - - case .service(let error, _): - if let serviceError = error as? E { - return serviceError - } - - case .client(let clientError, _): - return clientError.internalAWSServiceError() - - default: break - - } - return nil - } -} - -extension ClientError { - - func internalAWSServiceError() -> E? { - switch self { - case .retryError(let retryError): - if let sdkError = retryError as? SdkError { - return sdkError.internalAWSServiceError() - } - - default: break - } - return nil - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SetUserMFAPreferenceOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SetUserMFAPreferenceOutputError+AuthError.swift deleted file mode 100644 index 0a58116414..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SetUserMFAPreferenceOutputError+AuthError.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSCognitoIdentityProvider -import Amplify - -extension SetUserMFAPreferenceOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown( - exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return AuthError.service( - exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .notAuthorizedException(let exception): - return .notAuthorized( - exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service( - exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service( - exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .userNotConfirmedException(let userNotConfirmedException): - return .service( - userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return .service( - exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .forbiddenException(let forbiddenException): - return .service( - forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SignUpOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SignUpOutputError+AuthError.swift deleted file mode 100644 index dc530dc3b4..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SignUpOutputError+AuthError.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation -import AWSCognitoIdentityProvider - -extension SignUpOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeDeliveryFailureException(let exception): - - return .service( - exception.message ?? "Code Delivery Failure error", - AuthPluginErrorConstants.codeDeliveryError, - AWSCognitoAuthError.codeDelivery - ) - case .internalErrorException(let exception): - - return .unknown( - exception.message ?? "Internal exception occurred" - ) - case .invalidEmailRoleAccessPolicyException(let exception): - - return .service( - exception.message ?? "Invalid email role access policy error", - AuthPluginErrorConstants.invalidEmailRoleError, - AWSCognitoAuthError.emailRole - ) - case .invalidLambdaResponseException(let exception): - - return .service( - exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .invalidParameterException(let exception): - - return .service( - exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter - ) - case .invalidPasswordException(let exception): - - return .service( - exception.message ?? "Invalid password error", - AuthPluginErrorConstants.invalidPasswordError, - AWSCognitoAuthError.invalidPassword - ) - case .invalidSmsRoleAccessPolicyException(let exception): - - return .service( - exception.message ?? "Invalid SMS role access policy error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole - ) - case .invalidSmsRoleTrustRelationshipException(let exception): - - return .service( - exception.message ?? "Invalid SMS role access policy error", - AuthPluginErrorConstants.invalidSMSRoleError, - AWSCognitoAuthError.smsRole - ) - case .notAuthorizedException(let exception): - - return .notAuthorized( - exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .resourceNotFoundException(let exception): - - return .service( - exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound - ) - case .tooManyRequestsException(let exception): - - return .service( - exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded - ) - case .unexpectedLambdaException(let exception): - - return .service( - exception.message ?? "Unexpected lambda error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .userLambdaValidationException(let exception): - - return .service( - exception.message ?? "User lambda validation error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda - ) - case .usernameExistsException(let exception): - - return .service( - exception.message ?? "Username exists error", - AuthPluginErrorConstants.userNameExistsError, - AWSCognitoAuthError.usernameExists - ) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateDeviceStatusOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateDeviceStatusOutputError+AuthError.swift deleted file mode 100644 index 5df867d387..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateDeviceStatusOutputError+AuthError.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentityProvider -import Amplify - -extension UpdateDeviceStatusOutputError: AuthErrorConvertible { - - var authError: AuthError { - switch self { - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidUserPoolConfigurationException(let exception): - return .configuration(exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let exception): - return .notAuthorized(exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return .service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return .service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return .service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let userNotConfirmedException): - return .service(userNotConfirmedException.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return .service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateUserAttributesOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateUserAttributesOutputError+AuthError.swift deleted file mode 100644 index 2d68f8c446..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/UpdateUserAttributesOutputError+AuthError.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCognitoIdentity -import Amplify -import AWSCognitoIdentityProvider - -extension UpdateUserAttributesOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .aliasExistsException(let exception): - return AuthError.service(exception.message ?? "An account with this email or phone already exists.", - AuthPluginErrorConstants.aliasExistsError, - AWSCognitoAuthError.aliasExists) - case .codeMismatchException(let exception): - return AuthError.service(exception.message ?? "Provided code does not match what the server was expecting.", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch) - case .expiredCodeException(let exception): - return AuthError.service(exception.message ?? "Provided code has expired.", - AuthPluginErrorConstants.codeExpiredError, - AWSCognitoAuthError.codeExpired) - case .internalErrorException(let exception): - return .unknown(exception.message ?? "Internal exception occurred") - case .invalidLambdaResponseException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .invalidParameterException(let exception): - return AuthError.service(exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidSmsRoleAccessPolicyException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Access Policy error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .invalidSmsRoleTrustRelationshipException(let exception): - return AuthError.service(exception.message ?? "Invalid SMS Role Trust Relationship error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.smsRole) - case .notAuthorizedException(let exception): - return AuthError.notAuthorized(exception.message ?? "Not authrozied error", - AuthPluginErrorConstants.notAuthorizedError) - case .passwordResetRequiredException(let exception): - return AuthError.service(exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let exception): - return AuthError.service(exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service(exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .unexpectedLambdaException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userLambdaValidationException(let exception): - return AuthError.service(exception.message ?? "Invalid lambda response error", - AuthPluginErrorConstants.lambdaError, - AWSCognitoAuthError.lambda) - case .userNotConfirmedException(let exception): - return AuthError.service(exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service(exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - case .codeDeliveryFailureException(let exception): - return .service(exception.message ?? "Code Delivery Failure error", - AuthPluginErrorConstants.codeDeliveryError, - AWSCognitoAuthError.codeDelivery) - case .invalidEmailRoleAccessPolicyException(let exception): - return .service(exception.message ?? "Invalid email role access policy error", - AuthPluginErrorConstants.invalidEmailRoleError, - AWSCognitoAuthError.emailRole) - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } - -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifySoftwareTokenOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifySoftwareTokenOutputError+AuthError.swift deleted file mode 100644 index d7b56959ad..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifySoftwareTokenOutputError+AuthError.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import AWSCognitoIdentityProvider - -extension VerifySoftwareTokenOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeMismatchException(let exception): - return AuthError.service( - exception.message ?? "Provided code does not match what the server was expecting.", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch) - case .enableSoftwareTokenMFAException(let exception): - return AuthError.service( - exception.message ?? "Unable to enable software token MFA", - AuthPluginErrorConstants.softwareTokenNotFoundError, - AWSCognitoAuthError.softwareTokenMFANotEnabled) - case .forbiddenException(let forbiddenException): - return .service( - forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - case .internalErrorException(let internalErrorException): - return .unknown( - internalErrorException.message ?? "Internal exception occurred") - case .invalidParameterException(let invalidParameterException): - return .service( - invalidParameterException.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter) - case .invalidUserPoolConfigurationException(let exception): - return .configuration( - exception.message ?? "Invalid UserPool Configuration error", - AuthPluginErrorConstants.configurationError) - case .notAuthorizedException(let notAuthorizedException): - return .notAuthorized( - notAuthorizedException.message ?? "Not authorized Error", - AuthPluginErrorConstants.notAuthorizedError, - nil) - case .passwordResetRequiredException(let exception): - return AuthError.service( - exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired) - case .resourceNotFoundException(let resourceNotFoundException): - return AuthError.service( - resourceNotFoundException.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound) - case .softwareTokenMFANotFoundException(let exception): - return AuthError.service( - exception.message ?? "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool.", - AuthPluginErrorConstants.softwareTokenNotFoundError, - AWSCognitoAuthError.mfaMethodNotFound) - case .tooManyRequestsException(let exception): - return AuthError.service( - exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded) - case .userNotConfirmedException(let exception): - return AuthError.service( - exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed) - case .userNotFoundException(let exception): - return AuthError.service( - exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound) - case .unknown(let unknownAWSHttpServiceError): - let statusCode = unknownAWSHttpServiceError._statusCode?.rawValue ?? -1 - let message = unknownAWSHttpServiceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifyUserAttributeOutputError+AuthError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifyUserAttributeOutputError+AuthError.swift deleted file mode 100644 index 812e6b71ce..0000000000 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/VerifyUserAttributeOutputError+AuthError.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation -import AWSCognitoIdentityProvider - -extension VerifyUserAttributeOutputError: AuthErrorConvertible { - var authError: AuthError { - switch self { - case .codeMismatchException(let exception): - - return .service( - exception.message ?? "Code mismatch error", - AuthPluginErrorConstants.codeMismatchError, - AWSCognitoAuthError.codeMismatch - ) - case .expiredCodeException(let exception): - - return .service( - exception.message ?? "Expired code error", - AuthPluginErrorConstants.codeExpiredError, - AWSCognitoAuthError.codeExpired - ) - case .limitExceededException(let exception): - - return .service( - exception.message ?? "Limit exceeded error", - AuthPluginErrorConstants.limitExceededError, - AWSCognitoAuthError.limitExceeded - ) - case .userNotFoundException(let exception): - - return .service( - exception.message ?? "User not found error", - AuthPluginErrorConstants.userNotFoundError, - AWSCognitoAuthError.userNotFound - ) - case .internalErrorException(let exception): - - return .unknown( - exception.message ?? "Internal exception occurred" - ) - case .invalidParameterException(let exception): - - return .service( - exception.message ?? "Invalid parameter error", - AuthPluginErrorConstants.invalidParameterError, - AWSCognitoAuthError.invalidParameter - ) - case .notAuthorizedException(let exception): - - return .notAuthorized( - exception.message ?? "Not authorized error", - AuthPluginErrorConstants.notAuthorizedError - ) - case .resourceNotFoundException(let exception): - - return .service( - exception.message ?? "Resource not found error", - AuthPluginErrorConstants.resourceNotFoundError, - AWSCognitoAuthError.resourceNotFound - ) - case .tooManyRequestsException(let exception): - - return .service( - exception.message ?? "Too many requests error", - AuthPluginErrorConstants.tooManyRequestError, - AWSCognitoAuthError.requestLimitExceeded - ) - - case .unknown(let serviceError): - let statusCode = serviceError._statusCode?.rawValue ?? -1 - let message = serviceError._message ?? "" - return .unknown("Unknown service error occurred with status \(statusCode) \(message)") - - case .passwordResetRequiredException(let exception): - return .service( - exception.message ?? "Password reset required error", - AuthPluginErrorConstants.passwordResetRequired, - AWSCognitoAuthError.passwordResetRequired - ) - - case .userNotConfirmedException(let exception): - return AuthError.service( - exception.message ?? "User not confirmed error", - AuthPluginErrorConstants.userNotConfirmedError, - AWSCognitoAuthError.userNotConfirmed - ) - case .aliasExistsException(let exception): - return AuthError.service( - exception.message ?? "An account with this email or phone already exists.", - AuthPluginErrorConstants.aliasExistsError, - AWSCognitoAuthError.aliasExists) - - case .forbiddenException(let forbiddenException): - return .service(forbiddenException.message ?? "Access to the requested resource is forbidden", - AuthPluginErrorConstants.forbiddenError) - } - } -} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/SdkTypealiases.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/SdkTypealiases.swift index 28d19fa107..c4328aac58 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/SdkTypealiases.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/SdkTypealiases.swift @@ -9,6 +9,4 @@ import Foundation import AWSClientRuntime import ClientRuntime -public typealias SdkResult = Result> - public typealias NetworkResult = (Result) -> Void diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/SignInError+Helper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/SignInError+Helper.swift index 9cec199958..31b7d6b12e 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/SignInError+Helper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/SignInError+Helper.swift @@ -15,47 +15,19 @@ extension SignInError { var isUserNotConfirmed: Bool { switch self { case .service(error: let serviceError): - - if let internalError: InitiateAuthOutputError = serviceError.internalAWSServiceError(), - case .userNotConfirmedException = internalError { - return true - } - - if let internalError: RespondToAuthChallengeOutputError = serviceError.internalAWSServiceError(), - case .userNotConfirmedException = internalError { - return true - } - - if let internalError: VerifySoftwareTokenOutputError = serviceError.internalAWSServiceError(), - case .userNotConfirmedException = internalError { - return true - } - - default: break + return serviceError is AWSCognitoIdentityProvider.UserNotConfirmedException + default: + return false } - return false } var isResetPassword: Bool { switch self { case .service(error: let serviceError): - if let internalError: InitiateAuthOutputError = serviceError.internalAWSServiceError(), - case .passwordResetRequiredException = internalError { - return true - } - - if let internalError: RespondToAuthChallengeOutputError = serviceError.internalAWSServiceError(), - case .passwordResetRequiredException = internalError { - return true - } - - if let internalError: VerifySoftwareTokenOutputError = serviceError.internalAWSServiceError(), - case .passwordResetRequiredException = internalError { - return true - } - default: break + return serviceError is AWSCognitoIdentityProvider.PasswordResetRequiredException + default: + return false } - return false } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift index 9ded44922d..8e391355e5 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift @@ -25,27 +25,6 @@ struct AuthCognitoSignedOutSessionHelper { return authSession } - /// Guest/SignedOut session with any unhandled error - /// - /// The unhandled error is passed as identityId and aws credentials result. UserSub and Cognito Tokens will still - /// have signOut error. - /// - /// - Parameter error: Unhandled error - /// - Returns: Session will have isSignedIn = false - private static func makeSignedOutSession(withUnhandledError error: AuthError) -> AWSAuthCognitoSession { - - let identityIdError = error - let awsCredentialsError = error - - let tokensError = makeCognitoTokensSignedOutError() - - let authSession = AWSAuthCognitoSession(isSignedIn: false, - identityIdResult: .failure(identityIdError), - awsCredentialsResult: .failure(awsCredentialsError), - cognitoTokensResult: .failure(tokensError)) - return authSession - } - /// Guest/SignOut session when the guest access is not enabled. /// - Returns: Session with isSignedIn = false static func makeSessionWithNoGuestAccess() -> AWSAuthCognitoSession { @@ -68,26 +47,6 @@ struct AuthCognitoSignedOutSessionHelper { return authSession } - private static func makeOfflineSignedOutSession() -> AWSAuthCognitoSession { - let identityIdError = AuthError.service( - AuthPluginErrorConstants.identityIdOfflineError.errorDescription, - AuthPluginErrorConstants.identityIdOfflineError.recoverySuggestion, - AWSCognitoAuthError.network) - - let awsCredentialsError = AuthError.service( - AuthPluginErrorConstants.awsCredentialsOfflineError.errorDescription, - AuthPluginErrorConstants.awsCredentialsOfflineError.recoverySuggestion, - AWSCognitoAuthError.network) - - let tokensError = makeCognitoTokensSignedOutError() - - let authSession = AWSAuthCognitoSession(isSignedIn: false, - identityIdResult: .failure(identityIdError), - awsCredentialsResult: .failure(awsCredentialsError), - cognitoTokensResult: .failure(tokensError)) - return authSession - } - /// Guest/SignedOut session with couldnot retreive either aws credentials or identity id. /// - Returns: Session will have isSignedIn = false private static func makeSignedOutSessionWithServiceIssue() -> AWSAuthCognitoSession { @@ -109,13 +68,6 @@ struct AuthCognitoSignedOutSessionHelper { return authSession } - private static func makeUserSubSignedOutError() -> AuthError { - let userSubError = AuthError.signedOut( - AuthPluginErrorConstants.userSubSignOutError.errorDescription, - AuthPluginErrorConstants.userSubSignOutError.recoverySuggestion) - return userSubError - } - private static func makeCognitoTokensSignedOutError() -> AuthError { let tokensError = AuthError.signedOut( AuthPluginErrorConstants.cognitoTokensSignOutError.errorDescription, diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift index cd9760637a..9c225ec931 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift @@ -22,7 +22,7 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior { callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) { #if os(iOS) || os(macOS) self.webPresentation = presentationAnchor - let aswebAuthenticationSession = ASWebAuthenticationSession( + let aswebAuthenticationSession = createAuthenticationSession( url: url, callbackURLScheme: callbackScheme, completionHandler: { url, error in @@ -58,6 +58,16 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior { } #if os(iOS) || os(macOS) + var authenticationSessionFactory = ASWebAuthenticationSession.init(url:callbackURLScheme:completionHandler:) + + private func createAuthenticationSession( + url: URL, + callbackURLScheme: String?, + completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler + ) -> ASWebAuthenticationSession { + return authenticationSessionFactory(url, callbackURLScheme, completionHandler) + } + private func convertHostedUIError(_ error: Error) -> HostedUIError { if let asWebAuthError = error as? ASWebAuthenticationSessionError { switch asWebAuthError.code { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift index 32b7da1c83..ad3987d7f4 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift @@ -41,10 +41,12 @@ class AWSAuthChangePasswordTask: AuthChangePasswordTask, DefaultLogger { log.verbose("Received success") } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { - throw AuthError.configuration("Unable to execute auth task", AuthPluginErrorConstants.configurationError, error) + } catch { + throw AuthError.configuration( + "Unable to execute auth task", + AuthPluginErrorConstants.configurationError, + error + ) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift index d8b485acb0..bd9c8e0bc4 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift @@ -36,9 +36,7 @@ class AWSAuthConfirmResetPasswordTask: AuthConfirmResetPasswordTask, DefaultLogg try await confirmResetPassword() } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift index db0148eea5..962ae5c95a 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift @@ -42,16 +42,14 @@ class AWSAuthConfirmSignUpTask: AuthConfirmSignUpTask, DefaultLogger { _ = try await client.confirmSignUp(input: input) log.verbose("Received success") return AuthSignUpResult(.done, userID: nil) - } catch let error as AuthError { - throw error - } catch let error as AuthErrorConvertible { + } catch let error as AuthErrorConvertible { throw error.authError - } catch let error { - let error = AuthError.configuration( + } catch { + throw AuthError.configuration( "Unable to create a Swift SDK user pool service", AuthPluginErrorConstants.configurationError, - error) - throw error + error + ) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift index a39373f944..44b1836d24 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift @@ -38,9 +38,7 @@ class AWSAuthResendSignUpCodeTask: AuthResendSignUpCodeTask, DefaultLogger { return details } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift index 47b52040c0..a8bb33034e 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift @@ -38,9 +38,7 @@ class AWSAuthResetPasswordTask: AuthResetPasswordTask, DefaultLogger { return result } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift index 6400464697..485663d3d8 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift @@ -52,16 +52,14 @@ class AWSAuthSignUpTask: AuthSignUpTask, DefaultLogger { let response = try await client.signUp(input: input) log.verbose("Received result") return response.authResponse - } catch let error as AuthError { - throw error } catch let error as AuthErrorConvertible { throw error.authError - } catch let error { - let error = AuthError.configuration( + } catch { + throw AuthError.configuration( "Unable to create a Swift SDK user pool service", AuthPluginErrorConstants.configurationError, - error) - throw error + error + ) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift index 9e48e9f2f0..be7690da99 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift @@ -41,11 +41,9 @@ class AWSAuthWebUISignInTask: AuthWebUISignInTask, DefaultLogger { return result } catch let autherror as AuthErrorConvertible { throw autherror.authError - } catch let autherror as AuthError { - throw autherror - } catch let error { - let error = AuthError.unknown("Not able to signIn to the webUI", error) - throw error + } catch { + throw AuthError.unknown("Not able to signIn to the webUI", error) + } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/FetchMFAPreferenceTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/FetchMFAPreferenceTask.swift index 008b3647c1..b3a456214d 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/FetchMFAPreferenceTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/FetchMFAPreferenceTask.swift @@ -46,9 +46,7 @@ class FetchMFAPreferenceTask: AuthFetchMFAPreferenceTask, DefaultLogger { return try await fetchMFAPreference(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/SetUpTOTPTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/SetUpTOTPTask.swift index 78d90b3b6b..6a7ab2b6ea 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/SetUpTOTPTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/SetUpTOTPTask.swift @@ -37,9 +37,7 @@ class SetUpTOTPTask: AuthSetUpTOTPTask, DefaultLogger { return try await setUpTOTP(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UpdateMFAPreferenceTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UpdateMFAPreferenceTask.swift index de0774fb7a..b9bedf4fe8 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UpdateMFAPreferenceTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UpdateMFAPreferenceTask.swift @@ -52,9 +52,7 @@ class UpdateMFAPreferenceTask: AuthUpdateMFAPreferenceTask, DefaultLogger { return try await updateMFAPreference(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthAttributeResendConfirmationCodeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthAttributeResendConfirmationCodeTask.swift index b169211da5..66796374ef 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthAttributeResendConfirmationCodeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthAttributeResendConfirmationCodeTask.swift @@ -37,12 +37,12 @@ class AWSAuthAttributeResendConfirmationCodeTask: AuthAttributeResendConfirmatio return devices } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { - throw AuthError.configuration("Unable to execute auth task", - AuthPluginErrorConstants.configurationError, - error) + } catch { + throw AuthError.configuration( + "Unable to execute auth task", + AuthPluginErrorConstants.configurationError, + error + ) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthConfirmUserAttributeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthConfirmUserAttributeTask.swift index 390f15a2c2..8ed94106df 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthConfirmUserAttributeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthConfirmUserAttributeTask.swift @@ -37,9 +37,7 @@ class AWSAuthConfirmUserAttributeTask: AuthConfirmUserAttributeTask { try await confirmUserAttribute(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthFetchUserAttributeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthFetchUserAttributeTask.swift index 5a4ce680a0..cf15cc9395 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthFetchUserAttributeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthFetchUserAttributeTask.swift @@ -37,9 +37,7 @@ class AWSAuthFetchUserAttributeTask: AuthFetchUserAttributeTask { return try await getUserAttributes(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributeTask.swift index e0f6b9c386..9b25a406bf 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributeTask.swift @@ -36,11 +36,8 @@ class AWSAuthUpdateUserAttributeTask: AuthUpdateUserAttributeTask { return try await updateUserAttribute(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { - let error = AuthError.unknown("Unable to execute auth task", error) - throw error + } catch { + throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributesTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributesTask.swift index 30879838b4..0b0b8ed3dd 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributesTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UserTasks/AWSAuthUpdateUserAttributesTask.swift @@ -36,9 +36,7 @@ class AWSAuthUpdateUserAttributesTask: AuthUpdateUserAttributesTask { return try await updateUserAttribute(with: accessToken) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/VerifyTOTPSetupTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/VerifyTOTPSetupTask.swift index 38d49134b4..40156b2e12 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/VerifyTOTPSetupTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/VerifyTOTPSetupTask.swift @@ -41,9 +41,7 @@ class VerifyTOTPSetupTask: AuthVerifyTOTPSetupTask, DefaultLogger { with: accessToken, userCode: request.code) } catch let error as AuthErrorConvertible { throw error.authError - } catch let error as AuthError { - throw error - } catch let error { + } catch { throw AuthError.unknown("Unable to execute auth task", error) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/ClearCredentialsTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/ClearCredentialsTests.swift index 09dc525141..8f3f2a6899 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/ClearCredentialsTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/ClearCredentialsTests.swift @@ -50,7 +50,10 @@ class ClearCredentialsTests: XCTestCase { await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } /// Test is responsible to check if configuration error is correctly caught by the action @@ -82,7 +85,10 @@ class ClearCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } /// Test is responsible to check if the clear credentials handle a known error @@ -136,7 +142,10 @@ class ClearCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } /// Test is responsible to check if the clear credentials handle an unknown error @@ -191,7 +200,10 @@ class ClearCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/LoadCredentialsTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/LoadCredentialsTests.swift index c2a61e659d..2375daf8a6 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/LoadCredentialsTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/LoadCredentialsTests.swift @@ -67,7 +67,10 @@ class LoadCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [loadCredentialHandlerInvoked], + timeout: 0.1 + ) } /// Test is responsible to check if configuration error is correctly caught by the action @@ -99,7 +102,10 @@ class LoadCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } /// Test is responsible to check if the load credentials handle a known error @@ -153,7 +159,10 @@ class LoadCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } /// Test is responsible to check if the load credentials handle an unknown error @@ -210,7 +219,10 @@ class LoadCredentialsTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift index d4a0248e34..1252888632 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift @@ -72,7 +72,11 @@ class MigrateLegacyCredentialStoreTests: XCTestCase { let action = MigrateLegacyCredentialStore() await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [saveCredentialHandlerInvoked], + timeout: 0.1 + ) } /// Test is responsible for making sure that the legacy credential store clearing up is getting called for user pool and identity pool @@ -115,8 +119,124 @@ class MigrateLegacyCredentialStoreTests: XCTestCase { let action = MigrateLegacyCredentialStore() await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment) - await waitForExpectations(timeout: 0.1) - + await fulfillment( + of: [migrationCompletionInvoked], + + timeout: 0.1 + ) } + /// - Given: A credential store with an invalid environment + /// - When: The migration legacy store action is executed + /// - Then: An error event of type configuration is dispatched + func testExecute_withInvalidEnvironment_shouldDispatchError() async { + let expectation = expectation(description: "noEnvironment") + let action = MigrateLegacyCredentialStore() + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? CredentialStoreEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to no CredentialEnvironment") + expectation.fulfill() + return + } + XCTAssertEqual(error, .configuration(message: AuthPluginErrorConstants.configurationError)) + expectation.fulfill() + }, + environment: MockInvalidEnvironment() + ) + await fulfillment(of: [expectation], timeout: 1) + } + + /// - Given: A credential store with an environment that only has identity pool + /// - When: The migration legacy store action is executed + /// - Then: + /// - A .loadCredentialStore event with type .amplifyCredentials is dispatched + /// - An .identityPoolOnly credential is saved + func testExecute_withoutUserPool_andWithoutLoginsTokens_shouldDispatchLoadEvent() async { + let expectation = expectation(description: "noUserPoolTokens") + let action = MigrateLegacyCredentialStore() + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? CredentialStoreEvent, + case .loadCredentialStore(let type) = event.eventType else { + XCTFail("Expected .loadCredentialStore") + expectation.fulfill() + return + } + XCTAssertEqual(type, .amplifyCredentials) + expectation.fulfill() + }, + environment: CredentialEnvironment( + authConfiguration: .identityPools(.testData), + credentialStoreEnvironment: BasicCredentialStoreEnvironment( + amplifyCredentialStoreFactory: { + MockAmplifyCredentialStoreBehavior( + saveCredentialHandler: { codableCredentials in + guard let amplifyCredentials = codableCredentials as? AmplifyCredentials, + case .identityPoolOnly(_, let credentials) = amplifyCredentials else { + XCTFail("Expected .identityPoolOnly") + return + } + XCTAssertFalse(credentials.sessionToken.isEmpty) + } + ) + }, + legacyKeychainStoreFactory: { _ in + MockKeychainStoreBehavior(data: "hostedUI") + }), + logger: MigrateLegacyCredentialStore.log + ) + ) + await fulfillment(of: [expectation], timeout: 1) + } + + /// - Given: A credential store with an environment that only has identity pool + /// - When: The migration legacy store action is executed + /// - A .loadCredentialStore event with type .amplifyCredentials is dispatched + /// - An .identityPoolWithFederation credential is saved + func testExecute_withoutUserPool_andWithLoginsTokens_shouldDispatchLoadEvent() async { + let expectation = expectation(description: "noUserPoolTokens") + let action = MigrateLegacyCredentialStore() + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? CredentialStoreEvent, + case .loadCredentialStore(let type) = event.eventType else { + XCTFail("Expected .loadCredentialStore") + expectation.fulfill() + return + } + XCTAssertEqual(type, .amplifyCredentials) + expectation.fulfill() + }, + environment: CredentialEnvironment( + authConfiguration: .identityPools(.testData), + credentialStoreEnvironment: BasicCredentialStoreEnvironment( + amplifyCredentialStoreFactory: { + MockAmplifyCredentialStoreBehavior( + saveCredentialHandler: { codableCredentials in + guard let amplifyCredentials = codableCredentials as? AmplifyCredentials, + case .identityPoolWithFederation(let token, _, _) = amplifyCredentials else { + XCTFail("Expected .identityPoolWithFederation") + return + } + + XCTAssertEqual(token.token, "token") + XCTAssertEqual(token.provider.userPoolProviderName, "provider") + } + ) + }, + legacyKeychainStoreFactory: { _ in + let data = try! JSONEncoder().encode([ + "provider": "token" + ]) + return MockKeychainStoreBehavior( + data: String(decoding: data, as: UTF8.self) + ) + }), + logger: action.log + ) + ) + await fulfillment(of: [expectation], timeout: 1) + } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchAWSCredentials/FetchAuthAWSCredentialsTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchAWSCredentials/FetchAuthAWSCredentialsTests.swift index 5ee9586a47..fa4e75a9ce 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchAWSCredentials/FetchAuthAWSCredentialsTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchAWSCredentials/FetchAuthAWSCredentialsTests.swift @@ -30,7 +30,11 @@ class FetchAuthAWSCredentialsTests: XCTestCase { expectation.fulfill() } }, environment: MockInvalidEnvironment()) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } func testInvalidIdentitySuccessfullResponse() async { @@ -60,7 +64,10 @@ class FetchAuthAWSCredentialsTests: XCTestCase { } }, environment: authEnvironment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } func testInvalidAWSCredentialSuccessfulResponse() async { @@ -93,7 +100,10 @@ class FetchAuthAWSCredentialsTests: XCTestCase { environment: authEnvironment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } func testValidSuccessfulResponse() async { @@ -134,7 +144,11 @@ class FetchAuthAWSCredentialsTests: XCTestCase { }, environment: authEnvironment ) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [credentialValidExpectation], + timeout: 0.1 + ) } func testFailureResponse() async { @@ -166,7 +180,11 @@ class FetchAuthAWSCredentialsTests: XCTestCase { }, environment: authEnvironment ) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift new file mode 100644 index 0000000000..2b94f7721f --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift @@ -0,0 +1,310 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#if os(iOS) || os(macOS) + +@testable import AWSCognitoAuthPlugin +import AWSCognitoIdentityProvider +import AWSPluginsCore +import XCTest + +class RefreshHostedUITokensTests: XCTestCase { + private let tokenResult: [String: Any] = [ + "id_token": AWSCognitoUserPoolTokens.testData.idToken, + "access_token": AWSCognitoUserPoolTokens.testData.accessToken, + "refresh_token": AWSCognitoUserPoolTokens.testData.refreshToken, + "expires_in": 10 + ] + + private var hostedUIEnvironment: HostedUIEnvironment { + BasicHostedUIEnvironment( + configuration: .init( + clientId: "clientId", + oauth: .init( + domain: "cognitodomain", + scopes: ["name"], + signInRedirectURI: "myapp://", + signOutRedirectURI: "myapp://" + ) + ), + hostedUISessionFactory: sessionFactory, + urlSessionFactory: urlSessionMock, + randomStringFactory: mockRandomString + ) + } + + override func setUp() { + let result = try! JSONSerialization.data(withJSONObject: tokenResult) + MockURLProtocol.requestHandler = { _ in + return (HTTPURLResponse(), result) + } + } + + override func tearDown() { + MockURLProtocol.requestHandler = nil + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked with a valid response + /// Then: A RefreshSessionEvent.refreshIdentityInfo is dispatched + func testExecute_withValidResponse_shouldDispatchRefreshEvent() async { + let expectation = expectation(description: "refreshHostedUITokens") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case .refreshIdentityInfo(let data, _) = event.eventType else { + XCTFail("Failed to refresh tokens") + expectation.fulfill() + return + } + + XCTAssertEqual(data.cognitoUserPoolTokens.idToken, self.tokenResult["id_token"] as? String) + XCTAssertEqual(data.cognitoUserPoolTokens.accessToken, self.tokenResult["access_token"] as? String) + XCTAssertEqual(data.cognitoUserPoolTokens.refreshToken, self.tokenResult["refresh_token"] as? String) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked and throws a HostedUIError + /// Then: A RefreshSessionEvent.throwError is dispatched with .service + func testExecute_withHostedUIError_shouldDispatchErrorEvent() async { + let expectedError = HostedUIError.serviceMessage("Something went wrong") + MockURLProtocol.requestHandler = { _ in + throw expectedError + } + + let expectation = expectation(description: "refreshHostedUITokens") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to Service Error") + expectation.fulfill() + return + } + + XCTAssertEqual(error, .service(expectedError)) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked and returns empty data + /// Then: A RefreshSessionEvent.throwError is dispatched with .service + func testExecute_withEmptyData_shouldDispatchErrorEvent() async { + MockURLProtocol.requestHandler = { _ in + return (HTTPURLResponse(), Data()) + } + + let expectation = expectation(description: "refreshHostedUITokens") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to Invalid Tokens") + expectation.fulfill() + return + } + + guard case .service(let serviceError) = error else { + XCTFail("Expected FetchSessionError.service, got \(error)") + expectation.fulfill() + return + } + + + XCTAssertEqual((serviceError as NSError).code, NSPropertyListReadCorruptError) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked and returns data that is invalid for tokens + /// Then: A RefreshSessionEvent.throwError is dispatched with .invalidTokens + func testExecute_withInvalidTokens_shouldDispatchErrorEvent() async { + let result: [String: Any] = [ + "key": "value" + ] + MockURLProtocol.requestHandler = { _ in + return (HTTPURLResponse(), try! JSONSerialization.data(withJSONObject: result)) + } + + let expectation = expectation(description: "refreshHostedUITokens") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to Invalid Tokens") + expectation.fulfill() + return + } + + + XCTAssertEqual(error, .invalidTokens) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked and returns data representing an error + /// Then: A RefreshSessionEvent.throwError is dispatched with .service + func testExecute_withErrorResponse_shouldDispatchErrorEvent() async { + let result: [String: Any] = [ + "error": "Error.", + "error_description": "Something went wrong" + ] + MockURLProtocol.requestHandler = { _ in + return (HTTPURLResponse(), try! JSONSerialization.data(withJSONObject: result)) + } + + let expectation = expectation(description: "refreshHostedUITokens") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to Invalid Tokens") + expectation.fulfill() + return + } + + guard case .service(let serviceError) = error, + case .serviceMessage(let errorMessage) = serviceError as? HostedUIError else { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") + expectation.fulfill() + return + } + + + XCTAssertEqual(errorMessage, "Error. Something went wrong") + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked without a HostedUIEnvironment + /// Then: A RefreshSessionEvent.throwError is dispatched with .noUserPool + func testExecute_withoutHostedUIEnvironment_shouldDispatchErrorEvent() async { + let expectation = expectation(description: "noHostedUIEnvironment") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to no HostedUIEnvironment") + expectation.fulfill() + return + } + + XCTAssertEqual(error, .noUserPool) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: nil + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A RefreshHostedUITokens action + /// When: execute is invoked without a UserPoolEnvironment + /// Then: A RefreshSessionEvent.throwError is dispatched with .noUserPool + func testExecute_withoutUserPoolEnvironment_shouldDispatchErrorEvent() async { + let expectation = expectation(description: "noUserPoolEnvironment") + let action = RefreshHostedUITokens(existingSignedIndata: .testData) + action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? RefreshSessionEvent, + case let .throwError(error) = event.eventType else { + XCTFail("Expected failure due to no UserPoolEnvironment") + expectation.fulfill() + return + } + + XCTAssertEqual(error, .noUserPool) + expectation.fulfill() + }, + environment: MockInvalidEnvironment() + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + private func identityProviderFactory() throws -> CognitoUserPoolBehavior { + return MockIdentityProvider( + mockInitiateAuthResponse: { _ in + return InitiateAuthOutputResponse( + authenticationResult: .init( + accessToken: "accessTokenNew", + expiresIn: 100, + idToken: "idTokenNew", + refreshToken: "refreshTokenNew") + ) + } + ) + } + + private func urlSessionMock() -> URLSession { + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [MockURLProtocol.self] + return URLSession(configuration: configuration) + } + + private func sessionFactory() -> HostedUISessionBehavior { + MockHostedUISession(result: .failure(.cancelled)) + } + + private func mockRandomString() -> RandomStringBehavior { + return MockRandomStringGenerator( + mockString: "mockString", + mockUUID: "mockUUID" + ) + } +} +#endif diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshUserPoolTokensTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshUserPoolTokensTests.swift index a98e7124f3..ad029fbf67 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshUserPoolTokensTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshUserPoolTokensTests.swift @@ -34,7 +34,10 @@ class RefreshUserPoolTokensTests: XCTestCase { }, environment: MockInvalidEnvironment() ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } func testInvalidSuccessfulResponse() async { @@ -63,7 +66,10 @@ class RefreshUserPoolTokensTests: XCTestCase { userPoolFactory: identityProviderFactory) ) - await waitForExpectations(timeout: 1) + await fulfillment( + of: [expectation], + timeout: 1 + ) } func testValidSuccessfulResponse() async { @@ -93,7 +99,11 @@ class RefreshUserPoolTokensTests: XCTestCase { }, environment: Defaults.makeDefaultAuthEnvironment( userPoolFactory: identityProviderFactory) ) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } func testFailureResponse() async { @@ -128,7 +138,10 @@ class RefreshUserPoolTokensTests: XCTestCase { } }, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/InitializeFetchAuthSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/InitializeFetchAuthSessionTests.swift index 9c60fad781..4976285b0e 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/InitializeFetchAuthSessionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/InitializeFetchAuthSessionTests.swift @@ -32,7 +32,10 @@ class InitializeFetchAuthSessionTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [expectation], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/InitiateAuthSRPTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/InitiateAuthSRPTests.swift index 2ad9ef7ee9..9ce542f0a2 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/InitiateAuthSRPTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/InitiateAuthSRPTests.swift @@ -32,7 +32,10 @@ class InitiateAuthSRPTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [initiateAuthInvoked], + timeout: 0.1 + ) } func testFailedInitiateAuthPropagatesError() async { @@ -70,7 +73,10 @@ class InitiateAuthSRPTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [errorEventSent], + timeout: 0.1 + ) } func testSuccessfulInitiateAuthPropagatesSuccess() async { @@ -106,7 +112,9 @@ class InitiateAuthSRPTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [successEventSent], + timeout: 0.1 + ) } - } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift new file mode 100644 index 0000000000..53afd8af8f --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift @@ -0,0 +1,177 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import AWSCognitoAuthPlugin +import AWSCognitoIdentityProvider +@testable import AWSPluginsTestCommon +import XCTest + +class VerifyDevicePasswordSRPSignatureTests: XCTestCase { + private var srpClient: MockSRPClientBehavior! + + override func setUp() async throws { + MockSRPClientBehavior.reset() + srpClient = MockSRPClientBehavior() + } + + override func tearDown() { + MockSRPClientBehavior.reset() + srpClient = nil + } + + /// Given: A VerifyDevicePasswordSRP + /// When: signature is invoked + /// Then: a non-empty string is returned + func testSignature_withValidValues_shouldReturnSignature() async { + do { + let signature = try signature() + XCTAssertFalse(signature.isEmpty) + } catch { + XCTFail("Should not throw error: \(error)") + } + } + + /// Given: A VerifyDevicePasswordSRP + /// When: signature is invoked and the srpClient throws an SRPError error when generating a shared secret + /// Then: a .calculation error is thrown + func testSignature_withSRPErrorOnSharedSecret_shouldThrowCalculationError() async { + srpClient.sharedSecret = .failure(SRPError.numberConversion) + do { + try signature() + XCTFail("Should not succeed") + } catch { + guard case .calculation(let srpError) = error as? SignInError else { + XCTFail("Expected SRPError.calculation, got \(error)") + return + } + + XCTAssertEqual(srpError, .numberConversion) + } + } + + /// Given: A VerifyDevicePasswordSRP + /// When: signature is invoked and the srpClient throws a non-SRPError error when generating a shared secret + /// Then: a .configuration error is thrown + func testSignature_withOtherErrorOnSharedSecret_shouldThrowCalculationError() async { + srpClient.sharedSecret = .failure(CancellationError()) + do { + try signature() + XCTFail("Should not succeed") + } catch { + guard case .configuration(let message) = error as? SignInError else { + XCTFail("Expected SRPError.configuration, got \(error)") + return + } + + XCTAssertEqual(message, "Could not calculate shared secret") + } + } + + /// Given: A VerifyDevicePasswordSRP + /// When: signature is invoked and the srpClient throws a SRPError error when generating an authentication key + /// Then: a .calculation error is thrown + func testSignature_withSRPErrorOnAuthenticationKey_shouldThrowCalculationError() async { + MockSRPClientBehavior.authenticationKey = .failure(SRPError.numberConversion) + do { + try signature() + XCTFail("Should not succeed") + } catch { + guard case .calculation(let srpError) = error as? SignInError else { + XCTFail("Expected SRPError.calculation, got \(error)") + return + } + + XCTAssertEqual(srpError, .numberConversion) + } + } + + /// Given: A VerifyDevicePasswordSRP + /// When: signature is invoked and the srpClient throws a non-SRPError error when generating an authentication key + /// Then: a .configuration error is thrown + func testSignature_withOtherErrorOnAuthenticationKey_shouldThrowCalculationError() async { + MockSRPClientBehavior.authenticationKey = .failure(CancellationError()) + do { + try signature() + XCTFail("Should not succeed") + } catch { + guard case .configuration(let message) = error as? SignInError else { + XCTFail("Expected SRPError.configuration, got \(error)") + return + } + + XCTAssertEqual(message, "Could not calculate signature") + } + } + + @discardableResult + private func signature() throws -> String { + let action = VerifyDevicePasswordSRP( + stateData: .testData, + authResponse: InitiateAuthOutputResponse.validTestData + ) + + return try action.signature( + deviceGroupKey: "deviceGroupKey", + deviceKey: "deviceKey", + deviceSecret: "deviceSecret", + saltHex: "saltHex", + secretBlock: "secretBlock".data(using: .utf8) ?? Data(), + serverPublicBHexString: "serverPublicBHexString", + srpClient: srpClient + ) + } +} + +private class MockSRPClientBehavior: SRPClientBehavior { + var kHexValue: String = "kHexValue" + + static func calculateUHexValue( + clientPublicKeyHexValue: String, + serverPublicKeyHexValue: String + ) throws -> String { + return "UHexValue" + } + + static var authenticationKey: Result = .success("AuthenticationKey".data(using: .utf8)!) + static func generateAuthenticationKey( + sharedSecretHexValue: String, + uHexValue: String + ) throws -> Data { + return try authenticationKey.get() + } + + static func reset() { + authenticationKey = .success("AuthenticationKey".data(using: .utf8)!) + } + + func generateClientKeyPair() -> SRPKeys { + return .init( + publicKeyHexValue: "publicKeyHexValue", + privateKeyHexValue: "privateKeyHexValue" + ) + } + + var sharedSecret: Result = .success("SharedSecret") + func calculateSharedSecret( + username: String, + password: String, + saltHexValue: String, + clientPrivateKeyHexValue: String, + clientPublicKeyHexValue: String, + serverPublicKeyHexValue: String + ) throws -> String { + return try sharedSecret.get() + } + + func generateDevicePasswordVerifier( + deviceGroupKey: String, + deviceKey: String, + password: String + ) -> (salt: Data, passwordVerifier: Data) { + return (salt: Data(), passwordVerifier: Data()) + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyPasswordSRPTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyPasswordSRPTests.swift index 5700fa456b..e6a8e599bb 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyPasswordSRPTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyPasswordSRPTests.swift @@ -7,6 +7,7 @@ import XCTest import AWSCognitoIdentityProvider +import AWSClientRuntime @testable import AWSPluginsTestCommon @testable import AWSCognitoAuthPlugin @@ -47,7 +48,10 @@ class VerifyPasswordSRPTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [verifyPasswordInvoked], + timeout: 0.1 + ) } /// Test empty response is returned by Cognito proper error is thrown @@ -95,7 +99,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test invalid challenge response from initiate auth @@ -143,7 +150,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test challenge response with no salt from initiate auth @@ -191,7 +201,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test challenge response with no secretblock from initiate auth @@ -239,7 +252,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test challenge response with no SRPB from initiate auth @@ -287,7 +303,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test an exception from the SRP calculation @@ -335,7 +354,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test successful response from the VerifyPasswordSRP @@ -363,7 +385,8 @@ class VerifyPasswordSRPTests: XCTestCase { clientMetadata: [:]) let passwordVerifierCompletion = expectation( - description: "passwordVerifierCompletion") + description: "passwordVerifierCompletion" + ) let dispatcher = MockDispatcher { event in guard let event = event as? SignInEvent else { @@ -378,7 +401,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierCompletion], + timeout: 0.1 + ) } /// Test successful response from the VerifyPasswordSRP @@ -393,7 +419,13 @@ class VerifyPasswordSRPTests: XCTestCase { let identityProviderFactory: CognitoFactory = { MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw try RespondToAuthChallengeOutputError(httpResponse: MockHttpResponse.ok) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: MockHttpResponse.ok, + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) } @@ -425,7 +457,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test verify password retry on device not found @@ -441,9 +476,7 @@ class VerifyPasswordSRPTests: XCTestCase { let identityProviderFactory: CognitoFactory = { MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.resourceNotFoundException( - ResourceNotFoundException() - ) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) } @@ -473,7 +506,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test successful response from the VerifyPasswordSRP for confirmDevice @@ -516,7 +552,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierCompletion], + timeout: 0.1 + ) } /// Test successful response from the VerifyPasswordSRP for verifyDevice @@ -559,7 +598,10 @@ class VerifyPasswordSRPTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierCompletion], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/RevokeTokenTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/RevokeTokenTests.swift index d73b22ae01..31bf393a06 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/RevokeTokenTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/RevokeTokenTests.swift @@ -18,7 +18,7 @@ class RevokeTokenTests: XCTestCase { MockIdentityProvider( mockRevokeTokenResponse: { _ in revokeTokenInvoked.fulfill() - return try RevokeTokenOutputResponse(httpResponse: MockHttpResponse.ok) + return try await RevokeTokenOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -38,7 +38,10 @@ class RevokeTokenTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [revokeTokenInvoked], + timeout: 0.1 + ) } func testFailedRevokeTokenTriggersClearCredentialStore() async { @@ -79,14 +82,17 @@ class RevokeTokenTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [clearCredentialStoreEventSent], + timeout: 0.1 + ) } func testSuccessfulRevokeTokenTriggersClearCredentialStore() async { let identityProviderFactory: BasicUserPoolEnvironment.CognitoUserPoolFactory = { MockIdentityProvider( mockRevokeTokenResponse: { _ in - return try RevokeTokenOutputResponse(httpResponse: MockHttpResponse.ok) + return try await RevokeTokenOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -120,7 +126,10 @@ class RevokeTokenTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [clearCredentialStoreEventSent], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift new file mode 100644 index 0000000000..0751703550 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift @@ -0,0 +1,401 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import AWSCognitoAuthPlugin +import AWSCognitoIdentityProvider +import AWSPluginsCore +import XCTest + +class ShowHostedUISignOutTests: XCTestCase { + private var mockHostedUIResult: Result<[URLQueryItem], HostedUIError>! + private var signOutRedirectURI: String! + + override func setUp() { + signOutRedirectURI = "myapp://" + mockHostedUIResult = .success([.init(name: "key", value: "value")]) + } + + override func tearDown() { + signOutRedirectURI = nil + mockHostedUIResult = nil + } + + /// Given: A ShowHostedUISignOut action with global sign out set to true + /// When: execute is invoked with a success result + /// Then: A .signOutGlobally event is dispatched with a nil error + func testExecute_withGlobalSignOut_andSuccessResult_shouldDispatchSignOutEvent() async { + let expectation = expectation(description: "showHostedUISignOut") + let signInData = SignedInData.testData + let action = ShowHostedUISignOut( + signOutEvent: SignOutEventData(globalSignOut: true), + signInData: signInData + ) + + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let error) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + XCTAssertNil(error) + XCTAssertEqual(data, signInData) + self.validateDebugInformation(signInData: signInData, action: action) + + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action with global sign out set to false + /// When: execute is invoked with a success result + /// Then: A .revokeToken event is dispatched + func testExecute_withLocalSignOut_andSuccessResult_shouldDispatchSignOutEvent() async { + let expectation = expectation(description: "showHostedUISignOut") + let signInData = SignedInData.testData + let action = ShowHostedUISignOut( + signOutEvent: SignOutEventData(globalSignOut: false), + signInData: signInData + ) + + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .revokeToken(let data, let error, let globalSignOutError) = event.eventType else { + XCTFail("Expected SignOutEvent.revokeToken, got \(event)") + expectation.fulfill() + return + } + + XCTAssertNil(error) + XCTAssertNil(globalSignOutError) + XCTAssertEqual(data, signInData) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action + /// When: execute is invoked but fails to create a HostedUI session + /// Then: A .userCancelled event is dispatched + func testExecute_withInvalidResult_shouldDispatchUserCancelledEvent() async { + mockHostedUIResult = .failure(.cancelled) + let signInData = SignedInData.testData + + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + + let expectation = expectation(description: "showHostedUISignOut") + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent else { + XCTFail("Expected SignOutEvent, got \(event)") + expectation.fulfill() + return + } + + XCTAssertEqual(event.eventType, .userCancelled) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action + /// When: execute is invoked but fails to create a HostedUI session with a HostedUIError.signOutURI + /// Then: A .signOutGlobally event is dispatched with a HosterUIError.configuration error + func testExecute_withSignOutURIError_shouldThrowConfigurationError() async { + mockHostedUIResult = .failure(HostedUIError.signOutURI) + let signInData = SignedInData.testData + + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + + let expectation = expectation(description: "showHostedUISignOut") + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let hostedUIError) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + guard let hostedUIError = hostedUIError, + case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else { + XCTFail("Expected AuthError.configuration") + expectation.fulfill() + return + } + + XCTAssertEqual(errorDescription, "Could not create logout URL") + XCTAssertEqual(data, signInData) + XCTAssertNil(serviceError) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action + /// When: execute is invoked but fails to create a HostedUI session with a HostedUIError.invalidContext + /// Then: A .signOutGlobally event is dispatched with a HosterUIError.invalidState error + func testExecute_withInvalidContext_shouldThrowInvalidStateError() async { + mockHostedUIResult = .failure(HostedUIError.invalidContext) + let signInData = SignedInData.testData + + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + + let expectation = expectation(description: "showHostedUISignOut") + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let hostedUIError) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + guard let hostedUIError = hostedUIError, + case .invalidState(let errorDescription, let recoverySuggestion, let serviceError) = hostedUIError.error else { + XCTFail("Expected AuthError.invalidState") + expectation.fulfill() + return + } + + XCTAssertEqual(errorDescription, AuthPluginErrorConstants.hostedUIInvalidPresentation.errorDescription) + XCTAssertEqual(recoverySuggestion, AuthPluginErrorConstants.hostedUIInvalidPresentation.recoverySuggestion) + XCTAssertEqual(data, signInData) + XCTAssertNil(serviceError) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action with an invalid SignOutRedirectURI + /// When: execute is invoked + /// Then: A .signOutGlobally event is dispatched with a HosterUIError.configuration error + func testExecute_withInvalidSignOutURI_shouldThrowConfigurationError() async { + signOutRedirectURI = "invalidURI" + let signInData = SignedInData.testData + + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + + let expectation = expectation(description: "showHostedUISignOut") + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let hostedUIError) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + guard let hostedUIError = hostedUIError, + case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else { + XCTFail("Expected AuthError.configuration") + expectation.fulfill() + return + } + + XCTAssertEqual(errorDescription, "Callback URL could not be retrieved") + XCTAssertEqual(data, signInData) + XCTAssertNil(serviceError) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: hostedUIEnvironment + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action + /// When: execute is invoked with a nil HostedUIEnvironment + /// Then: A .signOutGlobally event is dispatched with a HosterUIError.configuration error + func testExecute_withoutHostedUIEnvironment_shouldThrowConfigurationError() async { + let expectation = expectation(description: "noHostedUIEnvironment") + let signInData = SignedInData.testData + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let hostedUIError) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + guard let hostedUIError = hostedUIError, + case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else { + XCTFail("Expected AuthError.configuration") + expectation.fulfill() + return + } + + XCTAssertEqual(data, signInData) + XCTAssertEqual(errorDescription, AuthPluginErrorConstants.configurationError) + XCTAssertNil(serviceError) + expectation.fulfill() + }, + environment: Defaults.makeDefaultAuthEnvironment( + userPoolFactory: identityProviderFactory, + hostedUIEnvironment: nil + ) + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + /// Given: A ShowHostedUISignOut action + /// When: execute is invoked with an invalid environment + /// Then: A .signOutGlobally event is dispatched with a HosterUIError.configuration error + func testExecute_withInvalidUserPoolEnvironment_shouldThrowConfigurationError() async { + let expectation = expectation(description: "invalidUserPoolEnvironment") + let signInData = SignedInData.testData + let action = ShowHostedUISignOut( + signOutEvent: .testData, + signInData: signInData + ) + await action.execute( + withDispatcher: MockDispatcher { event in + guard let event = event as? SignOutEvent, + case .signOutGlobally(let data, let hostedUIError) = event.eventType else { + XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)") + expectation.fulfill() + return + } + + guard let hostedUIError = hostedUIError, + case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else { + XCTFail("Expected AuthError.configuration") + expectation.fulfill() + return + } + + XCTAssertEqual(data, signInData) + XCTAssertEqual(errorDescription, AuthPluginErrorConstants.configurationError) + XCTAssertNil(serviceError) + expectation.fulfill() + }, + environment: MockInvalidEnvironment() + ) + + await fulfillment(of: [expectation], timeout: 1) + } + + private func validateDebugInformation(signInData: SignedInData, action: ShowHostedUISignOut) { + XCTAssertFalse(action.debugDescription.isEmpty) + guard let signInDataDictionary = action.debugDictionary["signInData"] as? [String: Any] else { + XCTFail("Expected signInData dictionary") + return + } + XCTAssertEqual(signInDataDictionary.count, signInData.debugDictionary.count) + + for key in signInDataDictionary.keys { + guard let left = signInDataDictionary[key] as? any Equatable, + let right = signInData.debugDictionary[key] as? any Equatable else { + continue + } + XCTAssertTrue(left.isEqual(to: right)) + } + } + + private var hostedUIEnvironment: HostedUIEnvironment { + BasicHostedUIEnvironment( + configuration: .init( + clientId: "clientId", + oauth: .init( + domain: "cognitodomain", + scopes: ["name"], + signInRedirectURI: "myapp://", + signOutRedirectURI: signOutRedirectURI + ) + ), + hostedUISessionFactory: { + MockHostedUISession(result: self.mockHostedUIResult) + }, + urlSessionFactory: { + URLSession.shared + }, + randomStringFactory: { + MockRandomStringGenerator( + mockString: "mockString", + mockUUID: "mockUUID" + ) + } + ) + } + + private func identityProviderFactory() throws -> CognitoUserPoolBehavior { + return MockIdentityProvider( + mockInitiateAuthResponse: { _ in + return InitiateAuthOutputResponse( + authenticationResult: .init( + accessToken: "accessTokenNew", + expiresIn: 100, + idToken: "idTokenNew", + refreshToken: "refreshTokenNew") + ) + } + ) + } +} + +private extension Equatable { + func isEqual(to other: any Equatable) -> Bool { + guard let other = other as? Self else { + return false + } + return self == other + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/SignOutGloballyTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/SignOutGloballyTests.swift index e13d8f2548..03d00fae34 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/SignOutGloballyTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/SignOutGloballyTests.swift @@ -18,7 +18,7 @@ class SignOutGloballyTests: XCTestCase { MockIdentityProvider( mockGlobalSignOutResponse: { _ in globalSignOutInvoked.fulfill() - return try GlobalSignOutOutputResponse(httpResponse: MockHttpResponse.ok) + return try await GlobalSignOutOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -36,7 +36,10 @@ class SignOutGloballyTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [globalSignOutInvoked], + timeout: 0.1 + ) } func testFailedGlobalSignOutTriggersBuildRevokeError() async { @@ -75,14 +78,17 @@ class SignOutGloballyTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [revokeTokenEventSent], + timeout: 0.1 + ) } func testSuccessfulGlobalSignOutTriggersRevokeToken() async { let identityProviderFactory: BasicUserPoolEnvironment.CognitoUserPoolFactory = { MockIdentityProvider( mockGlobalSignOutResponse: { _ in - return try GlobalSignOutOutputResponse(httpResponse: MockHttpResponse.ok) + return try await GlobalSignOutOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -114,7 +120,9 @@ class SignOutGloballyTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [revokeTokenEventSent], + timeout: 0.1 + ) } - } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/VerifySignInChallenge/VerifySignInChallengeTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/VerifySignInChallenge/VerifySignInChallengeTests.swift index b0d5dba2c0..f421ac3f57 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/VerifySignInChallenge/VerifySignInChallengeTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/VerifySignInChallenge/VerifySignInChallengeTests.swift @@ -7,6 +7,7 @@ import XCTest import AWSCognitoIdentityProvider +import AWSClientRuntime @testable import AWSPluginsTestCommon @testable import AWSCognitoAuthPlugin @@ -55,7 +56,10 @@ class VerifySignInChallengeTests: XCTestCase { environment: environment ) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [verifyPasswordInvoked], + timeout: 0.1 + ) } /// Test empty response is returned by Cognito proper error is thrown @@ -102,7 +106,10 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test successful response from the VerifySignInChallenge @@ -143,7 +150,10 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [verifyChallengeComplete], + timeout: 0.1 + ) } /// Test successful response from the VerifySignInChallenge @@ -158,7 +168,13 @@ class VerifySignInChallengeTests: XCTestCase { let identityProviderFactory: CognitoFactory = { MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw try RespondToAuthChallengeOutputError(httpResponse: MockHttpResponse.ok) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: MockHttpResponse.ok, + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) } @@ -189,7 +205,10 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test verify password retry on device not found @@ -205,9 +224,7 @@ class VerifySignInChallengeTests: XCTestCase { let identityProviderFactory: CognitoFactory = { MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.resourceNotFoundException( - ResourceNotFoundException() - ) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) } @@ -235,7 +252,10 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [passwordVerifierError], + timeout: 0.1 + ) } /// Test successful response from the VerifySignInChallenge for confirmDevice @@ -276,7 +296,10 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [verifyChallengeComplete], + timeout: 0.1 + ) } /// Test successful response from the VerifySignInChallenge for verify device @@ -317,6 +340,9 @@ class VerifySignInChallengeTests: XCTestCase { } await action.execute(withDispatcher: dispatcher, environment: environment) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [verifyChallengeComplete], + timeout: 0.1 + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift new file mode 100644 index 0000000000..63e72819b2 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift @@ -0,0 +1,62 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import AWSCognitoAuthPlugin +import XCTest + +class CognitoUserPoolASFTests: XCTestCase { + private var userPool: CognitoUserPoolASF! + + override func setUp() { + userPool = CognitoUserPoolASF() + } + + override func tearDown() { + userPool = nil + } + + /// Given: A CognitoUserPoolASF + /// When: userContextData is invoked + /// Then: A non-empty string is returned + func testUserContextData_shouldReturnData() throws { + let result = try userPool.userContextData( + for: "TestUser", + deviceInfo: ASFDeviceInfo(id: "mockedDevice"), + appInfo: ASFAppInfo(), + configuration: .testData + ) + XCTAssertFalse(result.isEmpty) + } + + /// Given: A CognitoUserPoolASF + /// When: calculateSecretHash is invoked + /// Then: A non-empty string is returned + func testCalculateSecretHash_shouldReturnHash() throws { + let result = try userPool.calculateSecretHash( + contextJson: "contextJson", + clientId: "clientId" + ) + XCTAssertFalse(result.isEmpty) + } + + /// Given: A CognitoUserPoolASF + /// When: calculateSecretHash is invoked with a clientId that cannot be parsed + /// Then: A ASFError.hashKey is thrown + func testCalculateSecretHash_withInvalidClientId_shouldThrowHashKeyError() { + do { + let result = try userPool.calculateSecretHash( + contextJson: "contextJson", + clientId: "🕺🏼" // This string cannot be represented using .ascii, so it will throw an error + ) + XCTFail("Expected ASFError.hashKey, got \(result)") + } catch let error as ASFError { + XCTAssertEqual(error, .hashKey) + } catch { + XCTFail("Expected ASFError.hashKey, for \(error)") + } + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/ClientSecretConfigurationTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/ClientSecretConfigurationTests.swift index 55bd81bec7..2c63eced5e 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/ClientSecretConfigurationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/ClientSecretConfigurationTests.swift @@ -104,7 +104,7 @@ class ClientSecretConfigurationTests: XCTestCase { mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { request in XCTAssertNotNil(request.secretHash) - return try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + return try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) try await plugin.confirmResetPassword( diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift index d385c628d8..acf3ec1c0d 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift @@ -6,143 +6,116 @@ // import XCTest -@testable import Amplify +@testable import func AmplifyTestCommon.XCTAssertThrowFatalError +import enum Amplify.JSONValue @testable import AWSCognitoAuthPlugin -class EscapeHatchTests: XCTestCase { - - let skipBrokenTests = true - - override func tearDown() async throws { - await Amplify.reset() - } +class EscapeHatchTests: XCTestCase { /// Test escape hatch with valid config for user pool and identity pool /// - /// - Given: Given valid config for user pool and identity pool + /// - Given: A AWSCognitoAuthPlugin configured with User Pool and Identity Pool /// - When: - /// - I configure auth with the given configuration and call getEscapeHatch + /// - I call getEscapeHatch /// - Then: - /// - I should get back user pool and identity pool clients + /// - I should get back both the User Pool and Identity Pool clients /// func testEscapeHatchWithUserPoolAndIdentityPool() throws { - if skipBrokenTests { - throw XCTSkip("TODO: fix this test") - } - - let plugin = AWSCognitoAuthPlugin() - try Amplify.add(plugin: plugin) - - let expectation = expectation(description: "Should get service") - let categoryConfig = AuthCategoryConfiguration(plugins: [ - "awsCognitoAuthPlugin": [ - "CredentialsProvider": ["CognitoIdentity": ["Default": - ["PoolId": "xx", - "Region": "us-east-1"] - ]], - "CognitoUserPool": ["Default": [ + let configuration: JSONValue = [ + "CredentialsProvider": [ + "CognitoIdentity": [ + "Default": [ + "PoolId": "xx", + "Region": "us-east-1" + ] + ] + ], + "CognitoUserPool": [ + "Default": [ "PoolId": "xx", "Region": "us-east-1", "AppClientId": "xx", - "AppClientSecret": "xx"]] + "AppClientSecret": "xx" + ] ] - ]) - let amplifyConfig = AmplifyConfiguration(auth: categoryConfig) - try Amplify.configure(amplifyConfig) - let internalPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin" - ) as! AWSCognitoAuthPlugin - let service = internalPlugin.getEscapeHatch() - switch service { - case .userPool: - XCTFail("Should return userPoolAndIdentityPool") - case .identityPool: - XCTFail("Should return userPoolAndIdentityPool") - case .userPoolAndIdentityPool: - expectation.fulfill() + ] + let plugin = AWSCognitoAuthPlugin() + try plugin.configure(using: configuration) + let escapeHatch = plugin.getEscapeHatch() + guard case .userPoolAndIdentityPool = escapeHatch else { + XCTFail("Expected .userPoolAndIdentityPool, got \(escapeHatch)") + return } - wait(for: [expectation], timeout: 1) } /// Test escape hatch with valid config for only identity pool /// - /// - Given: Given valid config for only identity pool + /// - Given: A AWSCognitoAuthPlugin configured with only Identity Pool /// - When: - /// - I configure auth with the given configuration and invoke getEscapeHatch + /// - I call getEscapeHatch /// - Then: - /// - I should get back only identity pool client + /// - I should get back only the Identity Pool client /// func testEscapeHatchWithOnlyIdentityPool() throws { - if skipBrokenTests { - throw XCTSkip("TODO: fix this test") - } - - let plugin = AWSCognitoAuthPlugin() - try Amplify.add(plugin: plugin) - - let categoryConfig = AuthCategoryConfiguration(plugins: [ - "awsCognitoAuthPlugin": [ - "CredentialsProvider": ["CognitoIdentity": ["Default": - ["PoolId": "cc", - "Region": "us-east-1"] - ]] + let configuration: JSONValue = [ + "CredentialsProvider": [ + "CognitoIdentity": [ + "Default": [ + "PoolId": "xx", + "Region": "us-east-1" + ] + ] ] - ]) - let amplifyConfig = AmplifyConfiguration(auth: categoryConfig) - try Amplify.configure(amplifyConfig) - let internalPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin" - ) as! AWSCognitoAuthPlugin - let service = internalPlugin.getEscapeHatch() - switch service { - case .userPool: - XCTFail("Should return identityPool") - case .userPoolAndIdentityPool: - XCTFail("Should return identityPool") - case .identityPool: - print("") + ] + let plugin = AWSCognitoAuthPlugin() + try plugin.configure(using: configuration) + let escapeHatch = plugin.getEscapeHatch() + guard case .identityPool = escapeHatch else { + XCTFail("Expected .identityPool, got \(escapeHatch)") + return } } /// Test escape hatch with valid config for only user pool /// - /// - Given: Given valid config for only user pool + /// - Given: A AWSCognitoAuthPlugin configured with only User Pool /// - When: - /// - I configure auth with the given configuration and invoke getEscapeHatch + /// - I call getEscapeHatch /// - Then: - /// - I should get the Cognito User pool client + /// - I should get only the User Pool client /// func testEscapeHatchWithOnlyUserPool() throws { - if skipBrokenTests { - throw XCTSkip("TODO: fix this test") - } - - let plugin = AWSCognitoAuthPlugin() - try Amplify.add(plugin: plugin) - - let categoryConfig = AuthCategoryConfiguration(plugins: [ - "awsCognitoAuthPlugin": [ - "CognitoUserPool": ["Default": [ + let configuration: JSONValue = [ + "CognitoUserPool": [ + "Default": [ "PoolId": "xx", "Region": "us-east-1", "AppClientId": "xx", - "AppClientSecret": "xx"]] + "AppClientSecret": "xx" + ] ] - ]) - let amplifyConfig = AmplifyConfiguration(auth: categoryConfig) - try Amplify.configure(amplifyConfig) - let internalPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin" - ) as! AWSCognitoAuthPlugin - let service = internalPlugin.getEscapeHatch() - switch service { - case .userPool: - break - case .identityPool: - XCTFail("Should return userPool") - case .userPoolAndIdentityPool: - XCTFail("Should return userPool") + ] + let plugin = AWSCognitoAuthPlugin() + try plugin.configure(using: configuration) + let escapeHatch = plugin.getEscapeHatch() + guard case .userPool = escapeHatch else { + XCTFail("Expected .userPool, got \(escapeHatch)") + return + } + } + + /// Test escape hatch without a valid configuration + /// + /// - Given: A AWSCognitoAuthPlugin plugin without being configured + /// - When: + /// - I call getEscapeHatch + /// - Then: + /// - A fatalError is thrown + /// + func testEscapeHatchWithoutConfiguration() throws { + let plugin = AWSCognitoAuthPlugin() + try XCTAssertThrowFatalError { + _ = plugin.getEscapeHatch() } } - } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/HubEventTests/AuthHubEventHandlerTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/HubEventTests/AuthHubEventHandlerTests.swift index 097407401d..6618988f58 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/HubEventTests/AuthHubEventHandlerTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/HubEventTests/AuthHubEventHandlerTests.swift @@ -48,7 +48,10 @@ class AuthHubEventHandlerTests: XCTestCase { XCTFail("Received failure with error \(error)") } - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } /// Test whether HubEvent emits a signOut event for mocked signOut operation @@ -74,7 +77,10 @@ class AuthHubEventHandlerTests: XCTestCase { } _ = await plugin.signOut(options: nil) - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } /// Test whether HubEvent emits a confirmSignedIn event for mocked signIn operation @@ -105,7 +111,10 @@ class AuthHubEventHandlerTests: XCTestCase { XCTFail("Received failure with error \(error)") } - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } /// Test whether HubEvent emits a deletedUser event for mocked delete user operation @@ -136,7 +145,10 @@ class AuthHubEventHandlerTests: XCTestCase { XCTFail("Received failure with error \(error)") } - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } /// Test whether HubEvent emits a sessionExpired event for mocked fetchSession operation with expired tokens @@ -161,7 +173,10 @@ class AuthHubEventHandlerTests: XCTestCase { } } _ = try await plugin.fetchAuthSession(options: AuthFetchSessionRequest.Options()) - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } #if os(iOS) || os(macOS) @@ -195,7 +210,7 @@ class AuthHubEventHandlerTests: XCTestCase { } catch { XCTFail("Received failure with error \(error)") } - wait(for: [hubEventExpectation], timeout: 10) + await fulfillment(of: [hubEventExpectation], timeout: 10) } /// Test whether HubEvent emits a mocked signedIn event for social provider signIn @@ -227,7 +242,7 @@ class AuthHubEventHandlerTests: XCTestCase { } catch { XCTFail("Received failure with error \(error)") } - wait(for: [hubEventExpectation], timeout: 10) + await fulfillment(of: [hubEventExpectation], timeout: 10) } #endif @@ -254,7 +269,10 @@ class AuthHubEventHandlerTests: XCTestCase { } _ = try await plugin.federateToIdentityPool(withProviderToken: "someToken", for: .facebook) - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } /// Test whether HubEvent emits a federationToIdentityPoolCleared event for mocked federated operation @@ -282,7 +300,10 @@ class AuthHubEventHandlerTests: XCTestCase { _ = try await plugin.federateToIdentityPool(withProviderToken: "something", for: .facebook) try await plugin.clearFederationToIdentityPool() - await waitForExpectations(timeout: networkTimeout) + await fulfillment( + of: [hubEventExpectation], + timeout: networkTimeout + ) } private func configurePluginForSignInEvent() { @@ -337,12 +358,12 @@ class AuthHubEventHandlerTests: XCTestCase { let mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) @@ -359,10 +380,10 @@ class AuthHubEventHandlerTests: XCTestCase { let mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) @@ -397,8 +418,9 @@ class AuthHubEventHandlerTests: XCTestCase { let mockIdentityProvider = MockIdentityProvider( mockInitiateAuthResponse: { _ in - throw try InitiateAuthOutputError.notAuthorizedException( - NotAuthorizedException.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw try await AWSCognitoIdentityProvider.NotAuthorizedException( + httpResponse: .init(body: .empty, statusCode: .ok) + ) }) configurePlugin(initialState: initialState, userPoolFactory: mockIdentityProvider) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift index fdb8862284..43db976492 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift @@ -26,8 +26,7 @@ class AWSAuthCognitoSessionTests: XCTestCase { let error = AuthError.unknown("", nil) let tokens = AWSCognitoUserPoolTokens(idToken: CognitoAuthTestHelper.buildToken(for: tokenData), accessToken: CognitoAuthTestHelper.buildToken(for: tokenData), - refreshToken: "refreshToken", - expiresIn: 121) + refreshToken: "refreshToken") let session = AWSAuthCognitoSession(isSignedIn: true, identityIdResult: .failure(error), @@ -53,8 +52,7 @@ class AWSAuthCognitoSessionTests: XCTestCase { let error = AuthError.unknown("", nil) let tokens = AWSCognitoUserPoolTokens(idToken: CognitoAuthTestHelper.buildToken(for: tokenData), accessToken: CognitoAuthTestHelper.buildToken(for: tokenData), - refreshToken: "refreshToken", - expiresIn: 121) + refreshToken: "refreshToken") let session = AWSAuthCognitoSession(isSignedIn: true, identityIdResult: .failure(error), @@ -65,4 +63,156 @@ class AWSAuthCognitoSessionTests: XCTestCase { XCTAssertFalse(cognitoTokens.doesExpire()) } + /// Given: An AWSAuthCognitoSession with a valid AWSCognitoUserPoolTokens + /// When: getUserSub is invoked + /// Then: The "sub" from the token data should be returned + func testGetUserSub_shouldReturnResult() { + let tokenData = [ + "sub": "1234567890", + "name": "John Doe", + "iat": "1516239022", + "exp": String(Date(timeIntervalSinceNow: 121).timeIntervalSince1970) + ] + + let error = AuthError.unknown("", nil) + let tokens = AWSCognitoUserPoolTokens( + idToken: CognitoAuthTestHelper.buildToken(for: tokenData), + accessToken: CognitoAuthTestHelper.buildToken(for: tokenData), + refreshToken: "refreshToken" + ) + + let session = AWSAuthCognitoSession( + isSignedIn: true, + identityIdResult: .failure(error), + awsCredentialsResult: .failure(error), + cognitoTokensResult: .success(tokens) + ) + + guard case .success(let userSub) = session.getUserSub() else { + XCTFail("Unable to retrieve userSub") + return + } + XCTAssertEqual(userSub, "1234567890") + } + + /// Given: An AWSAuthCognitoSession with a AWSCognitoUserPoolTokens that does not include a "sub" attribute + /// When: getUserSub is invoked + /// Then: A .failure with AuthError.unknown error is returned + func testGetUserSub_withoutSub_shouldReturnError() { + let tokenData = [ + "name": "John Doe", + "iat": "1516239022", + "exp": String(Date(timeIntervalSinceNow: 121).timeIntervalSince1970) + ] + + let error = AuthError.unknown("", nil) + let tokens = AWSCognitoUserPoolTokens( + idToken: CognitoAuthTestHelper.buildToken(for: tokenData), + accessToken: CognitoAuthTestHelper.buildToken(for: tokenData), + refreshToken: "refreshToken" + ) + + let session = AWSAuthCognitoSession( + isSignedIn: true, + identityIdResult: .failure(error), + awsCredentialsResult: .failure(error), + cognitoTokensResult: .success(tokens) + ) + + guard case .failure(let error) = session.getUserSub(), + case .unknown(let errorDescription, _) = error else { + XCTFail("Expected AuthError.unknown") + return + } + + XCTAssertEqual(errorDescription, "Could not retreive user sub from the fetched Cognito tokens.") + } + + /// Given: An AWSAuthCognitoSession that is signed out + /// When: getUserSub is invoked + /// Then: A .failure with AuthError.signedOut error is returned + func testGetUserSub_signedOut_shouldReturnError() { + let error = AuthError.signedOut("", "", nil) + let session = AWSAuthCognitoSession( + isSignedIn: false, + identityIdResult: .failure(error), + awsCredentialsResult: .failure(error), + cognitoTokensResult: .failure(error) + ) + + guard case .failure(let error) = session.getUserSub(), + case .signedOut(let errorDescription, let recoverySuggestion, _) = error else { + XCTFail("Expected AuthError.signedOut") + return + } + + XCTAssertEqual(errorDescription, AuthPluginErrorConstants.userSubSignOutError.errorDescription) + XCTAssertEqual(recoverySuggestion, AuthPluginErrorConstants.userSubSignOutError.recoverySuggestion) + } + + /// Given: An AWSAuthCognitoSession that has a service error + /// When: getUserSub is invoked + /// Then: A .failure with AuthError.signedOut error is returned + func testGetUserSub_serviceError_shouldReturnError() { + let serviceError = AuthError.service("Something went wrong", "Try again", nil) + let session = AWSAuthCognitoSession( + isSignedIn: false, + identityIdResult: .failure(serviceError), + awsCredentialsResult: .failure(serviceError), + cognitoTokensResult: .failure(serviceError) + ) + + guard case .failure(let error) = session.getUserSub() else { + XCTFail("Expected AuthError.signedOut") + return + } + + XCTAssertEqual(error, serviceError) + } + + /// Given: An AuthAWSCognitoCredentials and an AWSCognitoUserPoolTokens instance + /// When: Two AWSAuthCognitoSession are created from the same values + /// Then: The two AWSAuthCognitoSession are considered equal + func testSessionsAreEqual() { + let expiration = Date(timeIntervalSinceNow: 121) + let tokenData = [ + "sub": "1234567890", + "name": "John Doe", + "iat": "1516239022", + "exp": String(expiration.timeIntervalSince1970) + ] + + let credentials = AuthAWSCognitoCredentials( + accessKeyId: "accessKeyId", + secretAccessKey: "secretAccessKey", + sessionToken: "sessionToken", + expiration: expiration + ) + + let tokens = AWSCognitoUserPoolTokens( + idToken: CognitoAuthTestHelper.buildToken(for: tokenData), + accessToken: CognitoAuthTestHelper.buildToken(for: tokenData), + refreshToken: "refreshToken" + ) + + let session1 = AWSAuthCognitoSession( + isSignedIn: true, + identityIdResult: .success("identityId"), + awsCredentialsResult: .success(credentials), + cognitoTokensResult: .success(tokens) + ) + + let session2 = AWSAuthCognitoSession( + isSignedIn: true, + identityIdResult: .success("identityId"), + awsCredentialsResult: .success(credentials), + cognitoTokensResult: .success(tokens) + ) + + XCTAssertEqual(session1, session2) + XCTAssertEqual(session1.debugDictionary.count, session2.debugDictionary.count) + for key in session1.debugDictionary.keys where (key != "AWS Credentials" && key != "cognitoTokens") { + XCTAssertEqual(session1.debugDictionary[key] as? String, session2.debugDictionary[key] as? String) + } + } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift new file mode 100644 index 0000000000..3909827f36 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift @@ -0,0 +1,248 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#if os(iOS) || os(macOS) +import Amplify +import AuthenticationServices +@testable import AWSCognitoAuthPlugin +import XCTest + +class HostedUIASWebAuthenticationSessionTests: XCTestCase { + private var session: HostedUIASWebAuthenticationSession! + private var factory: ASWebAuthenticationSessionFactory! + + override func setUp() { + session = HostedUIASWebAuthenticationSession() + factory = ASWebAuthenticationSessionFactory() + session.authenticationSessionFactory = factory.createSession(url:callbackURLScheme:completionHandler:) + } + + override func tearDown() { + session = nil + factory = nil + } + + /// Given: A HostedUIASWebAuthenticationSession + /// When: showHostedUI is invoked and the session factory returns a URL with query items + /// Then: An array of query items should be returned + func testShowHostedUI_withUrlInCallback_withQueryItems_shouldReturnQueryItems() { + let expectation = expectation(description: "showHostedUI") + factory.mockedURL = createURL(queryItems: [.init(name: "name", value: "value")]) + + session.showHostedUI() { result in + do { + let queryItems = try result.get() + XCTAssertEqual(queryItems.count, 1) + XCTAssertEqual(queryItems.first?.name, "name") + XCTAssertEqual(queryItems.first?.value, "value") + } catch { + XCTFail("Expected .success(queryItems), got \(result)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: A HostedUIASWebAuthenticationSession + /// When: showHostedUI is invoked and the session factory returns a URL without query items + /// Then: An empty array should be returned + func testShowHostedUI_withUrlInCallback_withoutQueryItems_shouldReturnEmptyQueryItems() { + let expectation = expectation(description: "showHostedUI") + factory.mockedURL = createURL() + + session.showHostedUI() { result in + do { + let queryItems = try result.get() + XCTAssertTrue(queryItems.isEmpty) + } catch { + XCTFail("Expected .success(queryItems), got \(result)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: A HostedUIASWebAuthenticationSession + /// When: showHostedUI is invoked and the session factory returns a URL with query items representing errors + /// Then: A HostedUIError.serviceMessage should be returned + func testShowHostedUI_withUrlInCallback_withErrorInQueryItems_shouldReturnServiceMessageError() { + let expectation = expectation(description: "showHostedUI") + factory.mockedURL = createURL( + queryItems: [ + .init(name: "error", value: "Error."), + .init(name: "error_description", value: "Something went wrong") + ] + ) + + session.showHostedUI() { result in + do { + _ = try result.get() + XCTFail("Expected failure(.serviceMessage), got \(result)") + } catch let error as HostedUIError { + if case .serviceMessage(let message) = error { + XCTAssertEqual(message, "Error. Something went wrong") + } else { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") + } + } catch { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: A HostedUIASWebAuthenticationSession + /// When: showHostedUI is invoked and the session factory returns ASWebAuthenticationSessionErrors + /// Then: A HostedUIError corresponding to the error code should be returned + func testShowHostedUI_withASWebAuthenticationSessionErrors_shouldReturnRightError() { + let errorMap: [ASWebAuthenticationSessionError.Code: HostedUIError] = [ + .canceledLogin: .cancelled, + .presentationContextNotProvided: .invalidContext, + .presentationContextInvalid: .invalidContext + ] + + let errorCodes: [ASWebAuthenticationSessionError.Code] = [ + .canceledLogin, + .presentationContextNotProvided, + .presentationContextInvalid, + .init(rawValue: 500)! + ] + + for code in errorCodes { + factory.mockedError = ASWebAuthenticationSessionError(code) + let expectedError = errorMap[code] ?? .unknown + let expectation = expectation(description: "showHostedUI for error \(code)") + session.showHostedUI() { result in + do { + _ = try result.get() + XCTFail("Expected failure(.\(expectedError)), got \(result)") + } catch let error as HostedUIError { + XCTAssertEqual(error, expectedError) + } catch { + XCTFail("Expected HostedUIError.\(expectedError), got \(error)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + } + + /// Given: A HostedUIASWebAuthenticationSession + /// When: showHostedUI is invoked and the session factory returns an error + /// Then: A HostedUIError.unknown should be returned + func testShowHostedUI_withOtherError_shouldReturnUnknownError() { + factory.mockedError = CancellationError() + let expectation = expectation(description: "showHostedUI") + session.showHostedUI() { result in + do { + _ = try result.get() + XCTFail("Expected failure(.unknown), got \(result)") + } catch let error as HostedUIError { + XCTAssertEqual(error, .unknown) + } catch { + XCTFail("Expected HostedUIError.unknown, got \(error)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + private func createURL(queryItems: [URLQueryItem] = []) -> URL { + var components = URLComponents(string: "https://test.com")! + components.queryItems = queryItems + return components.url! + } +} + +class ASWebAuthenticationSessionFactory { + var mockedURL: URL? + var mockedError: Error? + + func createSession( + url URL: URL, + callbackURLScheme: String?, + completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler + ) -> ASWebAuthenticationSession { + let session = MockASWebAuthenticationSession( + url: URL, + callbackURLScheme: callbackURLScheme, + completionHandler: completionHandler + ) + session.mockedURL = mockedURL + session.mockedError = mockedError + return session + } +} + +class MockASWebAuthenticationSession: ASWebAuthenticationSession { + private var callback: ASWebAuthenticationSession.CompletionHandler + override init( + url URL: URL, + callbackURLScheme: String?, + completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler + ) { + self.callback = completionHandler + super.init( + url: URL, + callbackURLScheme: callbackURLScheme, + completionHandler: completionHandler + ) + } + + var mockedURL: URL? = nil + var mockedError: Error? = nil + override func start() -> Bool { + callback(mockedURL, mockedError) + return presentationContextProvider?.presentationAnchor(for: self) != nil + } +} + +extension HostedUIASWebAuthenticationSession { + func showHostedUI(callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) { + showHostedUI( + url: URL(string: "https://test.com")!, + callbackScheme: "https", + inPrivate: false, + presentationAnchor: nil, + callback: callback) + } +} +#else + +@testable import AWSCognitoAuthPlugin +import XCTest + +class HostedUIASWebAuthenticationSessionTests: XCTestCase { + func testShowHostedUI_shouldThrowServiceError() { + let expectation = expectation(description: "showHostedUI") + let session = HostedUIASWebAuthenticationSession() + session.showHostedUI( + url: URL(string: "https://test.com")!, + callbackScheme: "https", + inPrivate: false, + presentationAnchor: nil + ) { result in + do { + _ = try result.get() + XCTFail("Expected failure(.serviceMessage), got \(result)") + } catch let error as HostedUIError { + if case .serviceMessage(let message) = error { + XCTAssertEqual(message, "HostedUI is only available in iOS and macOS") + } else { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") + } + } catch { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } +} + +#endif diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift index 76f3c02779..e3612eaf69 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift @@ -264,7 +264,7 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { let getId: MockIdentity.MockGetIdResponse = { _ in if shouldThrowError { - throw GetIdOutputError.internalErrorException(.init()) + throw AWSCognitoIdentity.InternalErrorException() } else { return .init(identityId: "mockIdentityId") } @@ -501,7 +501,7 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { } catch { XCTFail("Received failure with error \(error)") } - wait(for: [cognitoAPIExpectation], timeout: apiTimeout) + await fulfillment(of: [cognitoAPIExpectation], timeout: apiTimeout) } /// Test fetchAuthSession when federated to identity pool with valid credentials @@ -671,7 +671,7 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { } catch { XCTFail("Received failure with error \(error)") } - wait(for: [cognitoAPIExpectation], timeout: apiTimeout) + await fulfillment(of: [cognitoAPIExpectation], timeout: apiTimeout) } /// Test federated to identity pool with developer provided identity Id @@ -787,7 +787,7 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { let getCredentials: MockIdentity.MockGetCredentialsResponse = { input in if shouldThrowError { - throw GetCredentialsForIdentityOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentity.InvalidParameterException() } else { return .init(credentials: credentials, identityId: mockIdentityId) } @@ -859,12 +859,12 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { let getId: MockIdentity.MockGetIdResponse = { _ in XCTFail("GetId should not be called") - throw GetIdOutputError.internalErrorException(.init()) + throw AWSCognitoIdentity.InternalErrorException() } let getCredentials: MockIdentity.MockGetCredentialsResponse = { _ in if shouldThrowError { - throw GetCredentialsForIdentityOutputError.internalErrorException(.init()) + throw AWSCognitoIdentity.InternalErrorException() } else { return .init(credentials: credentials, identityId: mockIdentityId) } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSignInSessionOperationTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSignInSessionOperationTests.swift index 51e0e00d86..e88cc1e53c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSignInSessionOperationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSignInSessionOperationTests.swift @@ -132,7 +132,7 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { XCTAssertNotNil(tokens?.idToken) XCTAssertNotNil(tokens?.refreshToken) - wait(for: [resultExpectation], timeout: apiTimeout) + await fulfillment(of: [resultExpectation], timeout: apiTimeout) } /// Test signedIn session with a user signed In to identityPool @@ -209,8 +209,9 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { AmplifyCredentials.testDataWithExpiredTokens)) let initAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in - throw try InitiateAuthOutputError.notAuthorizedException( - NotAuthorizedException.init(httpResponse: MockHttpResponse.ok)) + throw try await AWSCognitoIdentityProvider.NotAuthorizedException( + httpResponse: MockHttpResponse.ok + ) } let plugin = configurePluginWith(userPool: { MockIdentityProvider(mockInitiateAuthResponse: initAuth) }, initialState: initialState) @@ -265,8 +266,8 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { } let awsCredentials: MockIdentity.MockGetCredentialsResponse = { _ in - throw try GetCredentialsForIdentityOutputError.notAuthorizedException( - NotAuthorizedException.init(httpResponse: MockHttpResponse.ok) + throw try await AWSCognitoIdentityProvider.NotAuthorizedException( + httpResponse: MockHttpResponse.ok ) } @@ -354,7 +355,7 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { // XCTFail("Received failure with error \(error)") // } // } - // wait(for: [resultExpectation], timeout: apiTimeout) + // await fulfillment(of: [resultExpectation], timeout: apiTimeout) // } // // /// Test signedIn session with network error for identityId @@ -412,7 +413,7 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { // XCTFail("Received failure with error \(error)") // } // } - // wait(for: [resultExpectation], timeout: apiTimeout) + // await fulfillment(of: [resultExpectation], timeout: apiTimeout) // } // // /// Test signedIn session with network error for aws credentials @@ -470,7 +471,7 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { // XCTFail("Received failure with error \(error)") // } // } - // wait(for: [resultExpectation], timeout: apiTimeout) + // await fulfillment(of: [resultExpectation], timeout: apiTimeout) // } // /// Test signedIn session with invalid response for tokens @@ -655,10 +656,7 @@ class AWSAuthFetchSignInSessionOperationTests: BaseAuthorizationTests { AmplifyCredentials.testDataWithExpiredTokens)) let initAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in - let notAuthorized = InitiateAuthOutputError.notAuthorizedException(.init(message: "NotAuthorized")) - let serviceError = SdkError.service(notAuthorized, .init(body: .none, statusCode: .accepted)) - let clientError = ClientError.retryError(serviceError) - throw SdkError.client(clientError, nil) + throw AWSCognitoIdentityProvider.NotAuthorizedException(message: "NotAuthorized") } let plugin = configurePluginWith( diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AWSAuthSignOutTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AWSAuthSignOutTaskTests.swift index 1d84bbd17d..094e68c275 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AWSAuthSignOutTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AWSAuthSignOutTaskTests.swift @@ -41,7 +41,7 @@ class AWSAuthSignOutTaskTests: BasePluginTest { mockRevokeTokenResponse: { _ in return .testData }, mockGlobalSignOutResponse: { _ in - throw GlobalSignOutOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }) guard let result = await plugin.signOut(options: .init(globalSignOut: true)) as? AWSCognitoSignOutResult, case .partial(revokeTokenError: let revokeTokenError, @@ -59,7 +59,7 @@ class AWSAuthSignOutTaskTests: BasePluginTest { func testRevokeSignOutFailed() async { self.mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - throw RevokeTokenOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }, mockGlobalSignOutResponse: { _ in return .testData }) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AuthenticationProviderDeleteUserTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AuthenticationProviderDeleteUserTests.swift index b308b04586..2e0b2a06c2 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AuthenticationProviderDeleteUserTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AuthenticationProviderDeleteUserTests.swift @@ -13,18 +13,19 @@ import XCTest import AWSCognitoIdentityProvider import ClientRuntime import AwsCommonRuntimeKit +import AWSClientRuntime class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserSuccess() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) do { @@ -48,12 +49,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testSignOutFailureWhenDeleteUserIsSuccess() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - throw RevokeTokenOutputError.unsupportedTokenTypeException(.init()) + throw AWSCognitoIdentityProvider.UnsupportedTokenTypeException() }, mockGlobalSignOutResponse: { _ in - throw GlobalSignOutOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }, mockDeleteUserOutputResponse: { _ in - try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) do { @@ -75,16 +76,15 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { /// - Then: /// - I should get a .service error with .network as underlying error /// - func testOfflineDeleteUser() async { + func testOfflineDeleteUser() async throws { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - let crtError = ClientRuntime.ClientError.retryError(CommonRunTimeError.crtError(CRTError(code: 1059))) - throw SdkError.client(crtError) + throw CommonRunTimeError.crtError(CRTError(code: 1059)) } ) do { @@ -110,16 +110,15 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { /// - I should get a .service error with .network as underlying error for the first call /// - I should get a valid response for the second call /// - func testOfflineDeleteUserAndRetry() async { + func testOfflineDeleteUserAndRetry() async throws { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - let crtError = ClientRuntime.ClientError.retryError(CommonRunTimeError.crtError(CRTError(code: 1059))) - throw SdkError.client(crtError) + throw CommonRunTimeError.crtError(CRTError(code: 1059)) } ) do { @@ -135,12 +134,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) do { @@ -166,12 +165,17 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserInternalErrorException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .badRequest))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .badRequest), + message: nil, + requestID: nil, + typeName: nil + ) } ) @@ -199,12 +203,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithInvalidParameterException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() } ) @@ -233,12 +237,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithNotAuthorizedException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() } ) @@ -266,12 +270,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithPasswordResetRequiredException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() } ) @@ -300,12 +304,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithResourceNotFoundException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() } ) @@ -334,12 +338,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithTooManyRequestsException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() } ) @@ -368,12 +372,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithUserNotConfirmedException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() } ) @@ -403,12 +407,12 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { func testDeleteUserWithUserNotFoundException() async { mockIdentityProvider = MockIdentityProvider( mockRevokeTokenResponse: { _ in - try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockGlobalSignOutResponse: { _ in - try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockDeleteUserOutputResponse: { _ in - throw DeleteUserOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() } ) @@ -469,7 +473,7 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { // XCTFail("Received failure with error \(error)") // } // } -// wait(for: [resultExpectation], timeout: apiTimeout) +// await fulfillment(of: [resultExpectation], timeout: apiTimeout) // // let deleteUserResultExpectation = expectation(description: "Should receive a result") // _ = plugin.deleteUser { result in @@ -489,7 +493,7 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest { // } // } // } -// wait(for: [deleteUserResultExpectation], timeout: apiTimeout) +// await fulfillment(of: [deleteUserResultExpectation], timeout: apiTimeout) // } // // var window: UIWindow { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorConfirmResetPasswordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorConfirmResetPasswordTests.swift index c518c7ab0a..83ee5c4c3a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorConfirmResetPasswordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorConfirmResetPasswordTests.swift @@ -21,7 +21,7 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests super.setUp() mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -63,7 +63,7 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests func testSuccessfulConfirmResetPassword() async throws { mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) try await plugin.confirmResetPassword(for: "username", with: "newpassword", confirmationCode: "code", options: nil) @@ -81,7 +81,7 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) do { @@ -109,7 +109,7 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockConfirmForgotPasswordOutputResponse: { request in XCTAssertNoThrow(request.clientMetadata) XCTAssertEqual(request.clientMetadata?["key"], "value") - return try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + return try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) let pluginOptions = AWSAuthConfirmResetPasswordOptions(metadata: ["key": "value"]) @@ -131,7 +131,7 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - try ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) + try await ConfirmForgotPasswordOutputResponse(httpResponse: MockHttpResponse.ok) } ) do { @@ -158,7 +158,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.codeMismatchException(CodeMismatchException(message: "code mismatch")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "code mismatch" + ) } ) do { @@ -189,7 +191,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.expiredCodeException(ExpiredCodeException(message: "code expired")) + throw AWSCognitoIdentityProvider.ExpiredCodeException( + message: "code expired" + ) } ) do { @@ -219,7 +223,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.internalErrorException(InternalErrorException(message: "internal error")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error" + ) } ) do { @@ -245,10 +251,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests func testConfirmResetPasswordWithInvalidLambdaResponseException() async throws { mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw SdkError.service( - ConfirmForgotPasswordOutputError.invalidLambdaResponseException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InvalidLambdaResponseException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) do { @@ -280,7 +285,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) do { @@ -312,7 +319,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.invalidPasswordException(InvalidPasswordException(message: "invalid password")) + throw AWSCognitoIdentityProvider.InvalidPasswordException( + message: "invalid password" + ) } ) do { @@ -344,7 +353,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.limitExceededException(LimitExceededException(message: "limit exceeded")) + throw AWSCognitoIdentityProvider.LimitExceededException( + message: "limit exceeded" + ) } ) do { @@ -376,7 +387,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -404,7 +417,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -436,7 +451,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.tooManyFailedAttemptsException(TooManyFailedAttemptsException(message: "too many failed attempts")) + throw AWSCognitoIdentityProvider.TooManyFailedAttemptsException( + message: "too many failed attempts" + ) } ) do { @@ -468,7 +485,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -500,7 +519,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.unexpectedLambdaException(UnexpectedLambdaException(message: "unexpected lambda")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "unexpected lambda" + ) } ) do { @@ -532,7 +553,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.userLambdaValidationException(UserLambdaValidationException(message: "user lambda invalid")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "user lambda invalid" + ) } ) do { @@ -564,7 +587,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.userNotConfirmedException(UserNotConfirmedException(message: "user not confirmed")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed" + ) } ) do { @@ -596,7 +621,9 @@ class ClientBehaviorConfirmResetPasswordTests: AWSCognitoAuthClientBehaviorTests mockIdentityProvider = MockIdentityProvider( mockConfirmForgotPasswordOutputResponse: { _ in - throw ConfirmForgotPasswordOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorResetPasswordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorResetPasswordTests.swift index 59e440aa9e..0fe20404ae 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorResetPasswordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/ClientBehaviorResetPasswordTests.swift @@ -141,7 +141,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.codeDeliveryFailureException(CodeDeliveryFailureException(message: "Code delivery failure")) + throw AWSCognitoIdentityProvider.CodeDeliveryFailureException( + message: "Code delivery failure" + ) } ) do { @@ -171,10 +173,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw SdkError.service( - ForgotPasswordOutputError.internalErrorException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InternalErrorException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) do { @@ -200,7 +201,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { func testResetPasswordWithInvalidEmailRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.invalidEmailRoleAccessPolicyException(InvalidEmailRoleAccessPolicyException(message: "invalid email role")) + throw AWSCognitoIdentityProvider.InvalidEmailRoleAccessPolicyException( + message: "invalid email role" + ) } ) do { @@ -230,7 +233,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { func testResetPasswordWithInvalidLambdaResponseException() async throws { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.invalidLambdaResponseException(InvalidLambdaResponseException(message: "Invalid lambda response")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException( + message: "Invalid lambda response" + ) } ) do { @@ -262,7 +267,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) do { @@ -292,7 +299,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { func testResetPasswordWithInvalidSmsRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.invalidSmsRoleAccessPolicyException(InvalidSmsRoleAccessPolicyException(message: "invalid sms role")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "invalid sms role" + ) } ) do { @@ -322,7 +331,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { func testResetPasswordWithInvalidSmsRoleTrustRelationshipException() async throws { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.invalidSmsRoleTrustRelationshipException(InvalidSmsRoleTrustRelationshipException(message: "invalid sms role trust relationship")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException( + message: "invalid sms role trust relationship" + ) } ) do { @@ -354,7 +365,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.limitExceededException(LimitExceededException(message: "limit exceeded")) + throw AWSCognitoIdentityProvider.LimitExceededException( + message: "limit exceeded" + ) } ) do { @@ -386,7 +399,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -414,7 +429,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -446,7 +463,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -478,7 +497,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.unexpectedLambdaException(UnexpectedLambdaException(message: "unexpected lambda")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "unexpected lambda" + ) } ) do { @@ -510,7 +531,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.userLambdaValidationException(UserLambdaValidationException(message: "user lambda validation exception")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "user lambda validation exception" + ) } ) do { @@ -567,7 +590,7 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { } } - wait(for: [resultExpectation], timeout: networkTimeout) + await fulfillment(of: [resultExpectation], timeout: networkTimeout) } */ @@ -585,7 +608,9 @@ class ClientBehaviorResetPasswordTests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockForgotPasswordOutputResponse: { _ in - throw ForgotPasswordOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/FetchMFAPreferenceTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/FetchMFAPreferenceTaskTests.swift index 11a7b322a9..e713f439d8 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/FetchMFAPreferenceTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/FetchMFAPreferenceTaskTests.swift @@ -11,6 +11,7 @@ import XCTest import Amplify @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider +import AWSClientRuntime // swiftlint:disable type_body_length // swiftlint:disable file_length @@ -186,7 +187,13 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) do { @@ -211,7 +218,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) do { @@ -240,7 +247,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.notAuthorizedException(.init(message: "message")) + throw AWSCognitoIdentityProvider.NotAuthorizedException(message: "message") }) do { @@ -267,7 +274,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithPasswordResetRequiredException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) do { @@ -298,7 +305,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) do { @@ -329,7 +336,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) do { @@ -360,7 +367,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithUserNotConfirmedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) do { _ = try await plugin.fetchMFAPreference() @@ -390,7 +397,7 @@ class FetchMFAPreferenceTaskTests: BasePluginTest { func testFetchMFAPreferenceWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) do { _ = try await plugin.fetchMFAPreference() diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/SetUpTOTPTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/SetUpTOTPTaskTests.swift index 67083280a7..7d21c937a3 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/SetUpTOTPTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/SetUpTOTPTaskTests.swift @@ -11,6 +11,7 @@ import XCTest import Amplify @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider +import AWSClientRuntime // swiftlint:disable type_body_length // swiftlint:disable file_length @@ -54,8 +55,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .concurrentModificationException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.ConcurrentModificationException( + message: "Exception" + ) }) do { @@ -85,8 +87,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .forbiddenException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.ForbiddenException( + message: "Exception" + ) }) do { @@ -116,8 +119,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .internalErrorException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) }) do { @@ -147,8 +151,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .invalidParameterException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -180,8 +185,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .notAuthorizedException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -209,8 +215,9 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .softwareTokenMFANotFoundException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -221,7 +228,7 @@ class SetUpTOTPTaskTests: BasePluginTest { XCTFail("Should produce service error instead of \(error)") return } - guard case .mfaMethodNotFound = (underlyingError as? AWSCognitoAuthError) else { + guard case .softwareTokenMFANotEnabled = (underlyingError as? AWSCognitoAuthError) else { XCTFail("Underlying error should be softwareTokenMFANotEnabled \(error)") return } @@ -240,8 +247,9 @@ class SetUpTOTPTaskTests: BasePluginTest { func testSetUpTOTPInWithResourceNotFoundException() async { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .resourceNotFoundException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "Exception" + ) }) do { @@ -272,8 +280,13 @@ class SetUpTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockAssociateSoftwareTokenResponse: { request in - throw AssociateSoftwareTokenOutputError - .unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/UpdateMFAPreferenceTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/UpdateMFAPreferenceTaskTests.swift index 29c9b0b41b..a4c6fa9d27 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/UpdateMFAPreferenceTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/UpdateMFAPreferenceTaskTests.swift @@ -11,6 +11,7 @@ import XCTest import Amplify @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider +import AWSClientRuntime // swiftlint:disable type_body_length // swiftlint:disable file_length @@ -80,7 +81,13 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) } ) @@ -113,7 +120,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() } ) @@ -150,7 +157,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.notAuthorizedException(.init(message: "message")) + throw AWSCognitoIdentityProvider.NotAuthorizedException(message: "message") } ) @@ -185,7 +192,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() } ) @@ -224,7 +231,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() } ) @@ -263,7 +270,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.forbiddenException(.init()) + throw AWSCognitoIdentityProvider.ForbiddenException() } ) @@ -298,7 +305,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() } ) do { @@ -336,7 +343,7 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest { ) }, mockSetUserMFAPreferenceResponse: { _ in - throw SetUserMFAPreferenceOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/VerifyTOTPSetupTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/VerifyTOTPSetupTaskTests.swift index 125df7031e..0cd03bf4e9 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/VerifyTOTPSetupTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/VerifyTOTPSetupTaskTests.swift @@ -11,6 +11,7 @@ import XCTest import Amplify @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider +import AWSClientRuntime // swiftlint:disable type_body_length // swiftlint:disable file_length @@ -57,8 +58,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .forbiddenException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.ForbiddenException( + message: "Exception" + ) }) do { @@ -88,8 +90,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .internalErrorException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) }) do { @@ -119,8 +122,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .invalidParameterException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -152,8 +156,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .notAuthorizedException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -175,14 +180,15 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { /// - When: /// - I invoke verifyTOTPSetup /// - Then: - /// - I should get a .service error with .mfaMethodNotFound as underlyingError + /// - I should get a .service error with .softwareTokenMFANotEnabled as underlyingError /// func testVerifyTOTPSetupWithSoftwareTokenMFANotFoundException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .softwareTokenMFANotFoundException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -193,7 +199,7 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { XCTFail("Should produce service error instead of \(error)") return } - guard case .mfaMethodNotFound = (underlyingError as? AWSCognitoAuthError) else { + guard case .softwareTokenMFANotEnabled = (underlyingError as? AWSCognitoAuthError) else { XCTFail("Underlying error should be softwareTokenMFANotEnabled \(error)") return } @@ -212,8 +218,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithResourceNotFoundException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .resourceNotFoundException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "Exception" + ) }) do { @@ -244,8 +251,13 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) do { @@ -271,8 +283,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .codeMismatchException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -302,8 +315,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithEnableSoftwareTokenMFAException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .enableSoftwareTokenMFAException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.EnableSoftwareTokenMFAException( + message: "Exception" + ) }) do { @@ -333,8 +347,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithPasswordResetRequiredException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .passwordResetRequiredException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Exception" + ) }) do { @@ -364,8 +379,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithTooManyRequestsException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .tooManyRequestsException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Exception" + ) }) do { @@ -395,8 +411,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithUserNotFoundException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .userNotFoundException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Exception" + ) }) do { @@ -426,8 +443,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithUserNotConfirmedException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .userNotConfirmedException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Exception" + ) }) do { @@ -457,8 +475,9 @@ class VerifyTOTPSetupTaskTests: BasePluginTest { func testVerifyTOTPSetupInWithInvalidUserPoolConfigurationException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError - .invalidUserPoolConfigurationException(.init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + message: "Exception" + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift index 9e21edf0bc..da1a27739a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift @@ -164,8 +164,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.aliasExistsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.AliasExistsException( + message: "Exception" + ) }) do { @@ -176,10 +177,7 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { XCTFail("Should produce service error instead of \(error)") return } - guard case .aliasExists = (underlyingError as? AWSCognitoAuthError) else { - XCTFail("Underlying error should be aliasExists \(error)") - return - } + XCTAssertEqual(underlyingError as? AWSCognitoAuthError, .aliasExists) } } @@ -195,8 +193,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { func testConfirmSignInWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -217,8 +216,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { func testConfirmSignInRetryWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -261,8 +261,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.expiredCodeException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.ExpiredCodeException( + message: "Exception" + ) }) do { @@ -292,8 +293,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.internalErrorException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) }) do { @@ -319,8 +321,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { func testConfirmSignInWithInvalidLambdaResponseException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidLambdaResponseException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException( + message: "Exception" + ) }) do { @@ -352,8 +355,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidParameterException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -385,8 +389,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidPasswordException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidPasswordException( + message: "Exception" + ) }) do { @@ -416,8 +421,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { func testConfirmSignInWithinvalidSmsRoleAccessPolicyException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleAccessPolicyException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "Exception" + ) }) do { @@ -447,8 +453,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { func testConfirmSignInWithInvalidSmsRoleTrustRelationshipException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleTrustRelationshipException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException( + message: "Exception" + ) }) do { @@ -526,8 +533,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.mFAMethodNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.MFAMethodNotFoundException( + message: "Exception" + ) }) do { @@ -559,8 +567,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.notAuthorizedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -588,8 +597,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.passwordResetRequiredException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Exception" + ) }) do { @@ -618,8 +628,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.softwareTokenMFANotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -651,8 +662,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.tooManyRequestsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Exception" + ) }) do { @@ -684,8 +696,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.unexpectedLambdaException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "Exception" + ) }) do { @@ -717,8 +730,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userLambdaValidationException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "Exception" + ) }) do { @@ -750,8 +764,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotConfirmedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Exception" + ) }) do { @@ -779,8 +794,9 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Exception" + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthMigrationSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthMigrationSignInTaskTests.swift index e1116dbbb9..de2caa7054 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthMigrationSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthMigrationSignInTaskTests.swift @@ -84,7 +84,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { XCTFail("Error should not be returned \(error)") } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationInternalError() async throws { @@ -92,7 +92,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.internalErrorException(.init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.InternalErrorException(message: "Error Occurred") } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -107,7 +107,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationInvalidLambda() async throws { @@ -115,7 +115,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.invalidLambdaResponseException(.init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException(message: "Error Occurred") } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -134,7 +134,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationParameterException() async throws { @@ -142,7 +142,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.invalidParameterException(.init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.InvalidParameterException(message: "Error Occurred") } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -161,7 +161,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationSMSRoleAccessException() async throws { @@ -169,8 +169,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.invalidSmsRoleAccessPolicyException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -189,7 +190,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationUserPoolConfiguration() async throws { @@ -197,8 +198,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.invalidUserPoolConfigurationException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -213,7 +215,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationNotAuthorized() async throws { @@ -221,8 +223,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.notAuthorizedException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -237,7 +240,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperatioResetPassword() async throws { @@ -245,8 +248,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.passwordResetRequiredException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -260,7 +264,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { } catch { XCTFail("Should not produce a error result: \(error)") } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationResourceNotFound() async throws { @@ -268,8 +272,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.resourceNotFoundException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -287,7 +292,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationTooManyRequest() async throws { @@ -295,8 +300,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.tooManyRequestsException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -314,7 +320,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationUnexpectedLambda() async throws { @@ -322,8 +328,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.unexpectedLambdaException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -341,7 +348,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationUserLambdaValidation() async throws { @@ -349,8 +356,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.userLambdaValidationException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -368,7 +376,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationUserNotConfirmed() async throws { @@ -376,8 +384,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.userNotConfirmedException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -391,7 +400,7 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { } catch { XCTFail("Should not produce an error result - \(error)") } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } func testSignInOperationUserNotFound() async throws { @@ -399,8 +408,9 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { let initiateAuth: MockIdentityProvider.MockInitiateAuthResponse = { _ in initiateAuthExpectation.fulfill() - throw InitiateAuthOutputError.userNotFoundException( - .init(message: "Error Occurred")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Error Occurred" + ) } mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: initiateAuth) @@ -418,6 +428,6 @@ class AWSAuthMigrationSignInTaskTests: XCTestCase { return } } - wait(for: [initiateAuthExpectation], timeout: networkTimeout) + await fulfillment(of: [initiateAuthExpectation], timeout: networkTimeout) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInPluginTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInPluginTests.swift index 481a7324ef..7302e89996 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInPluginTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInPluginTests.swift @@ -783,7 +783,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithInternalErrorException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }) let options = AuthSignInRequest.Options() @@ -811,7 +811,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithInvalidLambdaResponseException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.invalidLambdaResponseException(.init()) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException() }) let options = AuthSignInRequest.Options() @@ -840,7 +840,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithInvalidParameterException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) let options = AuthSignInRequest.Options() @@ -869,7 +869,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithInvalidUserPoolConfigurationException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.invalidUserPoolConfigurationException(.init()) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException() }) let options = AuthSignInRequest.Options() @@ -897,7 +897,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithNotAuthorizedException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() }) let options = AuthSignInRequest.Options() @@ -925,7 +925,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithPasswordResetRequiredException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) let options = AuthSignInRequest.Options() @@ -954,10 +954,12 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithPasswordResetRequiredException2() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - let serviceError = SdkError - .service(.passwordResetRequiredException(PasswordResetRequiredException()), - .init(body: .none, statusCode: .badRequest)) - throw SdkError.client(.retryError(serviceError), nil) + throw try await AWSCognitoIdentityProvider.PasswordResetRequiredException( + httpResponse: .init(body: .none, statusCode: .badRequest), + decoder: nil, + message: nil, + requestID: nil + ) }) let options = AuthSignInRequest.Options() @@ -986,7 +988,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithResourceNotFoundException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) let options = AuthSignInRequest.Options() @@ -1015,7 +1017,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithTooManyRequestsException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) let options = AuthSignInRequest.Options() @@ -1044,7 +1046,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithUnexpectedLambdaException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.unexpectedLambdaException(.init()) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException() }) let options = AuthSignInRequest.Options() @@ -1073,7 +1075,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithUserLambdaValidationException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.userLambdaValidationException(.init()) + throw AWSCognitoIdentityProvider.UserLambdaValidationException() }) let options = AuthSignInRequest.Options() @@ -1101,7 +1103,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { /// func testSignInWithUserNotConfirmedException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) let options = AuthSignInRequest.Options() @@ -1130,10 +1132,9 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithUserNotConfirmedException2() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - let serviceError = SdkError - .service(.userNotConfirmedException(UserNotConfirmedException()), - .init(body: .none, statusCode: .badRequest)) - throw SdkError.client(.retryError(serviceError), nil) + throw try await AWSCognitoIdentityProvider.UserNotConfirmedException( + httpResponse: .init(body: .none, statusCode: .badRequest) + ) }) let options = AuthSignInRequest.Options() @@ -1162,7 +1163,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { func testSignInWithUserNotFoundException() async { self.mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in - throw InitiateAuthOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) let options = AuthSignInRequest.Options() @@ -1199,7 +1200,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { challengeParameters: InitiateAuthOutputResponse.validChalengeParams, session: "someSession") }, mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.aliasExistsException(.init()) + throw AWSCognitoIdentityProvider.AliasExistsException() }) let options = AuthSignInRequest.Options() @@ -1234,7 +1235,7 @@ class AWSAuthSignInPluginTests: BasePluginTest { challengeParameters: InitiateAuthOutputResponse.validChalengeParams, session: "someSession") }, mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidPasswordException(.init()) + throw AWSCognitoIdentityProvider.InvalidPasswordException() }) let options = AuthSignInRequest.Options() @@ -1330,7 +1331,9 @@ class AWSAuthSignInPluginTests: BasePluginTest { self.mockIdentity = MockIdentity( mockGetIdResponse: { _ in - throw GetIdOutputError.invalidParameterException(.init(message: "Invalid parameter passed")) + throw AWSCognitoIdentity.InvalidParameterException( + message: "Invalid parameter passed" + ) }, mockGetCredentialsResponse: getCredentials) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInTOTPTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInTOTPTaskTests.swift index 8c8b3bce14..98849dce07 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInTOTPTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInTOTPTaskTests.swift @@ -170,8 +170,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.aliasExistsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.AliasExistsException( + message: "Exception" + ) }) do { @@ -201,8 +202,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { func testConfirmSignInWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -232,8 +234,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { func testConfirmSignInRetryWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -276,8 +279,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.expiredCodeException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.ExpiredCodeException( + message: "Exception" + ) }) do { @@ -307,8 +311,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.internalErrorException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) }) do { @@ -334,8 +339,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { func testConfirmSignInWithInvalidLambdaResponseException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidLambdaResponseException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException( + message: "Exception" + ) }) do { @@ -367,8 +373,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidParameterException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -400,8 +407,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidPasswordException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidPasswordException( + message: "Exception" + ) }) do { @@ -431,8 +439,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { func testConfirmSignInWithinvalidSmsRoleAccessPolicyException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleAccessPolicyException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "Exception" + ) }) do { @@ -462,8 +471,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { func testConfirmSignInWithInvalidSmsRoleTrustRelationshipException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleTrustRelationshipException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException( + message: "Exception" + ) }) do { @@ -541,8 +551,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.mFAMethodNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.MFAMethodNotFoundException( + message: "Exception" + ) }) do { @@ -574,8 +585,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.notAuthorizedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -603,8 +615,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.passwordResetRequiredException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Exception" + ) }) do { @@ -633,8 +646,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.softwareTokenMFANotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -666,8 +680,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.tooManyRequestsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Exception" + ) }) do { @@ -699,8 +714,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.unexpectedLambdaException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "Exception" + ) }) do { @@ -732,8 +748,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userLambdaValidationException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "Exception" + ) }) do { @@ -765,8 +782,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotConfirmedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Exception" + ) }) do { @@ -794,8 +812,9 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Exception" + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithMFASelectionTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithMFASelectionTaskTests.swift index b7c442f3a9..da3e22fa6c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithMFASelectionTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithMFASelectionTaskTests.swift @@ -200,8 +200,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.aliasExistsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.AliasExistsException( + message: "Exception" + ) }) do { @@ -231,8 +232,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { func testConfirmSignInWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -264,8 +266,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { func testConfirmSignInRetryWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) }) do { @@ -312,8 +315,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.expiredCodeException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.ExpiredCodeException( + message: "Exception" + ) }) do { @@ -343,8 +347,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.internalErrorException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) }) do { @@ -370,8 +375,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { func testConfirmSignInWithInvalidLambdaResponseException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidLambdaResponseException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException( + message: "Exception" + ) }) do { @@ -403,8 +409,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidParameterException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -436,8 +443,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidPasswordException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidPasswordException( + message: "Exception" + ) }) do { @@ -467,8 +475,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { func testConfirmSignInWithinvalidSmsRoleAccessPolicyException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleAccessPolicyException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "Exception" + ) }) do { @@ -498,8 +507,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { func testConfirmSignInWithInvalidSmsRoleTrustRelationshipException() async { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.invalidSmsRoleTrustRelationshipException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException( + message: "Exception" + ) }) do { @@ -577,8 +587,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.mFAMethodNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.MFAMethodNotFoundException( + message: "Exception" + ) }) do { @@ -610,8 +621,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.notAuthorizedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -639,8 +651,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.passwordResetRequiredException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Exception" + ) }) do { @@ -669,8 +682,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.softwareTokenMFANotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -702,8 +716,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.tooManyRequestsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Exception" + ) }) do { @@ -735,8 +750,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.unexpectedLambdaException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "Exception" + ) }) do { @@ -768,8 +784,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userLambdaValidationException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "Exception" + ) }) do { @@ -801,8 +818,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotConfirmedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Exception" + ) }) do { @@ -830,8 +848,9 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockRespondToAuthChallengeResponse: { _ in - throw RespondToAuthChallengeOutputError.userNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Exception" + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithSetUpMFATaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithSetUpMFATaskTests.swift index 67dfc8ef6c..b0ab9ad69a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithSetUpMFATaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/ConfirmSignInWithSetUpMFATaskTests.swift @@ -152,8 +152,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) } ) @@ -186,8 +187,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { func testConfirmSignInRetryWithCodeMismatchException() async { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.codeMismatchException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.CodeMismatchException( + message: "Exception" + ) } ) @@ -237,8 +239,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.internalErrorException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "Exception" + ) } ) @@ -267,8 +270,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.invalidParameterException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "Exception" + ) }) do { @@ -346,8 +350,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.notAuthorizedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "Exception" + ) }) do { @@ -375,8 +380,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.passwordResetRequiredException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "Exception" + ) }) do { @@ -405,8 +411,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.softwareTokenMFANotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException( + message: "Exception" + ) }) do { @@ -417,7 +424,7 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { XCTFail("Should produce service error instead of \(error)") return } - guard case .mfaMethodNotFound = (underlyingError as? AWSCognitoAuthError) else { + guard case .softwareTokenMFANotEnabled = (underlyingError as? AWSCognitoAuthError) else { XCTFail("Underlying error should be softwareTokenMFANotEnabled \(error)") return } @@ -438,8 +445,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.tooManyRequestsException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "Exception" + ) }) do { @@ -471,8 +479,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.userNotConfirmedException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "Exception" + ) }) do { @@ -500,8 +509,9 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockVerifySoftwareTokenResponse: { request in - throw VerifySoftwareTokenOutputError.userNotFoundException( - .init(message: "Exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "Exception" + ) }) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSetUpTOTPTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSetUpTOTPTests.swift index bcf1cb0e02..6a6e10ab93 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSetUpTOTPTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSetUpTOTPTests.swift @@ -10,7 +10,7 @@ import AWSCognitoIdentity @testable import Amplify @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider -import ClientRuntime +import AWSClientRuntime class SignInSetUpTOTPTests: BasePluginTest { @@ -254,7 +254,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }) let options = AuthSignInRequest.Options() @@ -292,7 +292,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) let options = AuthSignInRequest.Options() @@ -331,7 +331,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() }) let options = AuthSignInRequest.Options() @@ -369,7 +369,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) let options = AuthSignInRequest.Options() @@ -408,7 +408,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.concurrentModificationException(.init()) + throw AWSCognitoIdentityProvider.ConcurrentModificationException() }) let options = AuthSignInRequest.Options() @@ -446,7 +446,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.forbiddenException(.init()) + throw AWSCognitoIdentityProvider.ForbiddenException() }) let options = AuthSignInRequest.Options() @@ -484,7 +484,7 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.softwareTokenMFANotFoundException(.init()) + throw AWSCognitoIdentityProvider.SoftwareTokenMFANotFoundException() }) let options = AuthSignInRequest.Options() @@ -493,7 +493,7 @@ class SignInSetUpTOTPTests: BasePluginTest { XCTFail("Should not produce result - \(result)") } catch { guard case AuthError.service(_, _, let underlyingError) = error, - case .mfaMethodNotFound = (underlyingError as? AWSCognitoAuthError) else { + case .softwareTokenMFANotEnabled = (underlyingError as? AWSCognitoAuthError) else { XCTFail("Should produce resourceNotFound error but instead produced \(error)") return } @@ -523,7 +523,13 @@ class SignInSetUpTOTPTests: BasePluginTest { challenge: .mfaSetup, challengeParameters: ["MFAS_CAN_SETUP": "[\"SMS_MFA\",\"SOFTWARE_TOKEN_MFA\"]"]) }, mockAssociateSoftwareTokenResponse: { input in - throw AssociateSoftwareTokenOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .accepted))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) let options = AuthSignInRequest.Options() diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpAPITests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpAPITests.swift index 2b65918975..fe5c8124c2 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpAPITests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpAPITests.swift @@ -11,6 +11,7 @@ import AWSCognitoIdentity @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider import ClientRuntime +import AWSClientRuntime class AWSAuthConfirmSignUpAPITests: BasePluginTest { @@ -114,18 +115,18 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest { func testSignUpServiceError() async { - let errorsToTest: [(confirmSignUpOutputError: ConfirmSignUpOutputError, cognitoError: AWSCognitoAuthError)] = [ - (.aliasExistsException(.init()), .aliasExists), - (.codeMismatchException(.init()), .codeMismatch), - (.invalidLambdaResponseException(.init()), .lambda), - (.invalidParameterException(.init()), .invalidParameter), - (.resourceNotFoundException(.init()), .resourceNotFound), - (.tooManyRequestsException(.init()), .requestLimitExceeded), - (.unexpectedLambdaException(.init()), .lambda), - (.userLambdaValidationException(.init()), .lambda), - (.userNotFoundException(.init()), .userNotFound), - (.limitExceededException(.init()), .limitExceeded), - (.tooManyFailedAttemptsException(.init()), .requestLimitExceeded), + let errorsToTest: [(confirmSignUpOutputError: Error, cognitoError: AWSCognitoAuthError)] = [ + (AWSCognitoIdentityProvider.AliasExistsException(), .aliasExists), + (AWSCognitoIdentityProvider.CodeMismatchException(), .codeMismatch), + (AWSCognitoIdentityProvider.InvalidLambdaResponseException(), .lambda), + (AWSCognitoIdentityProvider.InvalidParameterException(), .invalidParameter), + (AWSCognitoIdentityProvider.ResourceNotFoundException(), .resourceNotFound), + (AWSCognitoIdentityProvider.TooManyRequestsException(), .requestLimitExceeded), + (AWSCognitoIdentityProvider.UnexpectedLambdaException(), .lambda), + (AWSCognitoIdentityProvider.UserLambdaValidationException(), .lambda), + (AWSCognitoIdentityProvider.UserNotFoundException(), .userNotFound), + (AWSCognitoIdentityProvider.LimitExceededException(), .limitExceeded), + (AWSCognitoIdentityProvider.TooManyFailedAttemptsException(), .failedAttemptsLimitExceeded) ] for errorToTest in errorsToTest { @@ -139,7 +140,7 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockConfirmSignUpResponse: { _ in - throw ConfirmSignUpOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() } ) @@ -171,10 +172,9 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockConfirmSignUpResponse: { _ in - throw SdkError.service( - ConfirmSignUpOutputError.internalErrorException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InternalErrorException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) @@ -202,8 +202,12 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockConfirmSignUpResponse: { _ in - throw ConfirmSignUpOutputError.unknown( - .init(httpResponse: .init(body: .empty, statusCode: .accepted))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError.init( + httpResponse: .init(body: .empty, statusCode: .accepted), + message: nil, + requestID: nil, + typeName: nil + ) } ) @@ -228,7 +232,7 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest { } func validateConfirmSignUpServiceErrors( - confirmSignUpOutputError: ConfirmSignUpOutputError, + confirmSignUpOutputError: Error, expectedCognitoError: AWSCognitoAuthError) async { self.mockIdentityProvider = MockIdentityProvider( mockConfirmSignUpResponse: { _ in diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpTaskTests.swift index 9236e24c98..62a488fce2 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthConfirmSignUpTaskTests.swift @@ -16,7 +16,7 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import AWSPluginsTestCommon import ClientRuntime - +import AWSClientRuntime import AWSCognitoIdentityProvider class AWSAuthConfirmSignUpTaskTests: XCTestCase { @@ -33,7 +33,7 @@ class AWSAuthConfirmSignUpTaskTests: XCTestCase { let functionExpectation = expectation(description: "API call should be invoked") let confirmSignUp: MockIdentityProvider.MockConfirmSignUpResponse = { _ in functionExpectation.fulfill() - return try .init(httpResponse: MockHttpResponse.ok) + return try await .init(httpResponse: MockHttpResponse.ok) } let authEnvironment = Defaults.makeDefaultAuthEnvironment( @@ -52,7 +52,12 @@ class AWSAuthConfirmSignUpTaskTests: XCTestCase { let functionExpectation = expectation(description: "API call should be invoked") let confirmSignUp: MockIdentityProvider.MockConfirmSignUpResponse = { _ in functionExpectation.fulfill() - throw try ConfirmSignUpOutputError(httpResponse: MockHttpResponse.ok) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: MockHttpResponse.ok, + message: nil, + requestID: nil, + typeName: nil + ) } let authEnvironment = Defaults.makeDefaultAuthEnvironment( @@ -68,6 +73,6 @@ class AWSAuthConfirmSignUpTaskTests: XCTestCase { XCTFail("Should not produce success response") } catch { } - wait(for: [functionExpectation], timeout: 1) + await fulfillment(of: [functionExpectation], timeout: 1) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthResendSignUpCodeAPITests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthResendSignUpCodeAPITests.swift index d8cf555e19..80858c4bf8 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthResendSignUpCodeAPITests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthResendSignUpCodeAPITests.swift @@ -148,10 +148,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw SdkError.service( - ResendConfirmationCodeOutputError.codeDeliveryFailureException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.CodeDeliveryFailureException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) do { @@ -180,7 +179,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.internalErrorException(InternalErrorException(message: "internal error")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error" + ) } ) do { @@ -206,7 +207,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithInvalidEmailRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.invalidEmailRoleAccessPolicyException(InvalidEmailRoleAccessPolicyException(message: "Invalid email role access policy")) + throw AWSCognitoIdentityProvider.InvalidEmailRoleAccessPolicyException( + message: "Invalid email role access policy" + ) } ) do { @@ -236,7 +239,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithinvalidSmsRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.invalidSmsRoleAccessPolicyException(InvalidSmsRoleAccessPolicyException(message: "Invalid sms role access policy")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException( + message: "Invalid sms role access policy" + ) } ) do { @@ -266,7 +271,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithInvalidSmsRoleTrustRelationshipException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.invalidSmsRoleTrustRelationshipException(InvalidSmsRoleTrustRelationshipException(message: "Invalid sms role trust relationship")) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException( + message: "Invalid sms role trust relationship" + ) } ) do { @@ -296,7 +303,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithInvalidLambdaResponseException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.invalidLambdaResponseException(InvalidLambdaResponseException(message: "Invalid lambda response")) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException( + message: "Invalid lambda response" + ) } ) do { @@ -327,7 +336,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) do { @@ -358,7 +369,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithLimitExceededException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.limitExceededException(LimitExceededException(message: "limit exceeded")) + throw AWSCognitoIdentityProvider.LimitExceededException( + message: "limit exceeded" + ) } ) do { @@ -389,7 +402,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -416,7 +431,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -447,7 +464,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -478,7 +497,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithUnexpectedLambdaException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.unexpectedLambdaException(UnexpectedLambdaException(message: "unexpected lambda")) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException( + message: "unexpected lambda" + ) } ) do { @@ -509,7 +530,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeWithUserLambdaValidationException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.userLambdaValidationException(UserLambdaValidationException(message: "user lambda validation exception")) + throw AWSCognitoIdentityProvider.UserLambdaValidationException( + message: "user lambda validation exception" + ) } ) do { @@ -540,7 +563,9 @@ class AWSAuthResendSignUpCodeAPITests: AWSCognitoAuthClientBehaviorTests { func testResendSignupCodeUpWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider( mockResendConfirmationCodeOutputResponse: { _ in - throw ResendConfirmationCodeOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpAPITests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpAPITests.swift index b0f514aae8..206a32f568 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpAPITests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpAPITests.swift @@ -187,19 +187,19 @@ class AWSAuthSignUpAPITests: BasePluginTest { func testSignUpServiceError() async { - let errorsToTest: [(signUpOutputError: SignUpOutputError, cognitoError: AWSCognitoAuthError)] = [ - (.codeDeliveryFailureException(.init()), .codeDelivery), - (.invalidEmailRoleAccessPolicyException(.init()), .emailRole), - (.invalidLambdaResponseException(.init()), .lambda), - (.invalidParameterException(.init()), .invalidParameter), - (.invalidPasswordException(.init()), .invalidPassword), - (.invalidSmsRoleAccessPolicyException(.init()), .smsRole), - (.invalidSmsRoleTrustRelationshipException(.init()), .smsRole), - (.resourceNotFoundException(.init()), .resourceNotFound), - (.tooManyRequestsException(.init()), .requestLimitExceeded), - (.unexpectedLambdaException(.init()), .lambda), - (.userLambdaValidationException(.init()), .lambda), - (.usernameExistsException(.init()), .usernameExists), + let errorsToTest: [(signUpOutputError: Error, cognitoError: AWSCognitoAuthError)] = [ + (AWSCognitoIdentityProvider.CodeDeliveryFailureException(), .codeDelivery), + (AWSCognitoIdentityProvider.InvalidEmailRoleAccessPolicyException(), .emailRole), + (AWSCognitoIdentityProvider.InvalidLambdaResponseException(), .lambda), + (AWSCognitoIdentityProvider.InvalidParameterException(), .invalidParameter), + (AWSCognitoIdentityProvider.InvalidPasswordException(), .invalidPassword), + (AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException(), .smsRole), + (AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException(), .smsRole), + (AWSCognitoIdentityProvider.ResourceNotFoundException(), .resourceNotFound), + (AWSCognitoIdentityProvider.TooManyRequestsException(), .requestLimitExceeded), + (AWSCognitoIdentityProvider.UnexpectedLambdaException(), .lambda), + (AWSCognitoIdentityProvider.UserLambdaValidationException(), .lambda), + (AWSCognitoIdentityProvider.UsernameExistsException(), .usernameExists), ] for errorToTest in errorsToTest { @@ -213,7 +213,7 @@ class AWSAuthSignUpAPITests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockSignUpResponse: { _ in - throw SignUpOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() } ) @@ -245,10 +245,9 @@ class AWSAuthSignUpAPITests: BasePluginTest { self.mockIdentityProvider = MockIdentityProvider( mockSignUpResponse: { _ in - throw SdkError.service( - SignUpOutputError.internalErrorException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InternalErrorException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) @@ -273,7 +272,7 @@ class AWSAuthSignUpAPITests: BasePluginTest { } func validateSignUpServiceErrors( - signUpOutputError: SignUpOutputError, + signUpOutputError: Error, expectedCognitoError: AWSCognitoAuthError) async { self.mockIdentityProvider = MockIdentityProvider( mockSignUpResponse: { _ in diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpTaskTests.swift index 4368f834f5..df79b450dc 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignUp/AWSAuthSignUpTaskTests.swift @@ -10,6 +10,7 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import AWSPluginsTestCommon import ClientRuntime +import AWSClientRuntime import AWSCognitoIdentityProvider @@ -52,7 +53,9 @@ class AWSAuthSignUpTaskTests: XCTestCase { let functionExpectation = expectation(description: "API call should be invoked") let signUp: MockIdentityProvider.MockSignUpResponse = { _ in functionExpectation.fulfill() - throw try SignUpOutputError(httpResponse: MockHttpResponse.ok) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: MockHttpResponse.ok, message: nil, requestID: nil, typeName: nil + ) } let request = AuthSignUpRequest(username: "jeffb", @@ -67,7 +70,7 @@ class AWSAuthSignUpTaskTests: XCTestCase { XCTFail("Should not produce success response") } catch { } - wait(for: [functionExpectation], timeout: 1) + await fulfillment(of: [functionExpectation], timeout: 1) } /// Given: Configured AuthState machine with existing signUp flow @@ -90,6 +93,6 @@ class AWSAuthSignUpTaskTests: XCTestCase { userPoolFactory: {MockIdentityProvider(mockSignUpResponse: signUp)}) let task = AWSAuthSignUpTask(request, authEnvironment: authEnvironment) _ = try await task.value - wait(for: [functionExpectation], timeout: 1) + await fulfillment(of: [functionExpectation], timeout: 1) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorFetchDevicesTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorFetchDevicesTests.swift index f9893f0f7a..54c3b1b34b 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorFetchDevicesTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorFetchDevicesTests.swift @@ -20,7 +20,7 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { super.setUp() mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - try ListDevicesOutputResponse(httpResponse: MockHttpResponse.ok) + try await ListDevicesOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -124,10 +124,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw SdkError.service( - ListDevicesOutputError.internalErrorException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InternalErrorException( + httpResponse: .init(body: .empty, statusCode: .accepted) + ) } ) do { @@ -155,7 +154,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) do { @@ -187,7 +188,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.invalidUserPoolConfigurationException(InvalidUserPoolConfigurationException(message: "invalid user pool configuration")) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + message: "invalid user poo configuration" + ) } ) do { @@ -215,7 +218,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -243,7 +248,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.passwordResetRequiredException(PasswordResetRequiredException(message: "password reset required")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "password reset required" + ) } ) do { @@ -275,7 +282,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -307,7 +316,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -339,7 +350,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.userNotConfirmedException(UserNotConfirmedException(message: "user not confirmed")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed" + ) } ) do { @@ -371,7 +384,9 @@ class DeviceBehaviorFetchDevicesTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockListDevicesOutputResponse: { _ in - throw ListDevicesOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorForgetDeviceTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorForgetDeviceTests.swift index 7b2d57ed4d..bc9cab19e4 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorForgetDeviceTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorForgetDeviceTests.swift @@ -20,7 +20,7 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { super.setUp() mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - try ForgetDeviceOutputResponse(httpResponse: MockHttpResponse.ok) + try await ForgetDeviceOutputResponse(httpResponse: MockHttpResponse.ok) } ) } @@ -72,7 +72,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.internalErrorException(InternalErrorException(message: "internal error")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error" + ) } ) @@ -101,7 +103,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.internalErrorException(InternalErrorException(message: "internal error")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -135,10 +139,12 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw SdkError.service( - ForgetDeviceOutputError.invalidParameterException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InvalidParameterException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) } ) do { @@ -167,7 +173,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -202,7 +210,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.invalidUserPoolConfigurationException(InvalidUserPoolConfigurationException(message: "invalid user pool configuration")) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + message: "invalid user pool configuration" + ) } ) do { @@ -230,7 +240,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.invalidUserPoolConfigurationException(InvalidUserPoolConfigurationException(message: "invalid user pool configuration")) + throw AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + message: "invalid user pool configuration" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -264,7 +276,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -292,7 +306,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -326,7 +342,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.passwordResetRequiredException(PasswordResetRequiredException(message: "password reset required")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "password reset required" + ) } ) do { @@ -358,7 +376,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.passwordResetRequiredException(PasswordResetRequiredException(message: "password reset required")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "password reset required" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -396,7 +416,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -428,7 +450,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -466,7 +490,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -498,7 +524,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -536,7 +564,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.userNotConfirmedException(UserNotConfirmedException(message: "user not confirmed")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed" + ) } ) do { @@ -568,7 +598,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.userNotConfirmedException(UserNotConfirmedException(message: "user not confirmed")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", @@ -606,7 +638,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { @@ -638,7 +672,9 @@ class DeviceBehaviorForgetDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockForgetDeviceResponse: { _ in - throw ForgetDeviceOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) let awsAuthDevice = AWSAuthDevice(id: "authDeviceID", diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorRememberDeviceTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorRememberDeviceTests.swift index 6d1126ab79..6403a5534c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorRememberDeviceTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/DeviceBehaviorTests/DeviceBehaviorRememberDeviceTests.swift @@ -20,7 +20,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { super.setUp() mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - try UpdateDeviceStatusOutputResponse(httpResponse: MockHttpResponse.ok) + try await UpdateDeviceStatusOutputResponse( + httpResponse: MockHttpResponse.ok + ) } ) } @@ -77,7 +79,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.internalErrorException(InternalErrorException(message: "internal error")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error" + ) } ) do { @@ -105,7 +109,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.invalidParameterException(InvalidParameterException(message: "invalid parameter")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter" + ) } ) do { @@ -137,10 +143,12 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw SdkError.service( - UpdateDeviceStatusOutputError.invalidUserPoolConfigurationException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InvalidUserPoolConfigurationException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) } ) do { @@ -168,7 +176,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.notAuthorizedException(NotAuthorizedException(message: "not authorized")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized" + ) } ) do { @@ -196,7 +206,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.passwordResetRequiredException(PasswordResetRequiredException(message: "password reset required")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "password reset required" + ) } ) do { @@ -228,7 +240,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.resourceNotFoundException(ResourceNotFoundException(message: "resource not found")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found" + ) } ) do { @@ -260,7 +274,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.tooManyRequestsException(TooManyRequestsException(message: "too many requests")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests" + ) } ) do { @@ -292,7 +308,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.userNotConfirmedException(UserNotConfirmedException(message: "user not confirmed")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed" + ) } ) do { @@ -324,7 +342,9 @@ class DeviceBehaviorRememberDeviceTests: BasePluginTest { mockIdentityProvider = MockIdentityProvider( mockRememberDeviceResponse: { _ in - throw UpdateDeviceStatusOutputError.userNotFoundException(UserNotFoundException(message: "user not found")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found" + ) } ) do { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/HostedUITests/AWSAuthHostedUISignInTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/HostedUITests/AWSAuthHostedUISignInTests.swift index 7ec961d8dc..20c06d50bf 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/HostedUITests/AWSAuthHostedUISignInTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/HostedUITests/AWSAuthHostedUISignInTests.swift @@ -157,7 +157,7 @@ class AWSAuthHostedUISignInTests: XCTestCase { } expectation.fulfill() } - wait(for: [expectation], timeout: networkTimeout) + await fulfillment(of: [expectation], timeout: networkTimeout) } @MainActor @@ -209,7 +209,7 @@ class AWSAuthHostedUISignInTests: XCTestCase { } expectation.fulfill() } - wait(for: [expectation], timeout: networkTimeout) + await fulfillment(of: [expectation], timeout: networkTimeout) } @MainActor @@ -226,7 +226,7 @@ class AWSAuthHostedUISignInTests: XCTestCase { } expectation.fulfill() } - wait(for: [expectation], timeout: networkTimeout) + await fulfillment(of: [expectation], timeout: networkTimeout) } @MainActor @@ -254,7 +254,7 @@ class AWSAuthHostedUISignInTests: XCTestCase { } expectation.fulfill() } - wait(for: [expectation], timeout: networkTimeout) + await fulfillment(of: [expectation], timeout: networkTimeout) } @MainActor diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/AWSCognitoAuthUserBehaviorTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/AWSCognitoAuthUserBehaviorTests.swift index b5fd736e52..4412af3802 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/AWSCognitoAuthUserBehaviorTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/AWSCognitoAuthUserBehaviorTests.swift @@ -27,10 +27,10 @@ class AWSCognitoAuthUserBehaviorTests: BasePluginTest { UpdateUserAttributesOutputResponse() }, mockConfirmUserAttributeOutputResponse: { _ in - try VerifyUserAttributeOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await VerifyUserAttributeOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }, mockChangePasswordOutputResponse: { _ in - try ChangePasswordOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await ChangePasswordOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) } ) } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorChangePasswordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorChangePasswordTests.swift index 3bd2c488f8..2ff8da0acd 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorChangePasswordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorChangePasswordTests.swift @@ -25,7 +25,7 @@ class UserBehaviorChangePasswordTests: BasePluginTest { /// func testSuccessfulChangePassword() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - return try! ChangePasswordOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + return try await ChangePasswordOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }) try await plugin.update(oldPassword: "old password", to: "new password") } @@ -41,7 +41,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithInternalErrorException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.internalErrorException(.init(message: "internal error exception")) + throw AWSCognitoIdentityProvider.InternalErrorException( + message: "internal error exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -67,7 +69,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithInvalidParameterException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.invalidParameterException(.init(message: "invalid parameter exception")) + throw AWSCognitoIdentityProvider.InvalidParameterException( + message: "invalid parameter exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -97,7 +101,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithInvalidPasswordException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.invalidPasswordException(.init(message: "invalid password exception")) + throw AWSCognitoIdentityProvider.InvalidPasswordException( + message: "invalid password exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -127,10 +133,12 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithLimitExceededException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw SdkError.service( - ChangePasswordOutputError.limitExceededException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.LimitExceededException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -160,7 +168,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithNotAuthorizedException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.notAuthorizedException(.init(message: "not authorized exception")) + throw AWSCognitoIdentityProvider.NotAuthorizedException( + message: "not authorized exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -186,7 +196,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithPasswordResetRequiredException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.passwordResetRequiredException(.init(message: "password reset required exception")) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException( + message: "password reset required exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -216,7 +228,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithResourceNotFoundException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.resourceNotFoundException(.init(message: "resource not found exception")) + throw AWSCognitoIdentityProvider.ResourceNotFoundException( + message: "resource not found exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -246,7 +260,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithTooManyRequestsException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.tooManyRequestsException(.init(message: "too many requests exception")) + throw AWSCognitoIdentityProvider.TooManyRequestsException( + message: "too many requests exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -276,7 +292,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithUserNotConfirmedException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.userNotConfirmedException(.init(message: "user not confirmed exception")) + throw AWSCognitoIdentityProvider.UserNotConfirmedException( + message: "user not confirmed exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") @@ -306,7 +324,9 @@ class UserBehaviorChangePasswordTests: BasePluginTest { func testChangePasswordWithUserNotFoundException() async throws { self.mockIdentityProvider = MockIdentityProvider(mockChangePasswordOutputResponse: { _ in - throw ChangePasswordOutputError.userNotFoundException(.init(message: "user not found exception")) + throw AWSCognitoIdentityProvider.UserNotFoundException( + message: "user not found exception" + ) }) do { try await plugin.update(oldPassword: "old password", to: "new password") diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorConfirmAttributeTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorConfirmAttributeTests.swift index 9d202f27c2..b69c260145 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorConfirmAttributeTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorConfirmAttributeTests.swift @@ -23,7 +23,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { /// func testSuccessfulConfirmUpdateUserAttributes() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - try VerifyUserAttributeOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) + try await VerifyUserAttributeOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok)) }) try await plugin.confirm(userAttribute: .email, confirmationCode: "code") } @@ -41,7 +41,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { /// func testConfirmUpdateUserAttributesWithCodeMismatchException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.codeMismatchException(.init()) + throw AWSCognitoIdentityProvider.CodeMismatchException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -70,7 +70,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithExpiredCodeException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.expiredCodeException(.init()) + throw AWSCognitoIdentityProvider.ExpiredCodeException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -98,10 +98,12 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testcConfirmUpdateUserAttributesWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw SdkError.service( - VerifyUserAttributeOutputError.internalErrorException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.InternalErrorException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -127,7 +129,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -157,7 +159,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithLimitExceededException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.limitExceededException(.init()) + throw AWSCognitoIdentityProvider.LimitExceededException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -187,7 +189,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -213,7 +215,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithPasswordResetRequiredException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -243,7 +245,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") @@ -273,7 +275,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) do { @@ -304,7 +306,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithUserNotConfirmedException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) do { @@ -335,7 +337,7 @@ class UserBehaviorConfirmAttributeTests: BasePluginTest { func testConfirmUpdateUserAttributesWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockConfirmUserAttributeOutputResponse: { _ in - throw VerifyUserAttributeOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) do { try await plugin.confirm(userAttribute: .email, confirmationCode: "code") diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorFetchAttributeTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorFetchAttributeTests.swift index 89ed57376e..2ce26faf7e 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorFetchAttributeTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorFetchAttributeTests.swift @@ -10,6 +10,7 @@ import XCTest @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider import ClientRuntime +import AWSClientRuntime class UserBehaviorFetchAttributesTests: BasePluginTest { @@ -75,7 +76,13 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) do { @@ -100,7 +107,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) do { @@ -129,10 +136,12 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw SdkError.service( - GetUserOutputError.notAuthorizedException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.NotAuthorizedException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) }) do { @@ -159,7 +168,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithPasswordResetRequiredException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) do { @@ -190,7 +199,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) do { @@ -221,7 +230,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) do { @@ -252,7 +261,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithUserNotConfirmedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) do { _ = try await plugin.fetchUserAttributes() @@ -282,7 +291,7 @@ class UserBehaviorFetchAttributesTests: BasePluginTest { func testFetchUserAttributesWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeResponse: { _ in - throw GetUserOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) do { _ = try await plugin.fetchUserAttributes() diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorResendCodeTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorResendCodeTests.swift index 53952ac4c6..401fb27a0b 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorResendCodeTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorResendCodeTests.swift @@ -76,10 +76,12 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithCodeMismatchException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw SdkError.service( - GetUserAttributeVerificationCodeOutputError.codeDeliveryFailureException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.CodeDeliveryFailureException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -107,7 +109,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.internalErrorException(.init()) + throw AWSCognitoIdentityProvider.InternalErrorException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -133,7 +135,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -163,7 +165,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithLimitExceededException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.limitExceededException(.init()) + throw AWSCognitoIdentityProvider.LimitExceededException() }) do { @@ -194,7 +196,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -220,7 +222,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithPasswordResetRequiredException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -250,7 +252,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -280,7 +282,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -310,7 +312,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithUserNotConfirmedException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) @@ -340,7 +342,7 @@ class UserBehaviorResendCodeTests: BasePluginTest { func testResendConfirmationCodeWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockGetUserAttributeVerificationCodeOutputResponse: { _ in - throw GetUserAttributeVerificationCodeOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) do { _ = try await plugin.resendConfirmationCode(forUserAttributeKey: .email) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorUpdateAttributeTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorUpdateAttributeTests.swift index e2aac23bdf..568e0bdb42 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorUpdateAttributeTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/UserBehaviourTests/UserBehaviorUpdateAttributeTests.swift @@ -10,6 +10,7 @@ import XCTest @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider import ClientRuntime +import AWSClientRuntime class UserBehaviorUpdateAttributesTests: BasePluginTest { @@ -67,10 +68,12 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithAliasExistsException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw SdkError.service( - UpdateUserAttributesOutputError.aliasExistsException( - .init()), - .init(body: .empty, statusCode: .accepted)) + throw try await AWSCognitoIdentityProvider.AliasExistsException( + httpResponse: .init(body: .empty, statusCode: .accepted), + decoder: nil, + message: nil, + requestID: nil + ) }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -99,7 +102,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithCodeDeliveryFailureException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.codeDeliveryFailureException(.init()) + throw AWSCognitoIdentityProvider.CodeDeliveryFailureException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -128,7 +131,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithCodeMismatchException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.codeMismatchException(.init()) + throw AWSCognitoIdentityProvider.CodeMismatchException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -157,7 +160,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithExpiredCodeException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.expiredCodeException(.init()) + throw AWSCognitoIdentityProvider.ExpiredCodeException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -185,7 +188,13 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithInternalErrorException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok))) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: .init(body: .empty, statusCode: .ok), + message: nil, + requestID: nil, + requestID2: nil, + typeName: nil + ) }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -209,7 +218,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { /// func testUpdateUserAttributesWithInvalidEmailRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.invalidEmailRoleAccessPolicyException(.init()) + throw AWSCognitoIdentityProvider.InvalidEmailRoleAccessPolicyException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -237,7 +246,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { /// func testUpdateUserAttributesWithInvalidLambdaResponseException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.invalidLambdaResponseException(.init()) + throw AWSCognitoIdentityProvider.InvalidLambdaResponseException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -267,7 +276,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithInvalidParameterException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.invalidParameterException(.init()) + throw AWSCognitoIdentityProvider.InvalidParameterException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -295,7 +304,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { /// func testUpdateUserAttributesWithinvalidSmsRoleAccessPolicyException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.invalidSmsRoleAccessPolicyException(.init()) + throw AWSCognitoIdentityProvider.InvalidSmsRoleAccessPolicyException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -323,7 +332,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { /// func testUpdateUserAttributesCodeWithInvalidSmsRoleTrustRelationshipException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.invalidSmsRoleTrustRelationshipException(.init()) + throw AWSCognitoIdentityProvider.InvalidSmsRoleTrustRelationshipException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -353,7 +362,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithNotAuthorizedException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.notAuthorizedException(.init()) + throw AWSCognitoIdentityProvider.NotAuthorizedException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -379,7 +388,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithPasswordResetRequiredException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.passwordResetRequiredException(.init()) + throw AWSCognitoIdentityProvider.PasswordResetRequiredException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -409,7 +418,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithResourceNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.resourceNotFoundException(.init()) + throw AWSCognitoIdentityProvider.ResourceNotFoundException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -439,7 +448,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithTooManyRequestsException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.tooManyRequestsException(.init()) + throw AWSCognitoIdentityProvider.TooManyRequestsException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -469,7 +478,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithUnexpectedLambdaException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.unexpectedLambdaException(.init()) + throw AWSCognitoIdentityProvider.UnexpectedLambdaException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -499,7 +508,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithUserLambdaValidationException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.userLambdaValidationException(.init()) + throw AWSCognitoIdentityProvider.UserLambdaValidationException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -529,7 +538,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithUserNotConfirmedException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.userNotConfirmedException(.init()) + throw AWSCognitoIdentityProvider.UserNotConfirmedException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) @@ -559,7 +568,7 @@ class UserBehaviorUpdateAttributesTests: BasePluginTest { func testUpdateUserAttributesWithUserNotFoundException() async throws { mockIdentityProvider = MockIdentityProvider(mockUpdateUserAttributeResponse: { _ in - throw UpdateUserAttributesOutputError.userNotFoundException(.init()) + throw AWSCognitoIdentityProvider.UserNotFoundException() }) do { _ = try await plugin.update(userAttribute: AuthUserAttribute(.email, value: "Amplify@amazon.com")) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AmplifyAuthCognitoPluginTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AmplifyAuthCognitoPluginTests.swift index f13550cafa..064ccb693c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AmplifyAuthCognitoPluginTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AmplifyAuthCognitoPluginTests.swift @@ -14,7 +14,8 @@ class AmplifyAuthCognitoPluginTests: XCTestCase { let apiTimeout = 1.0 - func testAuthCognitoPlugin() { + @MainActor + func testAuthCognitoPlugin() async { // Load the json configs let bundle = Bundle.authCognitoTestBundle() @@ -28,12 +29,13 @@ class AmplifyAuthCognitoPluginTests: XCTestCase { atPath: testSuiteSubdirectoryPath) for testSuiteFile in testSuiteFiles { + print("Test Suite File: ---> \(directory)/\(testSuiteFile)") + let specification = FeatureSpecification( + fileName: testSuiteFile, + subdirectory: "\(AuthTestHarnessConstants.testSuitesPath)/\(directory)" + ) + let authTestHarness = await AuthTestHarness(featureSpecification: specification) XCTContext.runActivity(named: testSuiteFile) { activity in - print("Test Suite File: ---> \(directory)/\(testSuiteFile)") - let specification = FeatureSpecification( - fileName: testSuiteFile, - subdirectory: "\(AuthTestHarnessConstants.testSuitesPath)/\(directory)") - let authTestHarness = AuthTestHarness(featureSpecification: specification) beginTest(for: authTestHarness.plugin, with: authTestHarness) } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthCodableImplementations/Cognito/Response/ChangePasswordOutputResponse+Codable.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthCodableImplementations/Cognito/Response/ChangePasswordOutputResponse+Codable.swift index b016f5fd2b..dbc5cd16d9 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthCodableImplementations/Cognito/Response/ChangePasswordOutputResponse+Codable.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthCodableImplementations/Cognito/Response/ChangePasswordOutputResponse+Codable.swift @@ -19,7 +19,7 @@ extension ChangePasswordOutputResponse: Codable { guard let httpResponse = try containerValues.decodeIfPresent(HttpResponse.self, forKey: .httpResponse) else { fatalError("Unable to decode http response") } - try self.init(httpResponse: httpResponse) + self.init() } public func encode(to encoder: Encoder) throws { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarness.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarness.swift index e877dbadf4..b219711dee 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarness.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarness.swift @@ -29,7 +29,7 @@ class AuthTestHarness { mockedCognitoHelper.createPlugin() } - init(featureSpecification: FeatureSpecification) { + init(featureSpecification: FeatureSpecification) async { let awsCognitoAuthConfig = featureSpecification.preConditions.amplifyConfiguration.auth?.plugins["awsCognitoAuthPlugin"] @@ -42,7 +42,7 @@ class AuthTestHarness { fatalError("Unable to create auth configuarion") } - testHarnessInput = AuthTestHarnessInput.createInput( + testHarnessInput = await AuthTestHarnessInput.createInput( from: featureSpecification) mockedCognitoHelper = MockedAuthCognitoPluginHelper( diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarnessInput.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarnessInput.swift index b887487ba6..0f0b21a54a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarnessInput.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/AuthTestHarnessInput.swift @@ -23,9 +23,10 @@ struct AuthTestHarnessInput { extension AuthTestHarnessInput { - static func createInput(from specification: FeatureSpecification) -> AuthTestHarnessInput { - - return AuthTestHarnessInput( + static func createInput( + from specification: FeatureSpecification + ) async -> AuthTestHarnessInput { + return await AuthTestHarnessInput( initialAuthState: specification.preConditions.initialAuthState, expectedAuthState: getExpectedAuthState(from: specification), amplifyAPI: getAmplifyAPIUnderTest(from: specification), @@ -39,8 +40,9 @@ extension AuthTestHarnessInput { } private static func getCognitoAPI( - from specification: FeatureSpecification) -> [API.APIName: CognitoAPI] { - return CognitoAPIDecodingHelper.decode(with: specification) + from specification: FeatureSpecification + ) async -> [API.APIName: CognitoAPI] { + return await CognitoAPIDecodingHelper.decode(with: specification) } private static func getExpectedAuthState(from specification: FeatureSpecification) -> AuthState? { @@ -93,9 +95,8 @@ enum CognitoAPI { case globalSignOut(CognitoAPIData) } -struct CognitoAPIData { - +struct CognitoAPIData { let expectedInput: Input? - let output: Result - + let errorBinding: E.Type + let output: Result } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CognitoAPIDecoding/CognitoAPIDecodingHelper.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CognitoAPIDecoding/CognitoAPIDecodingHelper.swift index 867fbf1a97..283f467f9f 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CognitoAPIDecoding/CognitoAPIDecodingHelper.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CognitoAPIDecoding/CognitoAPIDecodingHelper.swift @@ -16,7 +16,7 @@ import Foundation struct CognitoAPIDecodingHelper { - static func decode(with specification: FeatureSpecification) -> [API.APIName: CognitoAPI] { + static func decode(with specification: FeatureSpecification) async -> [API.APIName: CognitoAPI] { var decodedAPIs: [API.APIName: CognitoAPI] = [:] @@ -46,84 +46,104 @@ struct CognitoAPIDecodingHelper { switch apiName { case "forgotPassword": - decodedAPIs[.forgotPassword] = .forgotPassword( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.forgotPassword] = await .forgotPassword( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "signUp": - decodedAPIs[.signUp] = .signUp( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.signUp] = await .signUp( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "deleteUser": - decodedAPIs[.deleteUser] = .deleteUser( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.deleteUser] = await .deleteUser( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "respondToAuthChallenge": - decodedAPIs[.confirmSignIn] = .respondToAuthChallenge( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.confirmSignIn] = await .respondToAuthChallenge( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "confirmDevice": - decodedAPIs[.confirmDevice] = .confirmDevice( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.confirmDevice] = await .confirmDevice( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "initiateAuth": - decodedAPIs[.initiateAuth] = .initiateAuth( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.initiateAuth] = await .initiateAuth( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "revokeToken": - decodedAPIs[.revokeToken] = .revokeToken( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.revokeToken] = await .revokeToken( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "getId": - decodedAPIs[.getId] = .getId( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.getId] = await .getId( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "getCredentialsForIdentity": - decodedAPIs[.getCredentialsForIdentity] = .getCredentialsForIdentity( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.getCredentialsForIdentity] = await .getCredentialsForIdentity( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) case "globalSignOut": - decodedAPIs[.globalSignOut] = .globalSignOut( - getApiInputAndOutput( - request: requestData, - response: response, - responseType: responseType - ) + decodedAPIs[.globalSignOut] = await .globalSignOut( + { + await getApiInputAndOutput( + request: requestData, + response: response, + responseType: responseType + ) + }() ) default: fatalError() @@ -154,11 +174,12 @@ struct CognitoAPIDecodingHelper { private static func getApiInputAndOutput< Input: Decodable, Output: Decodable, - Error: Swift.Error & ClientRuntime.HttpResponseBinding>( - request: Data?, - response: [String: JSONValue], - responseType: String - ) -> CognitoAPIData { + ErrorGenerator: ClientRuntime.HttpResponseErrorBinding + >( + request: Data?, + response: [String: JSONValue], + responseType: String + ) async -> CognitoAPIData { var input: Input? = nil if let request = request { @@ -166,7 +187,7 @@ struct CognitoAPIDecodingHelper { } - let result: Result + let result: Result switch responseType { case "failure": @@ -175,15 +196,17 @@ struct CognitoAPIDecodingHelper { fatalError() } - let error = try! Error( + let error = try! await ErrorGenerator.makeError( httpResponse: .init( headers: Headers( [ "x-amzn-error-message": errorMessage, "X-Amzn-Errortype": "#\(errorType):"]), body: .empty, - statusCode: .ok), - decoder: nil) + statusCode: .ok + ), + decoder: nil + ) result = .failure(error) case "success": let responseData = try! JSONEncoder().encode(response) @@ -193,6 +216,10 @@ struct CognitoAPIDecodingHelper { default: fatalError("invalid response type") } - return CognitoAPIData(expectedInput: input, output: result) + return CognitoAPIData( + expectedInput: input, + errorBinding: ErrorGenerator.self, + output: result + ) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineListenerTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineListenerTests.swift index 62cf4328ec..cdb8ccdc6b 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineListenerTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineListenerTests.swift @@ -41,7 +41,10 @@ class StateMachineListenerTests: XCTestCase { } let event = Counter.Event(id: "test", eventType: .increment) await stateMachine.send(event) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [notified], + timeout: 0.1 + ) } func testDoesNotNotifyOnNoStateChange() async { @@ -58,7 +61,10 @@ class StateMachineListenerTests: XCTestCase { let event = Counter.Event(id: "test", eventType: .adjustBy(0)) await stateMachine.send(event) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [notified], + timeout: 0.1 + ) } func testDoesNotNotifyAfterUnsubscribe() async { @@ -77,7 +83,10 @@ class StateMachineListenerTests: XCTestCase { seq.cancel() let event = Counter.Event(id: "test", eventType: .increment) await stateMachine.send(event) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [notified], + timeout: 0.1 + ) } func testOrderOfSubsription() async throws { @@ -109,7 +118,10 @@ class StateMachineListenerTests: XCTestCase { try await Task.sleep(nanoseconds: 1_000_000) await self.stateMachine.send(Counter.Event(id: "set3", eventType: .set(12))) } - await waitForExpectations(timeout: 2) + await fulfillment( + of: [notified], + timeout: 2 + ) seq.cancel() } } @@ -143,7 +155,10 @@ class StateMachineListenerTests: XCTestCase { } } - await waitForExpectations(timeout: 1) + await fulfillment( + of: [notified], + timeout: 1 + ) task2.cancel() } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineTests.swift index 59902bb014..c0d3dbca65 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/hierarchical-state-machine-swiftTests/StateMachineTests.swift @@ -49,7 +49,11 @@ class StateMachineTests: XCTestCase { taskCompletion.fulfill() } } - await waitForExpectations(timeout: 1) + + await fulfillment( + of: [taskCompletion], + timeout: 0.1 + ) let state = await testMachine.currentState XCTAssertEqual(state.value, 0) } @@ -106,7 +110,10 @@ class StateMachineTests: XCTestCase { ) await testMachine.send(event) - await waitForExpectations(timeout: 0.1) + await fulfillment( + of: [action1WasExecuted, action2WasExecuted], + timeout: 0.1 + ) } /// Given: @@ -148,7 +155,11 @@ class StateMachineTests: XCTestCase { ) await testMachine.send(event) - await waitForExpectations(timeout: 0.1) + + await fulfillment( + of: [action1WasExecuted, action2WasExecuted], + timeout: 0.1 + ) XCTAssertEqual(executionCount.get(), 2) } diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a8b70a2420..574dbfb6b0 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift.git", "state" : { - "revision" : "6feec6c3787877807aa9a00fad09591b96752376", - "version" : "0.6.1" + "revision" : "997904873945e074aaf5c51ea968d9a84684525a", + "version" : "0.13.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift.git", "state" : { - "revision" : "24bae88a2391fe75da8a940a544d1ef6441f5321", - "version" : "0.13.0" + "revision" : "ace826dbfe96e7e3103fe7f45f815b8a590bcf21", + "version" : "0.26.0" } }, { @@ -57,10 +57,10 @@ { "identity" : "smithy-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/smithy-swift.git", + "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "7b28da158d92cd06a3549140d43b8fbcf64a94a6", - "version" : "0.15.0" + "revision" : "eed3f3d8e5aa704fcd60bb227b0fc89bf3328c42", + "version" : "0.30.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MaxDesiatov/XMLCoder.git", "state" : { - "revision" : "c438dad94f6a243b411b70a4b4bac54595064808", - "version" : "0.15.0" + "revision" : "80b4a1646399b8e4e0ce80711653476a85bd5e37", + "version" : "0.17.0" } } ], diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/xcshareddata/xcschemes/AuthHostApp.xcscheme b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/xcshareddata/xcschemes/AuthHostApp.xcscheme index 1b31043b6d..31b70e7d59 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/xcshareddata/xcschemes/AuthHostApp.xcscheme +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/xcshareddata/xcschemes/AuthHostApp.xcscheme @@ -50,8 +50,7 @@ + skipped = "NO"> + skipped = "NO"> Self { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - XCTAssertTrue(springboard.buttons["Continue"].waitForExistence(timeout: 5)) + XCTAssertTrue(springboard.buttons["Continue"].waitForExistence(timeout: 60)) springboard.buttons["Continue"].tap() return self } diff --git a/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignInScreen.swift b/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignInScreen.swift index abed37fdd0..0b9224bf40 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignInScreen.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignInScreen.swift @@ -40,13 +40,13 @@ struct SignInScreen: Screen { func dismissSignInAlert() -> Self { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - XCTAssertTrue(springboard.buttons["Continue"].waitForExistence(timeout: 5)) + XCTAssertTrue(springboard.buttons["Continue"].waitForExistence(timeout: 60)) springboard.buttons["Continue"].tap() return self } func signIn(username: String, password: String) -> Self { - _ = app.webViews.textFields["Username"].waitForExistence(timeout: 5) + _ = app.webViews.textFields["Username"].waitForExistence(timeout: 60) app.webViews.textFields["Username"].tap() app.webViews.textFields["Username"].typeText(username) @@ -59,7 +59,7 @@ struct SignInScreen: Screen { func testSignInSucceeded() -> Self { let successText = app.staticTexts[Identifiers.successLabel] - XCTAssertTrue(successText.waitForExistence(timeout: 5), "SignIn operation failed") + XCTAssertTrue(successText.waitForExistence(timeout: 60), "SignIn operation failed") return self } } diff --git a/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignUpScreen.swift b/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignUpScreen.swift index 4ae3c73dc9..984877c2e6 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignUpScreen.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostedUIApp/AuthHostedUIAppUITests/Screen/SignUpScreen.swift @@ -38,7 +38,7 @@ struct SignUpScreen: Screen { func testSignUpSucceeded() -> Self { let successText = app.staticTexts[Identifiers.successLabel] - XCTAssertTrue(successText.waitForExistence(timeout: 5), "Signup operation failed") + XCTAssertTrue(successText.waitForExistence(timeout: 60), "Signup operation failed") return self } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift index 4f89dfe86d..ab72c799a8 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift @@ -13,7 +13,7 @@ public class AWSAuthService: AWSAuthServiceBehavior { public init() {} - public func getCredentialsProvider() -> CredentialsProvider { + public func getCredentialsProvider() -> CredentialsProviding { return AmplifyAWSCredentialsProvider() } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift index b9e9582418..e984116ede 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift @@ -11,7 +11,7 @@ import AWSClientRuntime public protocol AWSAuthServiceBehavior: AnyObject { - func getCredentialsProvider() -> CredentialsProvider + func getCredentialsProvider() -> CredentialsProviding func getTokenClaims(tokenString: String) -> Result<[String: AnyObject], AuthError> diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift index 730760c54f..1959aa58b5 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift @@ -10,7 +10,7 @@ import AWSClientRuntime import AwsCommonRuntimeKit import Foundation -public class AmplifyAWSCredentialsProvider: AWSClientRuntime.CredentialsProvider { +public class AmplifyAWSCredentialsProvider: AWSClientRuntime.CredentialsProviding { public func getCredentials() async throws -> AWSClientRuntime.AWSCredentials { let authSession = try await Amplify.Auth.fetchAuthSession() diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift index dfe7eb94a5..4da2777291 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift @@ -13,7 +13,7 @@ import AwsCommonRuntimeKit public protocol AWSSignatureV4Signer { func sigV4SignedRequest(requestBuilder: SdkHttpRequestBuilder, - credentialsProvider: AWSClientRuntime.CredentialsProvider, + credentialsProvider: AWSClientRuntime.CredentialsProviding, signingName: Swift.String, signingRegion: Swift.String, date: ClientRuntime.Date) async throws -> SdkHttpRequest? @@ -24,7 +24,7 @@ public class AmplifyAWSSignatureV4Signer: AWSSignatureV4Signer { } public func sigV4SignedRequest(requestBuilder: SdkHttpRequestBuilder, - credentialsProvider: AWSClientRuntime.CredentialsProvider, + credentialsProvider: AWSClientRuntime.CredentialsProviding, signingName: Swift.String, signingRegion: Swift.String, date: ClientRuntime.Date) async throws -> SdkHttpRequest? { diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift index 3d96e06798..3ceee7167e 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift @@ -10,7 +10,7 @@ import Amplify import AWSClientRuntime public protocol IAMCredentialsProvider { - func getCredentialsProvider() -> CredentialsProvider + func getCredentialsProvider() -> CredentialsProviding } public struct BasicIAMCredentialsProvider: IAMCredentialsProvider { @@ -20,7 +20,7 @@ public struct BasicIAMCredentialsProvider: IAMCredentialsProvider { self.authService = authService } - public func getCredentialsProvider() -> CredentialsProvider { + public func getCredentialsProvider() -> CredentialsProviding { return authService.getCredentialsProvider() } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Core/AWSPluginsCore/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift b/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift index 0848074acc..969467c0bd 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift @@ -6,7 +6,6 @@ // import Foundation -import AWSClientRuntime import Amplify /// Convenience class that is used by Amplify to include metadata such as values for a "User-Agent" during @@ -16,46 +15,12 @@ import Amplify public class AmplifyAWSServiceConfiguration { /// - Tag: AmplifyAWSServiceConfiguration.amplifyVersion - public static let amplifyVersion = "2.19.0" + public static let amplifyVersion = "2.21.3" /// - Tag: AmplifyAWSServiceConfiguration.platformName public static let platformName = "amplify-swift" - /// Returns basic amount of metadata that includes both - /// [AmplifyAWSServiceConfiguration.amplifyVersion](x-source-tag://AmplifyAWSServiceConfiguration.amplifyVersion) - /// and - /// [AmplifyAWSServiceConfiguration.platformName](x-source-tag://AmplifyAWSServiceConfiguration.platformName) - /// in addition to the operating system version if `includesOS` is set to `true`. - /// - /// - Tag: AmplifyAWSServiceConfiguration.frameworkMetaDataWithOS - public static func frameworkMetaData(includeOS: Bool = false) -> FrameworkMetadata { - let osKey = "os" - guard let flutterVersion = platformMapping[Platform.flutter] else { - if includeOS { - return FrameworkMetadata( - name: platformName, - version: amplifyVersion, - extras: [osKey: frameworkOS()] - ) - } - return FrameworkMetadata(name: platformName, version: amplifyVersion) - } - var extras = [platformName: amplifyVersion] - if includeOS { - extras[osKey] = frameworkOS() - } - return FrameworkMetadata(name: Platform.flutter.rawValue, - version: flutterVersion, - extras: extras) - } + public static let userAgentLib: String = "lib/\(platformName)#\(amplifyVersion)" - private static func frameworkOS() -> String { - // Please note that because the value returned by this function will be - // sanitized by FrameworkMetadata by removing anything not in a special - // character set that does NOT include the forward slash (/), the - // backslash (\) is used as a separator instead. - let separator = "\\" - let operatingSystem = DeviceInfo.current.operatingSystem - return [operatingSystem.name, operatingSystem.version].joined(separator: separator) - } + public static let userAgentOS: String = "os/\(DeviceInfo.current.operatingSystem.name)#\(DeviceInfo.current.operatingSystem.version)" } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift index b503ed5f3d..306165b134 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift @@ -9,7 +9,7 @@ import Foundation import ClientRuntime extension Foundation.URLRequest { - init(sdkRequest: ClientRuntime.SdkHttpRequest) throws { + init(sdkRequest: ClientRuntime.SdkHttpRequest) async throws { guard let url = sdkRequest.endpoint.url else { throw FoundationClientEngineError.invalidRequestURL(sdkRequest: sdkRequest) } @@ -22,11 +22,7 @@ extension Foundation.URLRequest { } } - switch sdkRequest.body { - case .data(let data): httpBody = data - case .stream(let stream): httpBody = stream.toBytes().getData() - case .none: break - } + httpBody = try await sdkRequest.body.readData() } } @@ -49,7 +45,7 @@ extension ClientRuntime.HttpResponse { convenience init(httpURLResponse: HTTPURLResponse, data: Data) throws { let headers = Self.headers(from: httpURLResponse.allHeaderFields) - let body = HttpBody.stream(ByteStream.from(data: data)) + let body = HttpBody.data(data) guard let statusCode = HttpStatusCode(rawValue: httpURLResponse.statusCode) else { // This shouldn't happen, but `HttpStatusCode` only exposes a failable diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift index 3bda16f9c1..cbf7f36be7 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift @@ -12,7 +12,7 @@ import Amplify @_spi(FoundationClientEngine) public struct FoundationClientEngine: HttpClientEngine { public func execute(request: ClientRuntime.SdkHttpRequest) async throws -> ClientRuntime.HttpResponse { - let urlRequest = try URLRequest(sdkRequest: request) + let urlRequest = try await URLRequest(sdkRequest: request) let (data, response) = try await URLSession.shared.data(for: urlRequest) guard let httpURLResponse = response as? HTTPURLResponse else { diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/PluginClientEngine.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/PluginClientEngine.swift new file mode 100644 index 0000000000..f779aa7fbe --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/PluginClientEngine.swift @@ -0,0 +1,26 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import ClientRuntime +import AWSClientRuntime + +@_spi(PluginHTTPClientEngine) +public func baseClientEngine( + for configuration: AWSClientConfiguration +) -> HttpClientEngine { + let baseClientEngine: HttpClientEngine + #if os(iOS) || os(macOS) + // networking goes through CRT + baseClientEngine = configuration.httpClientEngine + #else + // networking goes through Foundation + baseClientEngine = FoundationClientEngine() + #endif + return baseClientEngine +} + diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift new file mode 100644 index 0000000000..690b8f932f --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift @@ -0,0 +1,35 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import ClientRuntime + +@_spi(PluginHTTPClientEngine) +extension SdkHttpRequest { + public func updatingUserAgent(with value: String) -> SdkHttpRequest { + let userAgentKey = "User-Agent" + var headers = headers + headers.remove(name: userAgentKey) + headers.add(name: userAgentKey, value: value) + + let endpoint = ClientRuntime.Endpoint( + host: endpoint.host, + path: endpoint.path, + port: endpoint.port, + queryItems: endpoint.queryItems, + protocolType: endpoint.protocolType, + headers: headers, + properties: endpoint.properties + ) + + return SdkHttpRequest( + method: method, + endpoint: endpoint, + body: body + ) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSettingClientEngine.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSettingClientEngine.swift new file mode 100644 index 0000000000..aff15e315c --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSettingClientEngine.swift @@ -0,0 +1,47 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import ClientRuntime +import AWSClientRuntime + +@_spi(PluginHTTPClientEngine) +public struct UserAgentSettingClientEngine: AWSPluginExtension { + @_spi(InternalHttpEngineProxy) + public let target: HttpClientEngine + private let userAgentKey = "User-Agent" + + public init(target: HttpClientEngine) { + self.target = target + } +} + +@_spi(PluginHTTPClientEngine) +extension UserAgentSettingClientEngine: HttpClientEngine { + // CI updates the `platformName` property in `AmplifyAWSServiceConfiguration`. + // We can / probably should move this in the future + // as it's no longer necessary there. + var lib: String { AmplifyAWSServiceConfiguration.userAgentLib } + + public func execute(request: SdkHttpRequest) async throws -> HttpResponse { + let existingUserAgent = request.headers.value(for: userAgentKey) ?? "" + let userAgent = "\(existingUserAgent) \(lib)" + let updatedRequest = request.updatingUserAgent(with: userAgent) + + return try await target.execute(request: updatedRequest) + } +} + +@_spi(PluginHTTPClientEngine) +extension HttpClientEngine where Self == UserAgentSettingClientEngine { + public static func userAgentEngine( + for configuration: AWSClientConfiguration + ) -> Self { + let baseClientEngine = baseClientEngine(for: configuration) + return self.init(target: baseClientEngine) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift index c21664e7a3..863fa636bf 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift @@ -12,7 +12,7 @@ public class UserAgentSuffixAppender: AWSPluginExtension { @_spi(InternalHttpEngineProxy) public var target: HttpClientEngine? = nil public let suffix: String - private let userAgentHeader = "User-Agent" + private let userAgentKey = "User-Agent" public init(suffix: String) { self.suffix = suffix @@ -25,13 +25,11 @@ extension UserAgentSuffixAppender: HttpClientEngine { guard let target = target else { throw ClientError.unknownError("HttpClientEngine is not set") } - var headers = request.headers - let currentUserAgent = headers.value(for: userAgentHeader) ?? "" - headers.update( - name: userAgentHeader, - value: "\(currentUserAgent) \(suffix)" - ) - request.headers = headers + + let existingUserAgent = request.headers.value(for: userAgentKey) ?? "" + let userAgent = "\(existingUserAgent) \(suffix)" + let request = request.updatingUserAgent(with: userAgent) + return try await target.execute(request: request) } } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/ServiceConfiguration/AmplifyAWSServiceConfigurationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/ServiceConfiguration/AmplifyAWSServiceConfigurationTests.swift deleted file mode 100644 index 28fd9f6b80..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/ServiceConfiguration/AmplifyAWSServiceConfigurationTests.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import XCTest -@testable import AWSPluginsCore -@testable import AWSClientRuntime - -class AmplifyAWSServiceConfigurationTests: XCTestCase { - - override func tearDown() { - AmplifyAWSServiceConfiguration.platformMapping = [:] - } - - /// Test initiating AmplifyAWSServiceConfiguration - /// - /// - Given: Amplify library - /// - When: - /// - I call AmplifyAWSServiceConfiguration with credential provider - /// - Then: - /// - AmplifyAWSServiceConfiguration should be configured properly - /// - func testInstantiation() { - let frameworkMetaData = AmplifyAWSServiceConfiguration.frameworkMetaData() - XCTAssertNotNil(frameworkMetaData) - XCTAssertEqual(frameworkMetaData.sanitizedName, "amplify-swift") - XCTAssertEqual(frameworkMetaData.sanitizedVersion, AmplifyAWSServiceConfiguration.amplifyVersion) - } - - /// Test adding a new platform to AmplifyAWSServiceConfiguration - /// - /// - Given: Amplify library - /// - When: - /// - I add a new platform to the AmplifyAWSServiceConfiguration - /// - Then: - /// - AmplifyAWSServiceConfiguration should be configured properly with the new platform added. - /// - func testAddNewPlatform() { - AmplifyAWSServiceConfiguration.addUserAgentPlatform(.flutter, version: "1.1") - let frameworkMetaData = AmplifyAWSServiceConfiguration.frameworkMetaData() - XCTAssertNotNil(frameworkMetaData) - XCTAssertEqual(frameworkMetaData.sanitizedName, "amplify-flutter") - XCTAssertEqual(frameworkMetaData.sanitizedVersion, "1.1") - - XCTAssertNotNil(frameworkMetaData.extras) - XCTAssertEqual(frameworkMetaData.extras["amplify-swift"], AmplifyAWSServiceConfiguration.amplifyVersion) - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift new file mode 100644 index 0000000000..1e09f72d9d --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift @@ -0,0 +1,111 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@_spi(InternalAmplifyPluginExtension) +@_spi(PluginHTTPClientEngine) +@_spi(InternalHttpEngineProxy) +import AWSPluginsCore +import ClientRuntime +import XCTest + +class UserAgentSettingClientEngineTestCase: XCTestCase { + let userAgentKey = "User-Agent" + + /// Given: A `UserAgentSettingClientEngine`. + /// When: A request is invoked **with** an existing User-Agent. + /// Then: The `lib` component of the user-agent is added. + func test_existingUserAgent_addsLibComponent() async throws { + let request: SdkHttpRequest = .mock + let existingUserAgent = "foo/bar/baz" + request.withHeader(name: userAgentKey, value: existingUserAgent) + + let target = MockTargetEngine() + let engine = UserAgentSettingClientEngine(target: target) + _ = try await engine.execute(request: request) + let userAgent = try XCTUnwrap(target.request?.headers.value(for: userAgentKey)) + + XCTAssertEqual( + userAgent, + "\(existingUserAgent) \(AmplifyAWSServiceConfiguration.userAgentLib)" + ) + } + + /// Given: A `UserAgentSettingClientEngine`. + /// When: A request is invoked **without** existing User-Agent. + /// Then: The `lib` component of the user-agent is added. + func test_nonExistingUserAgent_addsLibComponent() async throws { + let request: SdkHttpRequest = .mock + let target = MockTargetEngine() + let engine = UserAgentSettingClientEngine(target: target) + _ = try await engine.execute(request: request) + let userAgent = try XCTUnwrap(target.request?.headers.value(for: userAgentKey)).trim() + + XCTAssertEqual(userAgent, AmplifyAWSServiceConfiguration.userAgentLib) + } + + /// Given: A `UserAgentSettingClientEngine` targeting a `UserAgentSuffixAppender`. + /// When: A request is invoked **with** existing User-Agent. + /// Then: The `lib` component of the user-agent and the suffix are added. + func test_existingUserAgentCombinedWithSuffixAppender_addLibAndSuffix() async throws { + let request: SdkHttpRequest = .mock + let existingUserAgent = "foo/bar/baz" + request.withHeader(name: userAgentKey, value: existingUserAgent) + + let target = MockTargetEngine() + let suffix = "a/b/c" + let suffixAppender = UserAgentSuffixAppender(suffix: suffix) + suffixAppender.target = target + let engine = UserAgentSettingClientEngine(target: suffixAppender) + + _ = try await engine.execute(request: request) + let userAgent = try XCTUnwrap(target.request?.headers.value(for: userAgentKey)) + XCTAssertEqual( + userAgent, + "\(existingUserAgent) \(AmplifyAWSServiceConfiguration.userAgentLib) \(suffix)" + ) + } + + /// Given: A `UserAgentSettingClientEngine` targeting a `UserAgentSuffixAppender`. + /// When: A request is invoked **without** existing User-Agent. + /// Then: The `lib` component of the user-agent and the suffix are added. + func test_nonExistingUserAgentCombinedWithSuffixAppender_addLibAndSuffix() async throws { + let request: SdkHttpRequest = .mock + + let target = MockTargetEngine() + let suffix = "a/b/c" + let suffixAppender = UserAgentSuffixAppender(suffix: suffix) + suffixAppender.target = target + let engine = UserAgentSettingClientEngine(target: suffixAppender) + + _ = try await engine.execute(request: request) + let userAgent = try XCTUnwrap(target.request?.headers.value(for: userAgentKey)).trim() + XCTAssertEqual( + userAgent, + "\(AmplifyAWSServiceConfiguration.userAgentLib) \(suffix)" + ) + } +} + +class MockTargetEngine: HttpClientEngine { + var request: SdkHttpRequest? + + func execute( + request: SdkHttpRequest + ) async throws -> HttpResponse { + self.request = request + return .init(body: .empty, statusCode: .accepted) + } +} + +extension SdkHttpRequest { + static var mock: SdkHttpRequest { + .init( + method: .get, + endpoint: .init(host: "amplify") + ) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift index d680cfee36..221ead103c 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift @@ -31,12 +31,14 @@ class UserAgentSuffixAppenderTests: XCTestCase { /// Then: The user agent suffix is appended func testExecute_withExistingUserAgentHeader_shouldAppendSuffix() async throws { let request = createRequest() - request.headers.add(name: userAgentKey, value: "existingUserAgent") + request.withHeader(name: userAgentKey, value: "existingUserAgent") _ = try await appender.execute(request: request) XCTAssertEqual(httpClientEngine.executeCount, 1) XCTAssertNotNil(httpClientEngine.executeRequest) - let userAgent = try XCTUnwrap(request.headers.value(for: userAgentKey)) + let userAgent = try XCTUnwrap( + httpClientEngine.executeRequest?.headers.value(for: userAgentKey) + ) XCTAssertTrue(userAgent.hasSuffix(customSuffix)) } @@ -49,7 +51,9 @@ class UserAgentSuffixAppenderTests: XCTestCase { _ = try await appender.execute(request: request) XCTAssertEqual(httpClientEngine.executeCount, 1) XCTAssertNotNil(httpClientEngine.executeRequest) - let userAgent = try XCTUnwrap(request.headers.value(for: userAgentKey)) + let userAgent = try XCTUnwrap( + httpClientEngine.executeRequest?.headers.value(for: userAgentKey) + ) XCTAssertTrue(userAgent.hasSuffix(customSuffix)) } @@ -72,8 +76,7 @@ class UserAgentSuffixAppenderTests: XCTestCase { private func createRequest() -> SdkHttpRequest { return SdkHttpRequest( method: .get, - endpoint: .init(host: "customHost"), - headers: .init() + endpoint: .init(host: "customHost") ) } } diff --git a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift index 7f6bd46fbf..8c5d7cfa62 100644 --- a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift +++ b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift @@ -28,7 +28,7 @@ public class MockAWSAuthService: AWSAuthServiceBehavior { interactions.append(#function) } - public func getCredentialsProvider() -> CredentialsProvider { + public func getCredentialsProvider() -> CredentialsProviding { interactions.append(#function) let cognitoCredentialsProvider = MyCustomCredentialsProvider() return cognitoCredentialsProvider @@ -61,7 +61,7 @@ public class MockAWSAuthService: AWSAuthServiceBehavior { } } -struct MyCustomCredentialsProvider: CredentialsProvider { +struct MyCustomCredentialsProvider: CredentialsProviding { func getCredentials() async throws -> AWSClientRuntime.AWSCredentials { AWSCredentials( accessKey: "AKIDEXAMPLE", diff --git a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift index e4dc290015..090010b65a 100644 --- a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift +++ b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift @@ -12,7 +12,7 @@ import Foundation class MockAWSSignatureV4Signer: AWSSignatureV4Signer { func sigV4SignedRequest(requestBuilder: SdkHttpRequestBuilder, - credentialsProvider: CredentialsProvider, + credentialsProvider: CredentialsProviding, signingName: String, signingRegion: String, date: Date) throws -> SdkHttpRequest? { diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c69ba3b3a --- /dev/null +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/AWSDataStoreLocalStoreTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/AWSDataStoreLocalStoreTests.swift index cd506db17f..32a80b1735 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/AWSDataStoreLocalStoreTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/AWSDataStoreLocalStoreTests.swift @@ -288,8 +288,8 @@ class AWSDataStoreLocalStoreTests: LocalStoreIntegrationTestBase { setUp(withModels: TestModelRegistration()) try await Amplify.DataStore.clear() var snapshotCount = 0 - let initialQueryComplete = asyncExpectation(description: "initial snapshot received") - let allSnapshotsReceived = asyncExpectation(description: "query snapshots received") + let initialQueryComplete = expectation(description: "initial snapshot received") + let allSnapshotsReceived = expectation(description: "query snapshots received") let subscription = Amplify.DataStore.observeQuery(for: Post.self) let sink = Amplify.Publisher.create(subscription).sink { completed in @@ -302,15 +302,15 @@ class AWSDataStoreLocalStoreTests: LocalStoreIntegrationTestBase { } receiveValue: { querySnapshot in snapshotCount += 1 if snapshotCount == 1 { - Task { await initialQueryComplete.fulfill() } + initialQueryComplete.fulfill() } if querySnapshot.items.count == 15 { - Task { await allSnapshotsReceived.fulfill() } + allSnapshotsReceived.fulfill() } } - await waitForExpectations([initialQueryComplete], timeout: 10) + await fulfillment(of: [initialQueryComplete], timeout: 10) _ = try await setUpLocalStore(numberOfPosts: 15) - await waitForExpectations([allSnapshotsReceived], timeout: 10) + await fulfillment(of: [allSnapshotsReceived], timeout: 10) XCTAssertTrue(snapshotCount >= 2) sink.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift index 3c33fb8dd6..5b8e0271c4 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift @@ -79,16 +79,20 @@ class AWSDataStorePluginTests: XCTestCase { validAuthPluginKey: "MockAuthCategoryPlugin") do { try plugin.configure(using: nil) - let queryCompleted = asyncExpectation(description: "query completed") + let queryCompleted = expectation(description: "query completed") Task { _ = try await plugin.query(ExampleWithEveryType.self) - await queryCompleted.fulfill() + queryCompleted.fulfill() } - await waitForExpectations([queryCompleted], timeout: 1.0) + await fulfillment(of: [queryCompleted], timeout: 1.0) } catch { XCTFail("DataStore configuration should not fail with nil configuration. \(error)") } - await waitForExpectations(timeout: 1.0) + + await fulfillment( + of: [startExpectation], + timeout: 1 + ) } func testStorageEngineStartsOnPluginStopStart() throws { @@ -230,7 +234,11 @@ class AWSDataStorePluginTests: XCTestCase { XCTAssertNotNil(plugin.storageEngine) XCTAssertNotNil(plugin.dataStorePublisher) }) - wait(for: [startExpectation, stopExpectation, startExpectationOnSecondStart], timeout: 1, enforceOrder: true) + wait( + for: [startExpectation, stopExpectation, startExpectationOnSecondStart], + timeout: 1, + enforceOrder: true + ) wait(for: [finishNotReceived], timeout: 1) sink.cancel() } catch { @@ -301,7 +309,12 @@ class AWSDataStorePluginTests: XCTestCase { XCTAssertNotNil(plugin.storageEngine) XCTAssertNotNil(plugin.dataStorePublisher) }) - wait(for: [startExpectation, clearExpectation, startExpectationOnSecondStart], timeout: 1, enforceOrder: true) + + wait( + for: [startExpectation, clearExpectation, startExpectationOnSecondStart], + timeout: 1, + enforceOrder: true + ) wait(for: [finishNotReceived], timeout: 1) sink.cancel() } catch { @@ -449,7 +462,7 @@ class AWSDataStorePluginTests: XCTestCase { startCompleted.fulfill() }) wait(for: [startCompleted], timeout: 1.0) - + let clearCompleted = expectation(description: "clear completed") plugin.clear(completion: { _ in XCTAssertNil(plugin.storageEngine) @@ -528,7 +541,7 @@ class AWSDataStorePluginTests: XCTestCase { startCompleted.fulfill() }) wait(for: [startCompleted], timeout: 1.0) - + let stopCompleted = expectation(description: "stop completed") plugin.stop(completion: { _ in XCTAssertNotNil(plugin.storageEngine) @@ -537,7 +550,11 @@ class AWSDataStorePluginTests: XCTestCase { }) wait(for: [stopCompleted], timeout: 1.0) - wait(for: [startExpectation, stopExpectation], timeout: 1, enforceOrder: true) + wait( + for: [startExpectation, stopExpectation], + timeout: 1, + enforceOrder: true + ) let mockModel = MockSynced(id: "12345") try plugin.dataStorePublisher?.send(input: MutationEvent(model: mockModel, modelSchema: mockModel.schema, diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/ListTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/ListTests.swift index e0a84f58d5..b2dcb3b347 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/ListTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/ListTests.swift @@ -8,9 +8,9 @@ import Combine import XCTest -@testable import Amplify -@testable import AmplifyTestCommon -@testable import AWSDataStorePlugin +import Amplify +import AmplifyTestCommon +import AWSDataStorePlugin class ListTests: BaseDataStoreTests { @@ -25,15 +25,9 @@ class ListTests: BaseDataStoreTests { let postId = preparePostDataForTest() func checkComments(_ comments: List) async throws { - guard case .notLoaded = comments.loadedState else { - XCTFail("Should not be loaded") - return - } + XCTAssertFalse(comments.isLoaded) try await comments.fetch() - guard case .loaded = comments.loadedState else { - XCTFail("Should be loaded") - return - } + XCTAssertTrue(comments.isLoaded) XCTAssertEqual(comments.count, 2) expect.fulfill() } @@ -49,7 +43,7 @@ class ListTests: BaseDataStoreTests { XCTFail("\(error)") } - await waitForExpectations(timeout: 1) + await fulfillment(of: [expect], timeout: 1) } // MARK: - Helpers diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StateMachineTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StateMachineTests.swift index 62e0616218..3f23198981 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StateMachineTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StateMachineTests.swift @@ -106,14 +106,16 @@ class StateMachineTests: XCTestCase { } } - wait(for: [ - receivedOneOnSubscribe, - receivedTwoAfterSubscribe, - receivedThreeAfterSubscribe, - receivedOneAfterSubscribe + wait( + for: [ + receivedOneOnSubscribe, + receivedTwoAfterSubscribe, + receivedThreeAfterSubscribe, + receivedOneAfterSubscribe ], - timeout: 1.0, - enforceOrder: true) + timeout: 1.0, + enforceOrder: true + ) listener.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/CascadeDeleteOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/CascadeDeleteOperationTests.swift index fcc97f368c..dd3a7dd4ea 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/CascadeDeleteOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/CascadeDeleteOperationTests.swift @@ -87,7 +87,7 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } } operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [completed], timeout: 1) guard case .success(let queriedRestaurants) = await queryModelSynchronous(modelType: Restaurant.self, predicate: predicate) else { XCTFail("Failed to query") @@ -361,7 +361,8 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } } operation.start() - await waitForExpectations(timeout: 1) + + await fulfillment(of: [receivedMutationEvent, expectedFailures, expectedSuccess, completed], timeout: 1) guard case .success(let queriedRestaurants) = await queryModelSynchronous(modelType: Restaurant.self, predicate: predicate) else { XCTFail("Failed to query") @@ -422,7 +423,7 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } } operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [receivedMutationEvent, expectedFailures, expectedSuccess, completed], timeout: 1) guard case .success(let queriedModels) = await queryModelSynchronous(modelType: ModelCompositePk.self, predicate: predicate) else { XCTFail("Failed to query") @@ -595,7 +596,7 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } operation.syncIfNeededAndFinish(result) - wait(for: [receivedMutationEvent, expectedFailures, expectedSuccess], timeout: 1) + await fulfillment(of: [receivedMutationEvent, expectedFailures, expectedSuccess], timeout: 1) } func testDeleteWithAssociatedModels() async { @@ -660,7 +661,7 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } operation.syncIfNeededAndFinish(result) - wait(for: [completed, receivedMutationEvent, expectedFailures, expectedSuccess], timeout: 1) + await fulfillment(of: [completed, receivedMutationEvent, expectedFailures, expectedSuccess], timeout: 1) XCTAssertEqual(submittedEvents.count, 3) // The delete mutations should be synced in reverse order (children to parent) XCTAssertEqual(submittedEvents[0].modelName, Dish.modelName) @@ -729,7 +730,8 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } operation.syncIfNeededAndFinish(result) - await waitForExpectations(timeout: 1) + await fulfillment(of: [receivedMutationEvent, expectedFailures, expectedSuccess, completed], timeout: 1) + XCTAssertEqual(submittedEvents.count, 2) // The delete mutations should be synced in reverse order (children to parent) XCTAssertEqual(submittedEvents[0].modelName, CommentWithCompositeKey.modelName) @@ -797,7 +799,8 @@ class CascadeDeleteOperationTests: StorageEngineTestsBase { } operation.syncIfNeededAndFinish(result) - await waitForExpectations(timeout: 1) + await fulfillment(of: [receivedMutationEvent, expectedFailures, expectedSuccess, completed], timeout: 1) + XCTAssertEqual(submittedEvents.count, 3) // The delete mutations should be synced in reverse order (children to parent) XCTAssertEqual(submittedEvents[0].modelName, Dish.modelName) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEnginePublisherTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEnginePublisherTests.swift index 565bf0cbbc..3fabbb069d 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEnginePublisherTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEnginePublisherTests.swift @@ -84,11 +84,15 @@ class StorageEnginePublisherTests: StorageEngineTestsBase { storageEngine.onReceive(receiveValue: .syncStarted) storageEngine.onReceive(receiveValue: .cleanedUp) storageEngine.onReceive(receiveValue: .cleanedUpForTermination) - wait(for: [receivedMutationEvent, - receivedModelSyncedEvent, - receivedSyncQueriesReadyEvent, - receivedReadyEvent], - timeout: 1) + wait( + for: [ + receivedMutationEvent, + receivedModelSyncedEvent, + receivedSyncQueriesReadyEvent, + receivedReadyEvent + ], + timeout: 1 + ) sink.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEngineTestsBase.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEngineTestsBase.swift index abebf0a5e8..bdd3ef26ac 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEngineTestsBase.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Storage/StorageEngineTestsBase.swift @@ -47,7 +47,7 @@ class StorageEngineTestsBase: XCTestCase { result = sResult saveFinished.fulfill() } - await waitForExpectations(timeout: defaultTimeout) + await fulfillment(of: [saveFinished], timeout: defaultTimeout) guard let saveResult = result else { return .failure(causedBy: "Save operation timed out") } @@ -105,7 +105,7 @@ class StorageEngineTestsBase: XCTestCase { queryFinished.fulfill() } - await waitForExpectations(timeout: defaultTimeout) + await fulfillment(of: [queryFinished], timeout: defaultTimeout) guard let queryResult = result else { return .failure(causedBy: "Query operation timed out") } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveQueryTaskRunnerTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveQueryTaskRunnerTests.swift index 0008101492..a172663280 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveQueryTaskRunnerTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveQueryTaskRunnerTests.swift @@ -34,8 +34,8 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - The item observed will be returned in the second snapshot /// func testItemChangedWillGenerateSnapshot() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshots") - let secondSnapshot = asyncExpectation(description: "second query snapshots") + let firstSnapshot = expectation(description: "first query snapshots") + let secondSnapshot = expectation(description: "second query snapshots") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -54,10 +54,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { XCTAssertEqual(querySnapshot.items.count, 0) - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { XCTAssertEqual(querySnapshot.items.count, 1) - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } } } catch { @@ -65,11 +65,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) let post = try createPost(id: "1") dataStorePublisher.send(input: post) - await waitForExpectations([secondSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot], timeout: 10) } /// ObserveQuery will send a single snapshot when the sync state toggles @@ -83,9 +83,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - ObserveQuery will send a second snapshot /// func testGenerateSnapshotOnObserveQueryWhenModelSynced() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshots") - let secondSnapshot = asyncExpectation(description: "second query snapshots") - let thirdSnapshot = asyncExpectation(description: "third query snapshot", isInverted: true) + let firstSnapshot = expectation(description: "first query snapshots") + let secondSnapshot = expectation(description: "second query snapshots") + let thirdSnapshot = expectation(description: "third query snapshot") + thirdSnapshot.isInverted = true + let dispatchedModelSyncedEvent = AtomicValue(initialValue: false) let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, @@ -106,14 +108,14 @@ class ObserveQueryTaskRunnerTests: XCTestCase { if querySnapshots.count == 1 { XCTAssertEqual(querySnapshot.items.count, 0) XCTAssertEqual(querySnapshot.isSynced, false) - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { XCTAssertEqual(querySnapshot.items.count, 0) XCTAssertEqual(querySnapshot.isSynced, true) - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } else if querySnapshots.count == 3 { XCTFail("Should not receive third snapshot for a Model change") - await thirdSnapshot.fulfill() + thirdSnapshot.fulfill() } } } catch { @@ -121,7 +123,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 5) + await fulfillment(of: [firstSnapshot], timeout: 5) dispatchedModelSyncedEvent.set(true) let modelSyncedEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, @@ -129,12 +131,12 @@ class ObserveQueryTaskRunnerTests: XCTestCase { isDeltaSync: false, added: 0, updated: 0, deleted: 0)) Amplify.Hub.dispatch(to: .dataStore, payload: modelSyncedEventPayload) - await waitForExpectations([secondSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot], timeout: 10) let modelSyncedEventNotMatch = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, data: ModelSyncedEvent.Builder().modelName) Amplify.Hub.dispatch(to: .dataStore, payload: modelSyncedEventNotMatch) - await waitForExpectations([thirdSnapshot], timeout: 10) + await fulfillment(of: [thirdSnapshot], timeout: 10) } /// ObserveQuery will send the first snapshot with 2 items when storage engine @@ -147,7 +149,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - The items queried will return two posts in the first snapshot /// func testFirstSnapshotFromStorageQueryReturnsTwoPosts() async { - let firstSnapshot = asyncExpectation(description: "firstSnapshot received") + let firstSnapshot = expectation(description: "firstSnapshot received") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -172,7 +174,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { XCTAssertEqual(querySnapshot.items.count, 2) - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } } @@ -180,7 +182,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { XCTFail("Failed with error \(error)") } } - await waitForExpectations([firstSnapshot], timeout: 10) + await fulfillment(of: [firstSnapshot], timeout: 10) } /// Multiple item changed observed will be returned in a single snapshot @@ -192,8 +194,8 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - The items observed will be returned in the second snapshot /// func testMultipleItemChangesWillGenerateSecondSnapshot() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot") + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, @@ -213,10 +215,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { XCTAssertEqual(querySnapshot.items.count, 0) - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { XCTAssertEqual(querySnapshot.items.count, 3) - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } } } catch { @@ -224,7 +226,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) let post1 = try createPost(id: "1") let post2 = try createPost(id: "2") @@ -232,7 +234,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { dataStorePublisher.send(input: post1) dataStorePublisher.send(input: post2) dataStorePublisher.send(input: post3) - await waitForExpectations([secondSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot], timeout: 10) } /// Multiple published objects (more than the `.collect` count of 1000) in a relatively short time window @@ -247,10 +249,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// remaining in the third query /// func testCollectOverMaxItemCountLimit() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot") - let thirdSnapshot = asyncExpectation(description: "third query snapshot") - let validateSnapshotsComplete = asyncExpectation(description: "validate snapshots") + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") + let thirdSnapshot = expectation(description: "third query snapshot") + let validateSnapshotsComplete = expectation(description: "validate snapshots") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -268,11 +270,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for try await querySnapshot in snapshots { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } else if querySnapshots.count == 3 { - await thirdSnapshot.fulfill() + thirdSnapshot.fulfill() } } @@ -280,22 +282,22 @@ class ObserveQueryTaskRunnerTests: XCTestCase { XCTAssertTrue(querySnapshots[0].items.count <= querySnapshots[1].items.count) XCTAssertTrue(querySnapshots[1].items.count <= querySnapshots[2].items.count) XCTAssertTrue(querySnapshots[2].items.count <= 1_100) - await validateSnapshotsComplete.fulfill() + validateSnapshotsComplete.fulfill() } catch { XCTFail("Failed with error \(error)") } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) for postId in 1 ... 1_100 { let post = try createPost(id: "\(postId)") dataStorePublisher.send(input: post) } - await waitForExpectations([secondSnapshot, thirdSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot, thirdSnapshot], timeout: 10) snapshots.cancel() - await waitForExpectations([validateSnapshotsComplete], timeout: 1.0) + await fulfillment(of: [validateSnapshotsComplete], timeout: 1.0) } /// Cancelling the subscription will no longer receive snapshots @@ -307,8 +309,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - no further snapshots are received /// func testSuccessfulSubscriptionCancel() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot", isInverted: true) + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") + secondSnapshot.isInverted = true + let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -326,10 +330,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for try await querySnapshot in snapshots { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { XCTFail("Should not receive second snapshot after cancelling") - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } } } catch { @@ -337,11 +341,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) snapshots.cancel() let post1 = try createPost(id: "1") dataStorePublisher.send(input: post1) - await waitForExpectations([secondSnapshot], timeout: 1) + await fulfillment(of: [secondSnapshot], timeout: 1) } /// Cancelling the underlying operation will emit a completion to the subscribers @@ -353,9 +357,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - the subscriber receives a cancellation /// func testSuccessfulSequenceCancel() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot", isInverted: true) - let completedEvent = asyncExpectation(description: "should have completed") + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") + secondSnapshot.isInverted = true + + let completedEvent = expectation(description: "should have completed") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -373,29 +379,29 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for try await querySnapshot in snapshots { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { XCTFail("Should not receive second snapshot after cancelling") - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } } - await completedEvent.fulfill() + completedEvent.fulfill() } catch { XCTFail("Failed with error \(error)") } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) snapshots.cancel() let post1 = try createPost(id: "1") dataStorePublisher.send(input: post1) - await waitForExpectations([secondSnapshot, completedEvent], timeout: 1) + await fulfillment(of: [secondSnapshot, completedEvent], timeout: 1) } /// ObserveQuery's state should be able to be reset and initial query able to be started again. func testObserveQueryResetStateThenStartObserveQuery() async { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot") + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -413,9 +419,9 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for try await querySnapshot in snapshots { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } } } catch { @@ -423,10 +429,10 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) dataStoreStateSubject.send(.stop) dataStoreStateSubject.send(.start(storageEngine: storageEngine)) - await waitForExpectations([secondSnapshot], timeout: 1) + await fulfillment(of: [secondSnapshot], timeout: 1) } /// Multiple calls to start the observeQuery should not start again @@ -437,9 +443,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { /// - Then: /// - Only one query should be performed / only one snapshot should be returned func testObserveQueryStaredShouldNotStartAgain() async { - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot") - let thirdSnapshot = asyncExpectation(description: "third query snapshot", isInverted: true) + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") + let thirdSnapshot = expectation(description: "third query snapshot") + thirdSnapshot.isInverted = true + let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -457,11 +465,11 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for try await querySnapshot in snapshots { querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } else if querySnapshots.count == 3 { - await thirdSnapshot.fulfill() + thirdSnapshot.fulfill() } } } catch { @@ -469,18 +477,19 @@ class ObserveQueryTaskRunnerTests: XCTestCase { } } - await waitForExpectations([firstSnapshot], timeout: 1) + await fulfillment(of: [firstSnapshot], timeout: 1) dataStoreStateSubject.send(.stop) dataStoreStateSubject.send(.start(storageEngine: storageEngine)) dataStoreStateSubject.send(.start(storageEngine: storageEngine)) - await waitForExpectations([secondSnapshot, thirdSnapshot], timeout: 1) + await fulfillment(of: [secondSnapshot, thirdSnapshot], timeout: 1) XCTAssertTrue(taskRunner.observeQueryStarted) } /// ObserveQuery operation entry points are `resetState`, `startObserveQuery`, and `onItemChanges(mutationEvents)`. /// Ensure concurrent random sequences of these API calls do not cause issues such as data race. func testConcurrent() async { - let completeReceived = asyncExpectation(description: "complete received", isInverted: true) + let completeReceived = expectation(description: "complete received") + completeReceived.isInverted = true let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -502,7 +511,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { do { for try await _ in snapshots { } - await completeReceived.fulfill() + completeReceived.fulfill() } catch { XCTFail("Failed with error \(error)") } @@ -529,17 +538,17 @@ class ObserveQueryTaskRunnerTests: XCTestCase { for await _ in group { } } - await waitForExpectations([completeReceived], timeout: 10) + await fulfillment(of: [completeReceived], timeout: 10) } /// When a predicate like `title.beginsWith("title")` is given, the models that matched the predicate /// should be added to the snapshots, like `post` and `post2`. When `post2.title` is updated to no longer /// match the predicate, it should be removed from the snapshot. func testUpdatedModelNoLongerMatchesPredicateRemovedFromSnapshot() async throws { - let firstSnapshot = asyncExpectation(description: "first query snapshots") - let secondSnapshot = asyncExpectation(description: "second query snapshots") - let thirdSnapshot = asyncExpectation(description: "third query snapshots") - let fourthSnapshot = asyncExpectation(description: "fourth query snapshots") + let firstSnapshot = expectation(description: "first query snapshots") + let secondSnapshot = expectation(description: "second query snapshots") + let thirdSnapshot = expectation(description: "third query snapshots") + let fourthSnapshot = expectation(description: "fourth query snapshots") let taskRunner = ObserveQueryTaskRunner( modelType: Post.self, modelSchema: Post.schema, @@ -560,31 +569,31 @@ class ObserveQueryTaskRunnerTests: XCTestCase { if querySnapshots.count == 1 { // First snapshot is empty from the initial query XCTAssertEqual(querySnapshot.items.count, 0) - await firstSnapshot.fulfill() + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { // Second snapshot contains `post` since it matches the predicate XCTAssertEqual(querySnapshot.items.count, 1) XCTAssertEqual(querySnapshot.items[0].id, "1") - await secondSnapshot.fulfill() + secondSnapshot.fulfill() } else if querySnapshots.count == 3 { // Third snapshot contains both posts since they both match the predicate XCTAssertEqual(querySnapshot.items.count, 2) XCTAssertEqual(querySnapshot.items[0].id, "1") XCTAssertEqual(querySnapshot.items[1].id, "2") - await thirdSnapshot.fulfill() + thirdSnapshot.fulfill() } else if querySnapshots.count == 4 { // Fourth snapshot no longer has the post2 since it was updated to not match the predicate // and deleted at the same time. XCTAssertEqual(querySnapshot.items.count, 1) XCTAssertEqual(querySnapshot.items[0].id, "1") - await fourthSnapshot.fulfill() + fourthSnapshot.fulfill() } } } catch { XCTFail("Failed with error \(error)") } } - await waitForExpectations([firstSnapshot], timeout: 5) + await fulfillment(of: [firstSnapshot], timeout: 5) let post = try createPost(id: "1", title: "title 1") dataStorePublisher.send(input: post) @@ -593,7 +602,7 @@ class ObserveQueryTaskRunnerTests: XCTestCase { var updatedPost2 = try createPost(id: "2", title: "Does not match predicate") updatedPost2.mutationType = MutationEvent.MutationType.update.rawValue dataStorePublisher.send(input: updatedPost2) - await waitForExpectations([secondSnapshot, thirdSnapshot, fourthSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot, thirdSnapshot, fourthSnapshot], timeout: 10) } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveTaskRunnerTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveTaskRunnerTests.swift index b0c297ad89..cf6a843b52 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveTaskRunnerTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Subscribe/ObserveTaskRunnerTests.swift @@ -16,26 +16,27 @@ final class ObserveTaskRunnerTests: XCTestCase { let runner = ObserveTaskRunner(publisher: dataStorePublisher.publisher) let sequence = runner.sequence - let started = asyncExpectation(description: "started") - let mutationEventReceived = asyncExpectation(description: "mutationEvent received", - expectedFulfillmentCount: 5) - let mutationEventReceivedAfterCancel = asyncExpectation(description: "mutationEvent received", isInverted: true) - + let started = expectation(description: "started") + let mutationEventReceived = expectation(description: "mutationEvent received") + mutationEventReceived.expectedFulfillmentCount = 5 + let mutationEventReceivedAfterCancel = expectation(description: "mutationEvent received") + mutationEventReceivedAfterCancel.isInverted = true + let task = Task { do { - await started.fulfill() + started.fulfill() for try await mutationEvent in sequence { if mutationEvent.id == "id" { - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } else { - await mutationEventReceivedAfterCancel.fulfill() + mutationEventReceivedAfterCancel.fulfill() } } } catch { XCTFail("Unexpected error \(error)") } } - await waitForExpectations([started], timeout: 10.0) + await fulfillment(of: [started], timeout: 10.0) var mutationEvent = MutationEvent(id: "id", modelId: "id", modelName: "name", @@ -46,7 +47,7 @@ final class ObserveTaskRunnerTests: XCTestCase { dataStorePublisher.send(input: mutationEvent) dataStorePublisher.send(input: mutationEvent) dataStorePublisher.send(input: mutationEvent) - await waitForExpectations([mutationEventReceived], timeout: 1.0) + await fulfillment(of: [mutationEventReceived], timeout: 1.0) task.cancel() mutationEvent = MutationEvent(id: "id2", @@ -55,6 +56,6 @@ final class ObserveTaskRunnerTests: XCTestCase { json: "json", mutationType: .create) dataStorePublisher.send(input: mutationEvent) - await waitForExpectations([mutationEventReceivedAfterCancel], timeout: 1.0) + await fulfillment(of: [mutationEventReceivedAfterCancel], timeout: 1.0) } } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift index 07ea9b3984..6d85a46cf6 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift @@ -97,7 +97,16 @@ class InitialSyncOrchestratorTests: XCTestCase { syncCallbackReceived.fulfill() } - await waitForExpectations(timeout: 1) + await fulfillment( + of: [ + syncCallbackReceived, + syncQueriesStartedReceived, + syncStartedReceived, + finishedReceived, + completionFinishedReceived + ], + timeout: 1 + ) XCTAssertEqual(orchestrator.syncOperationQueue.maxConcurrentOperationCount, 1) Amplify.Hub.removeListener(hubListener) sink.cancel() @@ -196,7 +205,16 @@ class InitialSyncOrchestratorTests: XCTestCase { syncCallbackReceived.fulfill() } - await waitForExpectations(timeout: 1) + await fulfillment( + of: [ + syncCallbackReceived, + syncQueriesStartedReceived, + syncStartedReceived, + finishedReceived, + failureCompletionReceived + ], + timeout: 1 + ) XCTAssertEqual(orchestrator.syncOperationQueue.maxConcurrentOperationCount, 1) Amplify.Hub.removeListener(hubListener) sink.cancel() diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/LocalSubscriptionTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/LocalSubscriptionTests.swift index ec7d82bf7e..31abc3a127 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/LocalSubscriptionTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/LocalSubscriptionTests.swift @@ -99,13 +99,13 @@ class LocalSubscriptionTests: XCTestCase { /// - Then: /// - I receive notifications for updates to that model func testObserve() async throws { - let receivedMutationEvent = asyncExpectation(description: "Received mutation event") - + let receivedMutationEvent = expectation(description: "Received mutation event") + receivedMutationEvent.assertForOverFulfill = false let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await _ in mutationEvents { - await receivedMutationEvent.fulfill() + receivedMutationEvent.fulfill() } } catch { XCTFail("Unexpected error: \(error)") @@ -122,7 +122,7 @@ class LocalSubscriptionTests: XCTestCase { comments: []) _ = try await Amplify.DataStore.save(model) - await waitForExpectations([receivedMutationEvent], timeout: 1.0) + await fulfillment(of: [receivedMutationEvent], timeout: 1.0) subscription.cancel() } @@ -132,14 +132,14 @@ class LocalSubscriptionTests: XCTestCase { /// - Then: /// - I am notified of `create` mutations func testCreate() async throws { - let receivedMutationEvent = asyncExpectation(description: "Received mutation event") - + let receivedMutationEvent = expectation(description: "Received mutation event") + receivedMutationEvent.assertForOverFulfill = false let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.mutationType == MutationEvent.MutationType.create.rawValue { - await receivedMutationEvent.fulfill() + receivedMutationEvent.fulfill() } } } catch { @@ -157,7 +157,7 @@ class LocalSubscriptionTests: XCTestCase { comments: []) _ = try await Amplify.DataStore.save(model) - await waitForExpectations([receivedMutationEvent], timeout: 1.0) + await fulfillment(of: [receivedMutationEvent], timeout: 1.0) subscription.cancel() } @@ -185,13 +185,14 @@ class LocalSubscriptionTests: XCTestCase { newModel.content = newContent newModel.updatedAt = .now() - let receivedMutationEvent = asyncExpectation(description: "Received mutation event") + let receivedMutationEvent = expectation(description: "Received mutation event") + receivedMutationEvent.assertForOverFulfill = false let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await _ in mutationEvents { - await receivedMutationEvent.fulfill() + receivedMutationEvent.fulfill() } } catch { XCTFail("Unexpected error: \(error)") @@ -200,7 +201,7 @@ class LocalSubscriptionTests: XCTestCase { _ = try await Amplify.DataStore.save(newModel) - await waitForExpectations([receivedMutationEvent], timeout: 1.0) + await fulfillment(of: [receivedMutationEvent], timeout: 1.0) subscription.cancel() } @@ -211,14 +212,15 @@ class LocalSubscriptionTests: XCTestCase { /// - Then: /// - I am notified of `delete` mutations func testDelete() async throws { - let receivedMutationEvent = asyncExpectation(description: "Received mutation event") + let receivedMutationEvent = expectation(description: "Received mutation event") + receivedMutationEvent.assertForOverFulfill = false let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.mutationType == MutationEvent.MutationType.delete.rawValue { - await receivedMutationEvent.fulfill() + receivedMutationEvent.fulfill() } } } catch { @@ -232,7 +234,7 @@ class LocalSubscriptionTests: XCTestCase { _ = try await Amplify.DataStore.save(model) _ = try await Amplify.DataStore.delete(model) - await waitForExpectations([receivedMutationEvent], timeout: 1.0) + await fulfillment(of: [receivedMutationEvent], timeout: 1.0) subscription.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/AWSMutationEventIngesterTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/AWSMutationEventIngesterTests.swift index fdb6272621..03e9e0a56a 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/AWSMutationEventIngesterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/AWSMutationEventIngesterTests.swift @@ -96,7 +96,7 @@ class AWSMutationEventIngesterTests: XCTestCase { XCTAssert(mutationEvents.first?.json.contains(post.id) ?? false) } - wait(for: [mutationEventQueryCompleted], timeout: 1.0) + await fulfillment(of: [mutationEventQueryCompleted], timeout: 1.0) } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationEventClearStateTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationEventClearStateTests.swift index c1b26d4fdb..56860bea60 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationEventClearStateTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationEventClearStateTests.swift @@ -49,8 +49,13 @@ class MutationEventClearStateTests: XCTestCase { mutationEventClearState.clearStateOutgoingMutations { completionExpectation.fulfill() } - wait(for: [queryExpectation, - saveExpectation, - completionExpectation], timeout: 1.0) + wait( + for: [ + queryExpectation, + saveExpectation, + completionExpectation + ], + timeout: 1.0 + ) } } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationIngesterConflictResolutionTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationIngesterConflictResolutionTests.swift index 81b28af2fd..780d80425b 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationIngesterConflictResolutionTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/MutationIngesterConflictResolutionTests.swift @@ -73,7 +73,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - await waitForExpectations(timeout: 1) + await fulfillment(of: [mutationEventVerified], timeout: 1) } /// - Given: An existing MutationEvent of type .create @@ -123,7 +123,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: An existing MutationEvent of type .create @@ -161,7 +161,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } // MARK: - Existing == .update @@ -218,7 +218,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: An existing MutationEvent of type .update @@ -268,7 +268,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: An existing MutationEvent of type .update @@ -310,7 +310,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } // MARK: - Existing == .delete @@ -358,7 +358,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } // test__ @@ -408,7 +408,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } // MARK: - Empty queue tests @@ -454,7 +454,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: An empty mutation queue @@ -499,7 +499,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: An empty mutation queue @@ -539,7 +539,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - wait(for: [mutationEventVerified], timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } // MARK: - In-process queue tests @@ -579,7 +579,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: A mutation queue with an in-process .create event @@ -633,7 +633,7 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } /// - Given: A mutation queue with an in-process .create event @@ -671,7 +671,6 @@ class MutationIngesterConflictResolutionTests: SyncEngineTestBase { mutationEventVerified.fulfill() } - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [mutationEventVerified], timeout: 1.0) } - } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift index aa3ce4181b..fecedfda2b 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift @@ -118,13 +118,13 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { try await startAmplifyAndWaitForSync() // Save initial model - let createdNewItem = asyncExpectation(description: "createdNewItem") + let createdNewItem = expectation(description: "createdNewItem") let postCopy = post Task { _ = try await Amplify.DataStore.save(postCopy) - await createdNewItem.fulfill() + createdNewItem.fulfill() } - await waitForExpectations([createdNewItem]) + await fulfillment(of: [createdNewItem]) await fulfillment(of: [apiRespondedWithSuccess], timeout: 1.0, enforceOrder: false) // Set the responder to reject the mutation. Make sure to push a retry advice before sending @@ -146,13 +146,13 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { // will be scheduled and probably in "waiting" mode when we send the network unavailable // notification below. post.content = "Update 1" - let savedUpdate1 = asyncExpectation(description: "savedUpdate1") + let savedUpdate1 = expectation(description: "savedUpdate1") let postCopy1 = post Task { _ = try await Amplify.DataStore.save(postCopy1) - await savedUpdate1.fulfill() + savedUpdate1.fulfill() } - await waitForExpectations([savedUpdate1]) + await fulfillment(of: [savedUpdate1]) // At this point, the MutationEvent table (the backing store for the outgoing mutation // queue) has only a record for the interim update. It is marked as `inProcess: true`, @@ -178,7 +178,7 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { // Assert that DataStore has pushed the no-network event. This isn't strictly necessary for // correct operation of the queue. - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [networkUnavailable], timeout: 1.0) // At this point, the MutationEvent table has only a record for update1. It is marked as // `inProcess: false`, because the mutation queue has been fully cancelled by the cleanup @@ -193,13 +193,13 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { // also expect that it will be overwritten by the next mutation, without ever being synced // to the service. post.content = "Update 2" - let savedUpdate2 = asyncExpectation(description: "savedUpdate2") + let savedUpdate2 = expectation(description: "savedUpdate2") let postCopy2 = post Task { _ = try await Amplify.DataStore.save(postCopy2) - await savedUpdate2.fulfill() + savedUpdate2.fulfill() } - await waitForExpectations([savedUpdate2]) + await fulfillment(of: [savedUpdate2]) // At this point, the MutationEvent table has only a record for update2. It is marked as // `inProcess: false`, because the mutation queue has been fully cancelled. @@ -210,13 +210,13 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { // even if there were multiple not-in-process mutations, after the reconciliation completes // there would only be one record in the MutationEvent table. post.content = expectedFinalContent - let savedFinalUpdate = asyncExpectation(description: "savedFinalUpdate") + let savedFinalUpdate = expectation(description: "savedFinalUpdate") let postCopy3 = post Task { _ = try await Amplify.DataStore.save(postCopy3) - await savedFinalUpdate.fulfill() + savedFinalUpdate.fulfill() } - await waitForExpectations([savedFinalUpdate]) + await fulfillment(of: [savedFinalUpdate]) let syncStarted = expectation(description: "syncStarted") setUpSyncStartedListener( @@ -250,7 +250,8 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase { apiPlugin.responders = [.mutateRequestListener: acceptSubsequentMutations] reachabilitySubject.send(ReachabilityUpdate(isOnline: true)) - await waitForExpectations(timeout: 5.0) + + await fulfillment(of: [networkAvailableAgain, syncStarted, expectedFinalContentReceived, outboxEmpty], timeout: 5.0) } // MARK: - Utilities diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift index 312480b64b..24f0d5a131 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift @@ -81,15 +81,22 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { try await startAmplifyAndWaitForSync() - let saveSuccess = asyncExpectation(description: "save success") + let saveSuccess = expectation(description: "save success") Task { _ = try await Amplify.DataStore.save(post) - await saveSuccess.fulfill() + saveSuccess.fulfill() } - await waitForExpectations([saveSuccess], timeout: 1.0) - - - await waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [saveSuccess], timeout: 1.0) + + await fulfillment( + of: [ + outboxStatusOnStart, + outboxStatusOnMutationEnqueued, + outboxMutationEnqueued, + createMutationSent + ], + timeout: 5.0 + ) Amplify.Hub.removeListener(hubListener) } @@ -142,7 +149,7 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { } - wait(for: [mutationEventSaved], timeout: 1.0) + await fulfillment(of: [mutationEventSaved], timeout: 1.0) var outboxStatusReceivedCurrentCount = 0 let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received") @@ -188,7 +195,18 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { try await startAmplify() } - await waitForExpectations(timeout: 5.0, handler: nil) + + + await fulfillment( + of: [ + outboxStatusOnStart, + outboxStatusOnMutationEnqueued, + mutation1Sent, + mutation2Sent + ], + timeout: 5.0 + ) + Amplify.Hub.removeListener(hubListener) } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift index 71faf036bc..263a6b52f6 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift @@ -75,7 +75,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { completion: completion) let queue = OperationQueue() queue.addOperation(operation) - wait(for: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else { XCTFail("Listener was not called through MockAPICategoryPlugin") return @@ -83,7 +83,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { let urlError = URLError(URLError.notConnectedToInternet) listenerFromFirstRequest(.failure(APIError.networkError("mock NotConnectedToInternetError", nil, urlError))) - wait(for: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout) guard let listenerFromSecondRequest = listenerFromSecondRequestOptional else { XCTFail("Listener was not called through MockAPICategoryPlugin") @@ -100,7 +100,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata) listenerFromSecondRequest(.success(.success(remoteMutationSync))) // waitForExpectations(timeout: 1) - wait(for: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout) } func testRetryOnChangeReachability() async throws { @@ -148,7 +148,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { completion: completion) let queue = OperationQueue() queue.addOperation(operation) - wait(for: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else { XCTFail("Listener was not called through MockAPICategoryPlugin") return @@ -158,7 +158,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { listenerFromFirstRequest(.failure(APIError.networkError("mock NotConnectedToInternetError", nil, urlError))) reachabilityPublisher.send(ReachabilityUpdate(isOnline: true)) - wait(for: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout) guard let listenerFromSecondRequest = listenerFromSecondRequestOptional else { XCTFail("Listener was not called through MockAPICategoryPlugin") return @@ -173,7 +173,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { version: 2) let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata) listenerFromSecondRequest(.success(.success(remoteMutationSync))) - wait(for: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout) } func testAbilityToCancel() async throws { @@ -221,7 +221,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { completion: completion) let queue = OperationQueue() queue.addOperation(operation) - wait(for: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout) guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else { XCTFail("Listener was not called through MockAPICategoryPlugin") return @@ -232,7 +232,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { // At this point, we will be "waiting forever" to retry our request or until the operation is canceled operation.cancel() - wait(for: [expectMutationRequestFailed], timeout: defaultAsyncWaitTimeout) + await fulfillment(of: [expectMutationRequestFailed], timeout: defaultAsyncWaitTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift index ebf43ea43b..0879f7d345 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift @@ -104,15 +104,22 @@ class RemoteSyncAPIInvocationTests: XCTestCase { } try Amplify.configure(amplifyConfig) - let startSuccess = asyncExpectation(description: "start success") + let startSuccess = expectation(description: "start success") Task { _ = try await Amplify.DataStore.start() - await startSuccess.fulfill() + startSuccess.fulfill() } - await waitForExpectations([startSuccess], timeout: 1.0) - - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [startSuccess], timeout: 1.0) + await fulfillment( + of: [ + createSubscriptionStarted, + updateSubscriptionStarted, + deleteSubscriptionStarted + ], + timeout: 1.0 + ) } + // TODO: Implement the test below /// - Given: Amplify configured with an API diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSyncEngineTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSyncEngineTests.swift index 8994b9a914..da315bde7e 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSyncEngineTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/RemoteSyncEngineTests.swift @@ -130,7 +130,20 @@ class RemoteSyncEngineTests: XCTestCase { remoteSyncEngine.start(api: apiPlugin, auth: nil) - await waitForExpectations(timeout: defaultAsyncWaitTimeout) + await fulfillment( + of: [ + storageAdapterAvailable, + subscriptionsPaused, + mutationsPaused, + stateMutationsCleared, + subscriptionsInitialized, + subscriptionsEstablishedReceived, + cleanedup, + failureOnInitialSync, + retryAdviceReceivedNetworkError + ], + timeout: defaultAsyncWaitTimeout + ) remoteSyncEngineSink.cancel() Amplify.Hub.removeListener(hubListener) } @@ -182,15 +195,20 @@ class RemoteSyncEngineTests: XCTestCase { remoteSyncEngine.start(api: apiPlugin, auth: nil) - wait(for: [storageAdapterAvailable, - subscriptionsPaused, - mutationsPaused, - stateMutationsCleared, - subscriptionsInitialized, - performedInitialSync, - subscriptionActivation, - mutationQueueStarted, - syncStarted], timeout: defaultAsyncWaitTimeout) + wait( + for: [ + storageAdapterAvailable, + subscriptionsPaused, + mutationsPaused, + stateMutationsCleared, + subscriptionsInitialized, + performedInitialSync, + subscriptionActivation, + mutationQueueStarted, + syncStarted + ], + timeout: defaultAsyncWaitTimeout + ) remoteSyncEngineSink.cancel() } @@ -253,17 +271,22 @@ class RemoteSyncEngineTests: XCTestCase { remoteSyncEngine.start(api: apiPlugin, auth: nil) - wait(for: [storageAdapterAvailable, - subscriptionsPaused, - mutationsPaused, - stateMutationsCleared, - subscriptionsInitialized, - performedInitialSync, - subscriptionActivation, - mutationQueueStarted, - syncStarted, - cleanedUp, - forceFailToNotRestartSyncEngine], timeout: defaultAsyncWaitTimeout) + wait( + for: [ + storageAdapterAvailable, + subscriptionsPaused, + mutationsPaused, + stateMutationsCleared, + subscriptionsInitialized, + performedInitialSync, + subscriptionActivation, + mutationQueueStarted, + syncStarted, + cleanedUp, + forceFailToNotRestartSyncEngine + ], + timeout: defaultAsyncWaitTimeout + ) remoteSyncEngineSink.cancel() } @@ -330,18 +353,23 @@ class RemoteSyncEngineTests: XCTestCase { remoteSyncEngine.start(api: apiPlugin, auth: nil) - wait(for: [storageAdapterAvailable, - subscriptionsPaused, - mutationsPaused, - stateMutationsCleared, - subscriptionsInitialized, - performedInitialSync, - subscriptionActivation, - mutationQueueStarted, - syncStarted, - cleanedUpForTermination, - completionBlockCalled, - forceFailToNotRestartSyncEngine], timeout: defaultAsyncWaitTimeout) + wait( + for: [ + storageAdapterAvailable, + subscriptionsPaused, + mutationsPaused, + stateMutationsCleared, + subscriptionsInitialized, + performedInitialSync, + subscriptionActivation, + mutationQueueStarted, + syncStarted, + cleanedUpForTermination, + completionBlockCalled, + forceFailToNotRestartSyncEngine + ], + timeout: defaultAsyncWaitTimeout + ) remoteSyncEngineSink.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueueTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueueTests.swift index b10e865a7d..7969b27416 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueueTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueueTests.swift @@ -78,8 +78,8 @@ class AWSIncomingEventReconciliationQueueTests: XCTestCase { operationQueue.addOperation(cancellableOperation) } operationQueue.isSuspended = false - await waitForExpectations(timeout: 2) + await fulfillment(of: [expectStarted, expectInitialized], timeout: 2) // Take action on the sink to prevent compiler warnings about unused variables. sink.cancel() } @@ -113,8 +113,8 @@ class AWSIncomingEventReconciliationQueueTests: XCTestCase { operationQueue.addOperation(cancellableOperation) } operationQueue.isSuspended = false - await waitForExpectations(timeout: 2) + await fulfillment(of: [expectStarted, expectInitialized], timeout: 2) sink.cancel() } @@ -152,8 +152,8 @@ class AWSIncomingEventReconciliationQueueTests: XCTestCase { operationQueue.addOperation(cancellableOperation) } operationQueue.isSuspended = false - await waitForExpectations(timeout: 2) + await fulfillment(of: [expectStarted, expectInitialized], timeout: 2) sink.cancel() } @@ -187,8 +187,8 @@ class AWSIncomingEventReconciliationQueueTests: XCTestCase { operationQueue.addOperation(cancellableOperation) } operationQueue.isSuspended = false - await waitForExpectations(timeout: 2) + await fulfillment(of: [expectStarted, expectInitialized], timeout: 2) sink.cancel() } @@ -228,8 +228,8 @@ class AWSIncomingEventReconciliationQueueTests: XCTestCase { operationQueue.addOperation(cancellableOperation) } operationQueue.isSuspended = false - await waitForExpectations(timeout: 2) + await fulfillment(of: [expectStarted, expectInitialized], timeout: 2) sink.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift index 0408f19ca2..b1b3d978bd 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift @@ -38,7 +38,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase { version: 2) let localMetadataSaved = expectation(description: "Local metadata saved") storageAdapter.save(localSyncMetadata) { _ in localMetadataSaved.fulfill() } - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [localMetadataSaved], timeout: 1) var valueListenerFromRequest: MutationSyncInProcessListener? let expectationListener = expectation(description: "listener") @@ -57,8 +57,8 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase { mockRemoteSyncEngineFor_testUpdateAfterDelete() try await startAmplifyAndWaitForSync() } - await waitForExpectations(timeout: 2.0) + await fulfillment(of: [expectationListener], timeout: 2) guard let valueListener = valueListenerFromRequest else { XCTFail("Incoming responder didn't set up listener") return @@ -149,8 +149,8 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase { mockRemoteSyncEngineFor_testDeleteWithNoLocalModel() try await startAmplifyAndWaitForSync() } - await waitForExpectations(timeout: 1) - + + await fulfillment(of: [expectationListener], timeout: 1) guard let valueListener = valueListenerFromRequest else { XCTFail("Incoming responder didn't set up listener") return @@ -176,8 +176,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase { let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata) valueListener(.data(.success(remoteMutationSync))) - await waitForExpectations(timeout: 1) - + await fulfillment(of: [syncReceivedNotification], timeout: 1) let finalLocalMetadata = try storageAdapter.queryMutationSyncMetadata(for: model.id, modelName: MockSynced.modelName) XCTAssertEqual(finalLocalMetadata?.version, 2) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationQueueBehaviorTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationQueueBehaviorTests.swift index f41c69de1a..34e381fba5 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationQueueBehaviorTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationQueueBehaviorTests.swift @@ -51,7 +51,7 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { subscriptionEventsSubject.send(.mutationEvent(mutationSync)) } - wait(for: [eventsNotSaved], timeout: 5.0) + await fulfillment(of: [eventsNotSaved], timeout: 5.0) } /// - Given: An AWSModelReconciliationQueue that has been buffering events @@ -120,7 +120,17 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { queue.start() - await waitForExpectations(timeout: 5.0) + await fulfillment( + of: [ + event1Saved, + event2Saved, + event3Saved, + eventsSentViaPublisher1, + eventsSentViaPublisher2, + eventsSentViaPublisher3 + ], + timeout: 5 + ) queueSink.cancel() } @@ -190,7 +200,15 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { queue.start() - await waitForExpectations(timeout: 5.0) + await fulfillment( + of: [ + event1Saved, + event3Saved, + eventsSentViaPublisher1, + eventsSentViaPublisher3 + ], + timeout: 5 + ) queueSink.cancel() } @@ -283,7 +301,15 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { queue.start() - await waitForExpectations(timeout: 5.0) + await fulfillment( + of: [ + eventsSentViaPublisher1, + eventsSentViaPublisher2, + eventsSentViaPublisher3, + allEventsProcessed + ], + timeout: 5 + ) queueSink.cancel() } @@ -351,7 +377,15 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { queue.start() - await waitForExpectations(timeout: 1.0) + await fulfillment( + of: [ + event1ShouldBeProcessed, + event2ShouldBeProcessed, + eventsSentViaPublisher1, + eventsSentViaPublisher2 + ], + timeout: 1 + ) let event1ShouldNotBeProcessed = expectation(description: "Event 1 should not be processed") event1ShouldNotBeProcessed.isInverted = true @@ -393,7 +427,15 @@ class ModelReconciliationQueueBehaviorTests: ReconciliationQueueTestBase { let mutationSync = MutationSync(model: model, syncMetadata: syncMetadata) subscriptionEventsSubject.send(.mutationEvent(mutationSync)) - await waitForExpectations(timeout: 1.0) + await fulfillment( + of: [ + event1ShouldNotBeProcessed, + event2ShouldNotBeProcessed, + event3ShouldBeProcessed, + eventsSentViaPublisher3 + ], + timeout: 1 + ) queueSink.cancel() } @@ -437,7 +479,7 @@ extension ModelReconciliationQueueBehaviorTests { }) subscriptionEventsSubject.send(completion: completion) - wait(for: [eventSentViaPublisher], timeout: 1.0) + await fulfillment(of: [eventSentViaPublisher], timeout: 1.0) queueSink.cancel() } @@ -465,7 +507,7 @@ extension ModelReconciliationQueueBehaviorTests { }) subscriptionEventsSubject.send(completion: completion) - wait(for: [eventSentViaPublisher], timeout: 1.0) + await fulfillment(of: [eventSentViaPublisher], timeout: 1.0) queueSink.cancel() } @@ -493,7 +535,7 @@ extension ModelReconciliationQueueBehaviorTests { }) subscriptionEventsSubject.send(completion: completion) - wait(for: [eventSentViaPublisher], timeout: 1.0) + await fulfillment(of: [eventSentViaPublisher], timeout: 1.0) queueSink.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/LocalStoreIntegrationTestBase.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/LocalStoreIntegrationTestBase.swift index af8e3a5849..645a4df4ad 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/LocalStoreIntegrationTestBase.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/LocalStoreIntegrationTestBase.swift @@ -30,13 +30,13 @@ class LocalStoreIntegrationTestBase: XCTestCase { } override func tearDown() async throws { - let clearComplete = asyncExpectation(description: "clear completed") + let clearComplete = expectation(description: "clear completed") Task { try await Amplify.DataStore.clear() - await clearComplete.fulfill() + clearComplete.fulfill() } - await waitForExpectations([clearComplete], timeout: 5) + await fulfillment(of: [clearComplete], timeout: 5) await Amplify.reset() } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreAuthBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreAuthBaseTest.swift index 6c390216e3..f1615670ec 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreAuthBaseTest.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreAuthBaseTest.swift @@ -85,18 +85,18 @@ class AWSDataStoreAuthBaseTest: XCTestCase { // MARK: - Test Helpers func makeExpectations() -> AuthTestExpectations { AuthTestExpectations( - subscriptionsEstablished: asyncExpectation(description: "Subscriptions established"), - modelsSynced: asyncExpectation(description: "Models synced"), + subscriptionsEstablished: expectation(description: "Subscriptions established"), + modelsSynced: expectation(description: "Models synced"), - query: asyncExpectation(description: "Query success"), + query: expectation(description: "Query success"), - mutationSave: asyncExpectation(description: "Mutation save success"), - mutationSaveProcessed: asyncExpectation(description: "Mutation save processed"), + mutationSave: expectation(description: "Mutation save success"), + mutationSaveProcessed: expectation(description: "Mutation save processed"), - mutationDelete: asyncExpectation(description: "Mutation delete success"), - mutationDeleteProcessed: asyncExpectation(description: "Mutation delete processed"), + mutationDelete: expectation(description: "Mutation delete success"), + mutationDeleteProcessed: expectation(description: "Mutation delete processed"), - ready: asyncExpectation(description: "Ready") + ready: expectation(description: "Ready") ) } @@ -292,12 +292,12 @@ extension AWSDataStoreAuthBaseTest { do { let posts = try await Amplify.DataStore.query(modelType) XCTAssertNotNil(posts) - await expectations.query.fulfill() + expectations.query.fulfill() } catch { onFailure(error as! DataStoreError) } } - await waitForExpectations([expectations.query], timeout: 60) + await fulfillment(of: [expectations.query], timeout: 60) } /// Asserts that DataStore is in a ready state and subscriptions are established @@ -315,25 +315,25 @@ extension AWSDataStoreAuthBaseTest { .sink { event in // subscription fulfilled if event.eventName == dataStoreEvents.subscriptionsEstablished { - Task { await expectations.subscriptionsEstablished.fulfill() } + expectations.subscriptionsEstablished.fulfill() } // modelsSynced fulfilled if event.eventName == dataStoreEvents.modelSynced { modelSyncedCount += 1 if modelSyncedCount == expectedModelSynced { - Task { await expectations.modelsSynced.fulfill() } + expectations.modelsSynced.fulfill() } } if event.eventName == dataStoreEvents.ready { - Task { await expectations.ready.fulfill() } + expectations.ready.fulfill() } } .store(in: &requests) try await Amplify.DataStore.start() - await waitForExpectations([expectations.subscriptionsEstablished, + await fulfillment(of: [expectations.subscriptionsEstablished, expectations.modelsSynced, expectations.ready], timeout: 60) @@ -361,12 +361,12 @@ extension AWSDataStoreAuthBaseTest { } if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { - Task { await expectations.mutationSaveProcessed.fulfill() } + expectations.mutationSaveProcessed.fulfill() return } if mutationEvent.mutationType == GraphQLMutationType.delete.rawValue { - Task { await expectations.mutationDeleteProcessed.fulfill() } + expectations.mutationDeleteProcessed.fulfill() return } } @@ -375,23 +375,23 @@ extension AWSDataStoreAuthBaseTest { do { let posts = try await Amplify.DataStore.save(model) XCTAssertNotNil(posts) - Task { await expectations.mutationSave.fulfill() } + expectations.mutationSave.fulfill() } catch let error as DataStoreError { onFailure(error) } } - await waitForExpectations([expectations.mutationSave, + await fulfillment(of: [expectations.mutationSave, expectations.mutationSaveProcessed], timeout: 60) Task { do { let deletedposts: () = try await Amplify.DataStore.delete(model) XCTAssertNotNil(deletedposts) - Task { await expectations.mutationDelete.fulfill() } + expectations.mutationDelete.fulfill() } catch let error as DataStoreError { onFailure(error) } } - await waitForExpectations([expectations.mutationDelete, + await fulfillment(of: [expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60) } @@ -408,15 +408,15 @@ extension AWSDataStoreAuthBaseTest { // MARK: - Expectations extension AWSDataStoreAuthBaseTest { struct AuthTestExpectations { - var subscriptionsEstablished: AsyncExpectation - var modelsSynced: AsyncExpectation - var query: AsyncExpectation - var mutationSave: AsyncExpectation - var mutationSaveProcessed: AsyncExpectation - var mutationDelete: AsyncExpectation - var mutationDeleteProcessed: AsyncExpectation - var ready: AsyncExpectation - var expectations: [AsyncExpectation] { + var subscriptionsEstablished: XCTestExpectation + var modelsSynced: XCTestExpectation + var query: XCTestExpectation + var mutationSave: XCTestExpectation + var mutationSaveProcessed: XCTestExpectation + var mutationDelete: XCTestExpectation + var mutationDeleteProcessed: XCTestExpectation + var ready: XCTestExpectation + var expectations: [XCTestExpectation] { return [subscriptionsEstablished, modelsSynced, query, diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests+Support.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests+Support.swift index bc9396f2cf..b2292f96ed 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests+Support.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests+Support.swift @@ -14,17 +14,17 @@ import DataStoreHostApp extension AWSDataStoreCategoryPluginAuthIntegrationTests { func saveModel(_ model: T) async throws { - let localSaveInvoked = asyncExpectation(description: "local model was saved") + let localSaveInvoked = expectation(description: "local model was saved") Task { do { let savedposts = try await Amplify.DataStore.save(model) print("Local model was saved: \(savedposts)") - await localSaveInvoked.fulfill() + localSaveInvoked.fulfill() } catch { XCTFail("Failed to save model \(error)") throw error } } - await waitForExpectations([localSaveInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [localSaveInvoked], timeout: TestCommonConstants.networkTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests.swift index 0e28f6f5c7..eefc8b2dd0 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthCognitoTests/AWSDataStoreCategoryPluginAuthIntegrationTests.swift @@ -40,7 +40,7 @@ class AWSDataStoreCategoryPluginAuthIntegrationTests: AWSDataStoreAuthBaseTest { return } - let syncReceivedInvoked = asyncExpectation(description: "Received SyncReceived event") + let syncReceivedInvoked = expectation(description: "Received SyncReceived event") var remoteTodoOptional: TodoExplicitOwnerField? let syncReceivedListener = Amplify.Hub.listen(to: .dataStore, eventName: syncReceived) { payload in guard let mutationEvent = payload.data as? MutationEvent, @@ -50,7 +50,7 @@ class AWSDataStoreCategoryPluginAuthIntegrationTests: AWSDataStoreAuthBaseTest { } if todo.id == savedLocalTodo.id { remoteTodoOptional = todo - Task { await syncReceivedInvoked.fulfill() } + syncReceivedInvoked.fulfill() } } guard try await HubListenerTestUtilities.waitForListener(with: syncReceivedListener, timeout: 5.0) else { @@ -60,7 +60,7 @@ class AWSDataStoreCategoryPluginAuthIntegrationTests: AWSDataStoreAuthBaseTest { try await signIn(user: user1) - await waitForExpectations([syncReceivedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [syncReceivedInvoked], timeout: 60) Amplify.Hub.removeListener(syncReceivedListener) guard let remoteTodo = remoteTodoOptional else { XCTFail("Should have received a SyncReceived event with the remote note reconciled to local store") diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreAuthBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreAuthBaseTest.swift index ca4ea667e9..885e9e590c 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreAuthBaseTest.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreAuthBaseTest.swift @@ -117,18 +117,18 @@ class AWSDataStoreAuthBaseTest: XCTestCase { // MARK: - Test Helpers func makeExpectations() -> AuthTestExpectations { AuthTestExpectations( - subscriptionsEstablished: AsyncExpectation(description: "Subscriptions established"), - modelsSynced: AsyncExpectation(description: "Models synced"), + subscriptionsEstablished: expectation(description: "Subscriptions established"), + modelsSynced: expectation(description: "Models synced"), - query: AsyncExpectation(description: "Query success"), + query: expectation(description: "Query success"), - mutationSave: AsyncExpectation(description: "Mutation save success"), - mutationSaveProcessed: AsyncExpectation(description: "Mutation save processed"), + mutationSave: expectation(description: "Mutation save success"), + mutationSaveProcessed: expectation(description: "Mutation save processed"), - mutationDelete: AsyncExpectation(description: "Mutation delete success"), - mutationDeleteProcessed: AsyncExpectation(description: "Mutation delete processed"), + mutationDelete: expectation(description: "Mutation delete success"), + mutationDeleteProcessed: expectation(description: "Mutation delete processed"), - ready: AsyncExpectation(description: "Ready") + ready: expectation(description: "Ready") ) } @@ -238,21 +238,17 @@ extension AWSDataStoreAuthBaseTest { XCTFail("Invalid user", file: file, line: line) return } - let signInInvoked = AsyncExpectation(description: "sign in completed") + let signInInvoked = expectation(description: "sign in completed") do { _ = try await Amplify.Auth.signIn(username: user.username, password: user.password, options: nil) - Task { - await signInInvoked.fulfill() - } + signInInvoked.fulfill() } catch(let error) { XCTFail("Signin failure \(error)", file: file, line: line) - Task { - await signInInvoked.fulfill() // won't count as pass - } + signInInvoked.fulfill() // won't count as pass } - await waitForExpectations([signInInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [signInInvoked], timeout: TestCommonConstants.networkTimeout) let signedIn = await isSignedIn() XCTAssert(signedIn, file: file, line: line) @@ -261,32 +257,30 @@ extension AWSDataStoreAuthBaseTest { /// Signout current signed-in user func signOut(file: StaticString = #file, line: UInt = #line) async { - let signoutInvoked = AsyncExpectation(description: "sign out completed") + let signoutInvoked = expectation(description: "sign out completed") Task { _ = await Amplify.Auth.signOut() - await signoutInvoked.fulfill() + signoutInvoked.fulfill() } - await waitForExpectations([signoutInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [signoutInvoked], timeout: TestCommonConstants.networkTimeout) let signedIn = await isSignedIn() XCTAssert(!signedIn, file: file, line: line) } func isSignedIn() async -> Bool { - let checkIsSignedInCompleted = AsyncExpectation(description: "retrieve auth session completed") + let checkIsSignedInCompleted = expectation(description: "retrieve auth session completed") var resultOptional: Bool? do { let authSession = try await Amplify.Auth.fetchAuthSession() resultOptional = authSession.isSignedIn - Task { - await checkIsSignedInCompleted.fulfill() - } + checkIsSignedInCompleted.fulfill() } catch(let error) { fatalError("Failed to get auth session \(error)") } - await waitForExpectations([checkIsSignedInCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [checkIsSignedInCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get isSignedIn for user") return false @@ -296,7 +290,7 @@ extension AWSDataStoreAuthBaseTest { } func getUserSub() async -> String { - let retrieveUserSubCompleted = AsyncExpectation(description: "retrieve userSub completed") + let retrieveUserSubCompleted = expectation(description: "retrieve userSub completed") var resultOptional: String? do { let authSession = try await Amplify.Auth.fetchAuthSession() @@ -307,9 +301,7 @@ extension AWSDataStoreAuthBaseTest { switch cognitoAuthSession.getUserSub() { case .success(let userSub): resultOptional = userSub - Task { - await retrieveUserSubCompleted.fulfill() - } + retrieveUserSubCompleted.fulfill() case .failure(let error): XCTFail("Failed to get auth session \(error)") } @@ -317,7 +309,7 @@ extension AWSDataStoreAuthBaseTest { XCTFail("Failed to get auth session \(error)") } - await waitForExpectations([retrieveUserSubCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [retrieveUserSubCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get userSub for user") return "" @@ -327,7 +319,7 @@ extension AWSDataStoreAuthBaseTest { } func getIdentityId() async -> String { - let retrieveIdentityCompleted = AsyncExpectation(description: "retrieve identity completed") + let retrieveIdentityCompleted = expectation(description: "retrieve identity completed") var resultOptional: String? do { let authSession = try await Amplify.Auth.fetchAuthSession() @@ -338,16 +330,14 @@ extension AWSDataStoreAuthBaseTest { switch cognitoAuthSession.getIdentityId() { case .success(let identityId): resultOptional = identityId - Task { - await retrieveIdentityCompleted.fulfill() - } + retrieveIdentityCompleted.fulfill() case .failure(let error): XCTFail("Failed to get auth session \(error)") } } catch(let error) { XCTFail("Failed to get auth session \(error)") } - await waitForExpectations([retrieveIdentityCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [retrieveIdentityCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get identityId for user") return "" @@ -361,19 +351,17 @@ extension AWSDataStoreAuthBaseTest { file: StaticString = #file, line: UInt = #line) async -> M? { var queriedModel: M? - let queriedInvoked = AsyncExpectation(description: "Model queried") + let queriedInvoked = expectation(description: "Model queried") do { let model = try await Amplify.DataStore.query(M.self, byId: id) queriedModel = model - Task { - await queriedInvoked.fulfill() - } + queriedInvoked.fulfill() } catch(let error) { XCTFail("Failed to query model \(error)", file: file, line: line) } - await waitForExpectations([queriedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [queriedInvoked], timeout: TestCommonConstants.networkTimeout) return queriedModel } } @@ -397,11 +385,9 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.query.fulfill() - } + expectations.query.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.query], + await fulfillment(of: [expectations.query], timeout: 60) } @@ -417,25 +403,19 @@ extension AWSDataStoreAuthBaseTest { .sink { event in // subscription fulfilled if event.eventName == dataStoreEvents.subscriptionsEstablished { - Task { - await expectations.subscriptionsEstablished.fulfill() - } + expectations.subscriptionsEstablished.fulfill() } // modelsSynced fulfilled if event.eventName == dataStoreEvents.modelSynced { modelSyncedCount += 1 if modelSyncedCount == expectedModelSynced { - Task { - await expectations.modelsSynced.fulfill() - } + expectations.modelsSynced.fulfill() } } if event.eventName == dataStoreEvents.ready { - Task { - await expectations.ready.fulfill() - } + expectations.ready.fulfill() } } .store(in: &requests) @@ -445,7 +425,7 @@ extension AWSDataStoreAuthBaseTest { } catch(let error) { XCTFail("Failure due to error: \(error)") } - await waitForExpectations([expectations.subscriptionsEstablished, + await fulfillment(of: [expectations.subscriptionsEstablished, expectations.modelsSynced, expectations.ready], timeout: 60) @@ -470,16 +450,12 @@ extension AWSDataStoreAuthBaseTest { } if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { - Task { - await expectations.mutationSaveProcessed.fulfill() - } + expectations.mutationSaveProcessed.fulfill() return } if mutationEvent.mutationType == GraphQLMutationType.delete.rawValue { - Task { - await expectations.mutationDeleteProcessed.fulfill() - } + expectations.mutationDeleteProcessed.fulfill() return } } @@ -494,12 +470,10 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.mutationSave.fulfill() - } + expectations.mutationSave.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.mutationSave, expectations.mutationSaveProcessed], timeout: 60) + await fulfillment(of: [expectations.mutationSave, expectations.mutationSaveProcessed], timeout: 60) Amplify.Publisher.create { try await Amplify.DataStore.delete(model) @@ -510,12 +484,10 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.mutationDelete.fulfill() - } + expectations.mutationDelete.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60) + await fulfillment(of: [expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60) } func assertUsedAuthTypes( @@ -545,15 +517,15 @@ extension AWSDataStoreAuthBaseTest { // MARK: - Expectations extension AWSDataStoreAuthBaseTest { struct AuthTestExpectations { - var subscriptionsEstablished: AsyncExpectation - var modelsSynced: AsyncExpectation - var query: AsyncExpectation - var mutationSave: AsyncExpectation - var mutationSaveProcessed: AsyncExpectation - var mutationDelete: AsyncExpectation - var mutationDeleteProcessed: AsyncExpectation - var ready: AsyncExpectation - var expectations: [AsyncExpectation] { + var subscriptionsEstablished: XCTestExpectation + var modelsSynced: XCTestExpectation + var query: XCTestExpectation + var mutationSave: XCTestExpectation + var mutationSaveProcessed: XCTestExpectation + var mutationDelete: XCTestExpectation + var mutationDeleteProcessed: XCTestExpectation + var ready: XCTestExpectation + var expectations: [XCTestExpectation] { return [subscriptionsEstablished, modelsSynced, query, diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginCPKTests/PrimaryKey/AWSDataStorePrimaryKeyBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginCPKTests/PrimaryKey/AWSDataStorePrimaryKeyBaseTest.swift index 1b562be5c1..781d532348 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginCPKTests/PrimaryKey/AWSDataStorePrimaryKeyBaseTest.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginCPKTests/PrimaryKey/AWSDataStorePrimaryKeyBaseTest.swift @@ -121,8 +121,14 @@ extension AWSDataStorePrimaryKeyBaseTest { .store(in: &requests) try await Amplify.DataStore.start() - - await waitForExpectations(timeout: 60) + await fulfillment( + of: [ + ready, + subscriptionsEstablished, + modelsSynced + ], + timeout: 60 + ) } /// Assert that a save and a delete mutation complete successfully. @@ -152,8 +158,11 @@ extension AWSDataStorePrimaryKeyBaseTest { let savedModels = try await Amplify.DataStore.save(model) XCTAssertNotNil(savedModels) - await waitForExpectations(timeout: 60) - + await fulfillment( + of: [mutationSaveProcessed], + timeout: 60 + ) + let mutationDeleteProcessed = expectation(description: "mutation delete processed") Amplify .Hub @@ -173,7 +182,10 @@ extension AWSDataStorePrimaryKeyBaseTest { .store(in: &requests) try await Amplify.DataStore.delete(model) - await waitForExpectations(timeout: 60) + await fulfillment( + of: [mutationDeleteProcessed], + timeout: 60 + ) } /// Assert that a save and a delete mutation complete successfully. @@ -211,8 +223,8 @@ extension AWSDataStorePrimaryKeyBaseTest { // save child _ = try await Amplify.DataStore.save(child) - await waitForExpectations(timeout: 60) - + await fulfillment(of: [mutationSaveProcessed], timeout: 60) + guard shouldDeleteParent else { return } @@ -242,6 +254,6 @@ extension AWSDataStorePrimaryKeyBaseTest { // delete parent try await Amplify.DataStore.delete(parent) - await waitForExpectations(timeout: 60) + await fulfillment(of: [mutationDeleteProcessed], timeout: 60) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario1FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario1FlutterTests.swift index f483310a97..de1c6d6309 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario1FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario1FlutterTests.swift @@ -62,7 +62,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveTeamCompleted, syncedTeamReceived], timeout: networkTimeout) + await fulfillment(of: [saveTeamCompleted, syncedTeamReceived], timeout: networkTimeout) let saveProjectCompleted = expectation(description: "save project completed") plugin.save(project.model, modelSchema: Project1.schema) { result in switch result { @@ -72,7 +72,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveProjectCompleted, syncProjectReceived], timeout: networkTimeout) + await fulfillment(of: [saveProjectCompleted, syncProjectReceived], timeout: networkTimeout) let queriedProjectCompleted = expectation(description: "query project completed") plugin.query(FlutterSerializedModel.self, modelSchema: Project1.schema, where: Project1.keys.id.eq(project.model.id)) { result in switch result { @@ -91,7 +91,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [queriedProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [queriedProjectCompleted], timeout: networkTimeout) } func testUpdateProjectWithAnotherTeam() throws { @@ -127,7 +127,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveTeamCompleted], timeout: networkTimeout) + await fulfillment(of: [saveTeamCompleted], timeout: networkTimeout) let saveAnotherTeamCompleted = expectation(description: "save team completed") plugin.save(anotherTeam.model, modelSchema: Team1.schema) { result in switch result { @@ -137,7 +137,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveAnotherTeamCompleted], timeout: networkTimeout) + await fulfillment(of: [saveAnotherTeamCompleted], timeout: networkTimeout) let saveProjectCompleted = expectation(description: "save project completed") plugin.save(project.model, modelSchema: Project1.schema) { result in switch result { @@ -147,7 +147,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [saveProjectCompleted], timeout: networkTimeout) let updateProjectCompleted = expectation(description: "save project completed") try project.setTeam(team: anotherTeam.model) plugin.save(project.model, modelSchema: Project1.schema) { result in @@ -158,7 +158,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [updateProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [updateProjectCompleted], timeout: networkTimeout) let queriedProjectCompleted = expectation(description: "query project completed") plugin.query(FlutterSerializedModel.self, modelSchema: Project1.schema, where: Project1.keys.id.eq(project.model.id)) { result in switch result { @@ -175,7 +175,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [queriedProjectCompleted, syncUpdatedProjectReceived], timeout: networkTimeout) + await fulfillment(of: [queriedProjectCompleted, syncUpdatedProjectReceived], timeout: networkTimeout) } func testDeleteAndGetProject() throws { @@ -195,7 +195,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Project1.schema, where: Project1.keys.id.eq(project.model.id)) { result in switch result { @@ -206,7 +206,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testDeleteWithValidCondition() throws { @@ -225,7 +225,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest if queriedProjectExpect1!.count == 1 { queriedProjectExpect1Successful.fulfill() } - wait(for: [queriedProjectExpect1Successful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [queriedProjectExpect1Successful], timeout: TestCommonConstants.networkTimeout) let deleteProjectSuccessful = expectation(description: "delete project") plugin.delete(project.model, modelSchema: Project1.schema, where: Project1.keys.id.eq(project.idString())) { result in switch result { @@ -235,7 +235,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") let queriedProjectExpect0 = queryProject(id: project.model.id) XCTAssertNotNil(queriedProjectExpect0) @@ -243,7 +243,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest if queriedProjectExpect0!.count == 0 { getProjectAfterDeleteCompleted.fulfill() } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testDeleteWithInvalidCondition() throws { @@ -262,7 +262,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest if queriedProjectExpect1!.count == 1 { queriedProjectExpect1Successful.fulfill() } - wait(for: [queriedProjectExpect1Successful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [queriedProjectExpect1Successful], timeout: TestCommonConstants.networkTimeout) let deleteProjectFailed = expectation(description: "delete project") plugin.delete(project.model, modelSchema: Project1.schema, where: Project1.keys.id.eq("invalid")) { result in switch result { @@ -276,7 +276,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest deleteProjectFailed.fulfill() } } - wait(for: [deleteProjectFailed], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectFailed], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") let queriedProjectExpectUnDeleted = queryProject(id: project.model.id) XCTAssertNotNil(queriedProjectExpectUnDeleted) @@ -285,7 +285,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest if queriedProjectExpectUnDeleted!.count == 1 { getProjectAfterDeleteCompleted.fulfill() } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testListProjectsByTeamID() throws { @@ -312,7 +312,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listProjectByTeamIDCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listProjectByTeamIDCompleted], timeout: TestCommonConstants.networkTimeout) } func saveTeam(name: String) throws -> TeamWrapper? { @@ -329,7 +329,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return TeamWrapper(model: result!) } @@ -349,7 +349,7 @@ class DataStoreConnectionScenario1FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return Project1Wrapper(model: result!) } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario2FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario2FlutterTests.swift index 31d70cd76a..36d7684b18 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario2FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario2FlutterTests.swift @@ -63,7 +63,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveTeamCompleted, syncedTeamReceived], timeout: networkTimeout) + await fulfillment(of: [saveTeamCompleted, syncedTeamReceived], timeout: networkTimeout) let saveProjectCompleted = expectation(description: "save project completed") plugin.save(project.model, modelSchema: Project2.schema) { result in switch result { @@ -73,7 +73,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveProjectCompleted, syncProjectReceived], timeout: networkTimeout) + await fulfillment(of: [saveProjectCompleted, syncProjectReceived], timeout: networkTimeout) let queriedProjectCompleted = expectation(description: "query project completed") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project.model.id)) { result in switch result { @@ -85,7 +85,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [queriedProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [queriedProjectCompleted], timeout: networkTimeout) } func testUpdateProjectWithAnotherTeam() throws { @@ -124,7 +124,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveTeamCompleted], timeout: networkTimeout) + await fulfillment(of: [saveTeamCompleted], timeout: networkTimeout) let saveAnotherTeamCompleted = expectation(description: "save team completed") plugin.save(anotherTeam.model, modelSchema: Team2.schema) { result in switch result { @@ -134,7 +134,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveAnotherTeamCompleted], timeout: networkTimeout) + await fulfillment(of: [saveAnotherTeamCompleted], timeout: networkTimeout) let saveProjectCompleted = expectation(description: "save project completed") plugin.save(project.model, modelSchema: Project2.schema) { result in switch result { @@ -144,7 +144,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [saveProjectCompleted], timeout: networkTimeout) let updateProjectCompleted = expectation(description: "save project completed") try project.setTeam(name: "project1", team: anotherTeam.model, teamID: anotherTeam.idString()) plugin.save(project.model, modelSchema: Project2.schema) { result in @@ -155,7 +155,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [updateProjectCompleted], timeout: networkTimeout) + await fulfillment(of: [updateProjectCompleted], timeout: networkTimeout) let queriedProjectCompleted = expectation(description: "query project completed") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project.model.id)) { result in switch result { @@ -172,7 +172,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [queriedProjectCompleted, syncUpdatedProjectReceived], timeout: networkTimeout) + await fulfillment(of: [queriedProjectCompleted, syncUpdatedProjectReceived], timeout: networkTimeout) } func testDeleteAndGetProject() throws { @@ -193,7 +193,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project.model.id)) { result in switch result { @@ -204,7 +204,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testDeleteWithValidCondition() throws { @@ -222,7 +222,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project!.model.id)) { result in switch result { @@ -233,7 +233,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testDeleteWithInvalidCondition() throws { @@ -257,7 +257,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest deleteProjectFailed.fulfill() } } - wait(for: [deleteProjectFailed], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectFailed], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project.model.id)) { result in switch result { @@ -268,7 +268,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testDeleteAlreadyDeletedItemWithCondition() throws { @@ -288,7 +288,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful], timeout: TestCommonConstants.networkTimeout) let getProjectAfterDeleteCompleted = expectation(description: "get project after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Project2.schema, where: Project2.keys.id.eq(project.model.id)) { result in switch result { @@ -300,7 +300,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getProjectAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) let deleteProjectSuccessful2 = expectation(description: "delete project") plugin.delete(project.model, modelSchema: Project2.schema, where: Project2.keys.teamID.eq(team.idString())) { result in switch result { @@ -310,7 +310,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteProjectSuccessful2], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteProjectSuccessful2], timeout: TestCommonConstants.networkTimeout) } func testListProjectsByTeamID() throws { @@ -338,7 +338,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listProjectByTeamIDCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listProjectByTeamIDCompleted], timeout: TestCommonConstants.networkTimeout) } func saveTeam(name: String, plugin: AWSDataStorePlugin) throws -> TeamWrapper? { @@ -354,7 +354,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -373,7 +373,7 @@ class DataStoreConnectionScenario2FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario3FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario3FlutterTests.swift index f51c51ab80..05a9730c4e 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario3FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario3FlutterTests.swift @@ -64,7 +64,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [savePostCompleted, syncedPostReceived], timeout: networkTimeout) + await fulfillment(of: [savePostCompleted, syncedPostReceived], timeout: networkTimeout) let saveCommentCompleted = expectation(description: "save comment completed") plugin.save(comment.model, modelSchema: Comment3.schema) { result in switch result { @@ -74,7 +74,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [saveCommentCompleted, syncCommentReceived], timeout: networkTimeout) + await fulfillment(of: [saveCommentCompleted, syncCommentReceived], timeout: networkTimeout) let queriedCommentCompleted = expectation(description: "query comment completed") plugin.query(FlutterSerializedModel.self, modelSchema: Comment3.schema, where: Comment3.keys.id.eq(comment.model.id)) { result in switch result { @@ -86,7 +86,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [queriedCommentCompleted], timeout: networkTimeout) + await fulfillment(of: [queriedCommentCompleted], timeout: networkTimeout) } /// TODO: Include testSaveCommentAndGetPostWithComments test when nested model lazy loading is implemented @@ -117,7 +117,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [updateCommentSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [updateCommentSuccessful], timeout: TestCommonConstants.networkTimeout) } func testDeleteAndGetComment() throws { @@ -140,7 +140,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteCommentSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteCommentSuccessful], timeout: TestCommonConstants.networkTimeout) let getCommentAfterDeleteCompleted = expectation(description: "get comment after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Comment3.schema, where: Comment3.keys.id.eq(comment.idString())) { result in switch result { @@ -154,7 +154,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getCommentAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getCommentAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testListCommentsByPostID() throws { @@ -179,7 +179,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listCommentByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listCommentByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) } func savePost(id: String = UUID().uuidString, title: String, plugin: AWSDataStorePlugin) throws -> Post3Wrapper? { @@ -197,7 +197,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -214,7 +214,7 @@ class DataStoreConnectionScenario3FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario4FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario4FlutterTests.swift index 533dd258d4..93afce11cb 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario4FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario4FlutterTests.swift @@ -54,7 +54,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getCommentCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getCommentCompleted], timeout: TestCommonConstants.networkTimeout) } func testUpdateComment() throws { @@ -84,7 +84,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [updateCommentSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [updateCommentSuccessful], timeout: TestCommonConstants.networkTimeout) } func testDeleteAndGetComment() throws { @@ -108,7 +108,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [deleteCommentSuccessful], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteCommentSuccessful], timeout: TestCommonConstants.networkTimeout) let getCommentAfterDeleteCompleted = expectation(description: "get comment after deleted complete") plugin.query(FlutterSerializedModel.self, modelSchema: Comment4.schema, where: Comment4.keys.id.eq(comment.idString())) { result in switch result { @@ -122,7 +122,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [getCommentAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getCommentAfterDeleteCompleted], timeout: TestCommonConstants.networkTimeout) } func testListCommentsByPostID() throws { @@ -147,7 +147,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listCommentByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listCommentByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) } func savePost(id: String = UUID().uuidString, title: String, plugin: AWSDataStorePlugin) throws -> Post4Wrapper? { @@ -163,7 +163,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -180,7 +180,7 @@ class DataStoreConnectionScenario4FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario5FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario5FlutterTests.swift index 2e11e72df0..757cbdda17 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario5FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario5FlutterTests.swift @@ -65,7 +65,7 @@ class DataStoreConnectionScenario5FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listPostEditorByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listPostEditorByPostIDCompleted], timeout: TestCommonConstants.networkTimeout) } func testListPostEditorByUser() throws { @@ -93,7 +93,7 @@ class DataStoreConnectionScenario5FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("\(error)") } } - wait(for: [listPostEditorByEditorIdCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [listPostEditorByEditorIdCompleted], timeout: TestCommonConstants.networkTimeout) } /// TODO: Include testGetPostThenLoadPostEditors when nested model lazy loading is implemented @@ -110,7 +110,7 @@ class DataStoreConnectionScenario5FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -127,7 +127,7 @@ class DataStoreConnectionScenario5FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -144,7 +144,7 @@ class DataStoreConnectionScenario5FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario6FlutterTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario6FlutterTests.swift index 179aed62f1..6c9f84111a 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario6FlutterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/Connection/DataStoreConnectionScenario6FlutterTests.swift @@ -57,7 +57,7 @@ class DataStoreConnectionScenario6FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("Failed with: \(response)") } } - wait(for: [getCommentCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getCommentCompleted], timeout: TestCommonConstants.networkTimeout) guard let fetchedComment = resultComment else { XCTFail("Could not get comment") return @@ -90,7 +90,7 @@ class DataStoreConnectionScenario6FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -107,7 +107,7 @@ class DataStoreConnectionScenario6FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } @@ -124,7 +124,7 @@ class DataStoreConnectionScenario6FlutterTests: SyncEngineFlutterIntegrationTest XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift index 88edeb3fee..c0172df6ff 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift @@ -79,7 +79,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [saveAndImmediatelyUpdate], timeout: networkTimeout) + await fulfillment(of: [saveAndImmediatelyUpdate], timeout: networkTimeout) // query the updated post immediately guard let queryResult = queryPost(id: updatedPost.idString(), plugin: plugin) else { @@ -88,7 +88,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB } XCTAssertEqual(queryResult, updatedPost) - wait(for: [saveSyncReceived, updateSyncReceived], timeout: networkTimeout) + await fulfillment(of: [saveSyncReceived, updateSyncReceived], timeout: networkTimeout) // query the updated post in eventual consistent state guard let queryResultAfterSync = queryPost(id: updatedPost.idString(), plugin: plugin) else { @@ -126,7 +126,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [apiQuerySuccess], timeout: networkTimeout) + await fulfillment(of: [apiQuerySuccess], timeout: networkTimeout) } /// - Given: API has been setup with `Post` model registered @@ -191,13 +191,13 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [saveAndImmediatelyDelete], timeout: networkTimeout) + await fulfillment(of: [saveAndImmediatelyDelete], timeout: networkTimeout) // query the deleted post immediately let queryResult = queryPost(id: newPost.idString(), plugin: plugin) XCTAssertNil(queryResult) - wait(for: [saveSyncReceived, deleteSyncReceived], timeout: networkTimeout) + await fulfillment(of: [saveSyncReceived, deleteSyncReceived], timeout: networkTimeout) // query the deleted post in eventual consistent state let queryResultAfterSync = queryPost(id: newPost.idString(), plugin: plugin) @@ -231,7 +231,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [apiQuerySuccess], timeout: networkTimeout) + await fulfillment(of: [apiQuerySuccess], timeout: networkTimeout) } /// - Given: API has been setup with `Post` model registered @@ -304,7 +304,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [saveCompleted, saveSyncReceived], timeout: networkTimeout) + await fulfillment(of: [saveCompleted, saveSyncReceived], timeout: networkTimeout) let updateAndImmediatelyDelete = expectation(description: "Post is updated and deleted immediately") @@ -324,13 +324,13 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB } } - wait(for: [updateAndImmediatelyDelete], timeout: networkTimeout) + await fulfillment(of: [updateAndImmediatelyDelete], timeout: networkTimeout) // query the deleted post immediately let queryResult = queryPost(id: newPost.idString(), plugin: plugin) XCTAssertNil(queryResult) - wait(for: [updateSyncReceived, deleteSyncReceived], timeout: networkTimeout) + await fulfillment(of: [updateSyncReceived, deleteSyncReceived], timeout: networkTimeout) // query the deleted post let queryResultAfterSync = queryPost(id: updatedPost.idString(), plugin: plugin) @@ -364,7 +364,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [apiQuerySuccess], timeout: networkTimeout) + await fulfillment(of: [apiQuerySuccess], timeout: networkTimeout) } private func queryPost(id: String, plugin: AWSDataStorePlugin) -> PostWrapper? { @@ -381,7 +381,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB XCTFail("Error: \(error)") } } - wait(for: [queryExpectation], timeout: networkTimeout) + await fulfillment(of: [queryExpectation], timeout: networkTimeout) return queryResult } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterEndToEndTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterEndToEndTests.swift index 0245ca8cc0..ce1a143e1e 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterEndToEndTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterEndToEndTests.swift @@ -74,15 +74,15 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { plugin.save(newPost.model, modelSchema: Post.schema) { _ in } - wait(for: [createReceived], timeout: networkTimeout) + await fulfillment(of: [createReceived], timeout: networkTimeout) plugin.save(updatedPost.model, modelSchema: Post.schema) { _ in } - wait(for: [updateReceived], timeout: networkTimeout) + await fulfillment(of: [updateReceived], timeout: networkTimeout) plugin.delete(updatedPost.model, modelSchema: Post.schema) { _ in } - wait(for: [deleteReceived], timeout: networkTimeout) + await fulfillment(of: [deleteReceived], timeout: networkTimeout) } /// - Given: A post that has been saved @@ -151,11 +151,11 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { plugin.save(newPost.model, modelSchema: Post.schema) { _ in } - wait(for: [createReceived], timeout: networkTimeout) + await fulfillment(of: [createReceived], timeout: networkTimeout) plugin.save(updatedPost.model, modelSchema: Post.schema, where: post.title == title) { _ in } - wait(for: [updateReceived], timeout: networkTimeout) + await fulfillment(of: [updateReceived], timeout: networkTimeout) } /// Ensure DataStore.stop followed by DataStore.start is successful @@ -186,7 +186,7 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { XCTFail("\(error)") } } - wait(for: [stopStartSuccess], timeout: networkTimeout) + await fulfillment(of: [stopStartSuccess], timeout: networkTimeout) try validateSavePost(plugin: plugin) } @@ -211,13 +211,13 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { try startAmplify { amplifyStarted.fulfill() } - wait(for: [amplifyStarted], timeout: 1.0) + await fulfillment(of: [amplifyStarted], timeout: 1.0) // We expect the query to complete, but not to return a value. Thus, we'll ignore the error let queryCompleted = expectation(description: "queryCompleted") plugin.query(FlutterSerializedModel.self, modelSchema: Post.schema, where: Post.keys.id.eq("123")) { _ in queryCompleted.fulfill() } - wait(for: [dataStoreStarted, queryCompleted], timeout: networkTimeout) + await fulfillment(of: [dataStoreStarted, queryCompleted], timeout: networkTimeout) sink.cancel() } @@ -249,7 +249,7 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { XCTFail("\(error)") } } - wait(for: [clearStartSuccess], timeout: networkTimeout) + await fulfillment(of: [clearStartSuccess], timeout: networkTimeout) try validateSavePost(plugin: plugin) } @@ -290,6 +290,6 @@ class DataStoreEndToEndTests: SyncEngineFlutterIntegrationTestBase { } plugin.save(newPost.model, modelSchema: Post.schema) { _ in } - wait(for: [createReceived], timeout: networkTimeout) + await fulfillment(of: [createReceived], timeout: networkTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift index 8eb79b00ee..5505303ae7 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift @@ -86,7 +86,7 @@ class SyncEngineFlutterIntegrationTestBase: XCTestCase { } } - wait(for: [syncStarted], timeout: 100.0) + await fulfillment(of: [syncStarted], timeout: 100.0) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario2Tests.swift index 19f93dfe08..820b191aaf 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario2Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario2Tests.swift @@ -88,7 +88,7 @@ class DataStoreConnectionScenario2Tests: SyncEngineIntegrationTestBase { let anotherTeam = Team2(name: "name1") var project = Project2(teamID: team.id, team: team) let expectedUpdatedProject = Project2(id: project.id, name: project.name, teamID: anotherTeam.id) - let syncUpdatedProjectReceived = asyncExpectation(description: "received updated project from sync path") + let syncUpdatedProjectReceived = expectation(description: "received updated project from sync path") let hubListener = Amplify.Hub.listen(to: .dataStore, eventName: HubPayload.EventName.DataStore.syncReceived) { payload in guard let mutationEvent = payload.data as? MutationEvent else { @@ -98,9 +98,7 @@ class DataStoreConnectionScenario2Tests: SyncEngineIntegrationTestBase { if let syncedUpdatedProject = try? mutationEvent.decodeModel() as? Project2, expectedUpdatedProject == syncedUpdatedProject { - Task { - await syncUpdatedProjectReceived.fulfill() - } + syncUpdatedProjectReceived.fulfill() } } guard try await HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { @@ -121,7 +119,7 @@ class DataStoreConnectionScenario2Tests: SyncEngineIntegrationTestBase { XCTAssertEqual(queriedProject, project) XCTAssertEqual(queriedProject.teamID, anotherTeam.id) } - await waitForExpectations([syncUpdatedProjectReceived], timeout: networkTimeout) + await fulfillment(of: [syncUpdatedProjectReceived], timeout: networkTimeout) } func testDeleteAndGetProjectReturnsNilWithSync() async throws { diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreConsecutiveUpdatesTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreConsecutiveUpdatesTests.swift index a4660294b0..eb21319b2e 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreConsecutiveUpdatesTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreConsecutiveUpdatesTests.swift @@ -83,7 +83,7 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { let queryResult = try await queryPost(byId: updatedPost.id) XCTAssertEqual(queryResult, updatedPost) - await waitForExpectations(timeout: networkTimeout) + await fulfillment(of: [updateSyncReceived], timeout: networkTimeout) // query the updated post in eventual consistent state let queryResultAfterSync = try await queryPost(byId: updatedPost.id) @@ -202,8 +202,8 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { updatedPost.title = "MyUpdatedPost" updatedPost.content = "This is my updated post." - let saveSyncReceived = asyncExpectation(description: "Received create mutation event on subscription for Post") - let deleteSyncReceived = asyncExpectation(description: "Received delete mutation event on subscription for Post") + let saveSyncReceived = expectation(description: "Received create mutation event on subscription for Post") + let deleteSyncReceived = expectation(description: "Received delete mutation event on subscription for Post") let hubListener = Amplify.Hub.listen( to: .dataStore, @@ -221,12 +221,12 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { XCTAssertEqual(post, newPost) XCTAssertEqual(mutationEvent.version, 1) - Task { await saveSyncReceived.fulfill() } + saveSyncReceived.fulfill() } if mutationEvent.version == 3 { XCTAssertEqual(post, updatedPost) - Task { await deleteSyncReceived.fulfill() } + deleteSyncReceived.fulfill() } } @@ -237,7 +237,7 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { // save the post, update and delete immediately _ = try await Amplify.DataStore.save(newPost) - await waitForExpectations([saveSyncReceived], timeout: networkTimeout) + await fulfillment(of: [saveSyncReceived], timeout: networkTimeout) _ = try await Amplify.DataStore.save(updatedPost) try await Amplify.DataStore.delete(updatedPost) @@ -246,7 +246,7 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { let queryResult = try await queryPost(byId: newPost.id) XCTAssertNil(queryResult) - await waitForExpectations([deleteSyncReceived], timeout: networkTimeout) + await fulfillment(of: [deleteSyncReceived], timeout: networkTimeout) // query the deleted post let queryResultAfterSync = try await queryPost(byId: updatedPost.id) @@ -254,22 +254,22 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { let queryRequest = GraphQLRequest.query(modelName: updatedPost.modelName, byId: updatedPost.id) let mutationSyncResult = try await Amplify.API.query(request: queryRequest) - switch mutationSyncResult { - case .success(let data): - guard let post = data else { - XCTFail("Failed to get data") - return - } - - XCTAssertEqual(post.model["title"] as? String, updatedPost.title) - XCTAssertEqual(post.model["content"] as? String, updatedPost.content) - XCTAssertEqual(post.model["rating"] as? Double, updatedPost.rating) - - XCTAssertTrue(post.syncMetadata.deleted) - XCTAssertEqual(post.syncMetadata.version, 3) - case .failure(let error): - XCTFail("Error: \(error)") - } + switch mutationSyncResult { + case .success(let data): + guard let post = data else { + XCTFail("Failed to get data") + return + } + + XCTAssertEqual(post.model["title"] as? String, updatedPost.title) + XCTAssertEqual(post.model["content"] as? String, updatedPost.content) + XCTAssertEqual(post.model["rating"] as? Double, updatedPost.rating) + + XCTAssertTrue(post.syncMetadata.deleted) + XCTAssertEqual(post.syncMetadata.version, 3) + case .failure(let error): + XCTFail("Error: \(error)") + } } /// - Given: API has been setup with `Post` model registered @@ -325,14 +325,14 @@ class DataStoreConsecutiveUpdatesTests: SyncEngineIntegrationTestBase { } _ = try await Amplify.DataStore.save(newPost) - wait(for: [saveSyncReceived], timeout: networkTimeout) + await fulfillment(of: [saveSyncReceived], timeout: networkTimeout) for index in 1 ... updateCount { updatedPost.title = updatedPostDefaultTitle + String(index) _ = try await Amplify.DataStore.save(updatedPost) } - wait(for: [updateSyncReceived], timeout: networkTimeout) + await fulfillment(of: [updateSyncReceived], timeout: networkTimeout) // query the updated post in eventual consistent state let queryResultAfterSync = try await queryPost(byId: updatedPost.id) diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreCustomPrimaryKeyTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreCustomPrimaryKeyTests.swift index 5bba4ed773..1c89de647a 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreCustomPrimaryKeyTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreCustomPrimaryKeyTests.swift @@ -69,7 +69,7 @@ class DataStoreCustomPrimaryKeyTests: SyncEngineIntegrationTestBase { // create customer order _ = try await Amplify.DataStore.save(customerOrder) - await waitForExpectations(timeout: networkTimeout) + await fulfillment(of: [createReceived], timeout: networkTimeout) // update customer order let updateReceived = expectation(description: "Update notification received") @@ -102,8 +102,8 @@ class DataStoreCustomPrimaryKeyTests: SyncEngineIntegrationTestBase { return } _ = try await Amplify.DataStore.save(updatedCustomerOrder) - await waitForExpectations(timeout: networkTimeout) - + await fulfillment(of: [updateReceived], timeout: networkTimeout) + // query the updated order let queriedOrder = try await Amplify.DataStore.query(CustomerOrder.self, byId: updatedCustomerOrder.id) guard let order = queriedOrder else { @@ -144,8 +144,8 @@ class DataStoreCustomPrimaryKeyTests: SyncEngineIntegrationTestBase { } _ = try await Amplify.DataStore.delete(CustomerOrder.self, withIdentifier: updatedCustomerOrder.id) - await waitForExpectations(timeout: networkTimeout) - + await fulfillment(of: [deleteReceived], timeout: networkTimeout) + // query the customer order after deletion let queriedOrder2 = try await Amplify.DataStore.query(CustomerOrder.self, byId: updatedCustomerOrder.id) XCTAssertNil(queriedOrder2) diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreEndToEndTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreEndToEndTests.swift index 3854743fb2..1576d90407 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreEndToEndTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreEndToEndTests.swift @@ -686,7 +686,7 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { _ = try await Amplify.DataStore.save(newPost) - await waitForExpectations(timeout: networkTimeout) + await fulfillment(of: [createReceived], timeout: networkTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift index b2797adaba..916a4d26ea 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift @@ -44,6 +44,7 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { try await Task.sleep(seconds: 1) let networkStatusReceived = expectation(description: "networkStatus received") + networkStatusReceived.assertForOverFulfill = false var networkStatusActive = false let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received") let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received") diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreObserveQueryTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreObserveQueryTests.swift index 518c69d24c..2ac54a0f71 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreObserveQueryTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreObserveQueryTests.swift @@ -41,8 +41,8 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { await setUp(withModels: TestModelRegistration()) try startAmplify() try await clearDataStore() - let snapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") - let querySnapshotsCancelled = asyncExpectation(description: "query snapshots cancelled") + let snapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") + let querySnapshotsCancelled = expectation(description: "query snapshots cancelled") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self) let task = Task { var snapshots = [DataStoreQuerySnapshot]() @@ -50,7 +50,7 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { for try await querySnapshot in querySnapshots { snapshots.append(querySnapshot) if querySnapshot.isSynced { - await snapshotWithIsSynced.fulfill() + snapshotWithIsSynced.fulfill() } } } catch { @@ -58,14 +58,14 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } XCTAssertTrue(snapshots.count >= 2) XCTAssertFalse(snapshots[0].isSynced) - await querySnapshotsCancelled.fulfill() + querySnapshotsCancelled.fulfill() } - let receivedPost = asyncExpectation(description: "received Post") + let receivedPost = expectation(description: "received Post") try await savePostAndWaitForSync(Post(title: "title", content: "content", createdAt: .now()), postSyncedExpctation: receivedPost) - await waitForExpectations([snapshotWithIsSynced, receivedPost], timeout: 100) + await fulfillment(of: [snapshotWithIsSynced], timeout: 100) task.cancel() - await waitForExpectations([querySnapshotsCancelled], timeout: 10) + await fulfillment(of: [querySnapshotsCancelled], timeout: 10) } /// ObserveQuery API will eventually return query snapshot with `isSynced` true @@ -82,7 +82,7 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { try startAmplify() try await clearDataStore() var snapshots = [DataStoreQuerySnapshot]() - let snapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let snapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)).sink { completed in switch completed { case .finished: @@ -93,13 +93,20 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } receiveValue: { querySnapshot in snapshots.append(querySnapshot) if querySnapshot.isSynced { - Task { await snapshotWithIsSynced.fulfill() } + snapshotWithIsSynced.fulfill() } }.store(in: &cancellables) - let receivedPost = asyncExpectation(description: "received Post") - try await savePostAndWaitForSync(Post(title: "title", content: "content", createdAt: .now()), - postSyncedExpctation: receivedPost) - await waitForExpectations([snapshotWithIsSynced, receivedPost], timeout: 100) + let receivedPost = expectation(description: "received Post") + receivedPost.assertForOverFulfill = false + try await savePostAndWaitForSync( + Post( + title: "title", + content: "content", + createdAt: .now() + ), + postSyncedExpctation: receivedPost + ) + await fulfillment(of: [snapshotWithIsSynced], timeout: 100) XCTAssertTrue(snapshots.count >= 2) XCTAssertFalse(snapshots[0].isSynced) @@ -121,8 +128,8 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { try await clearDataStore() var snapshots = [DataStoreQuerySnapshot]() var isObserveQueryReadyForTest = false - let observeQueryReadyForTest = asyncExpectation(description: "received query snapshot with .isSynced true") - let snapshotWithPost = asyncExpectation(description: "received first snapshot") + let observeQueryReadyForTest = expectation(description: "received query snapshot with .isSynced true") + let snapshotWithPost = expectation(description: "received first snapshot") let post = Post(title: "title", content: "content", createdAt: .now()) Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)).sink { completed in switch completed { @@ -135,16 +142,16 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { snapshots.append(querySnapshot) if !isObserveQueryReadyForTest && querySnapshot.isSynced { isObserveQueryReadyForTest = true - Task { await observeQueryReadyForTest.fulfill() } + observeQueryReadyForTest.fulfill() } if querySnapshot.items.contains(where: { $0.id == post.id }) { - Task { await snapshotWithPost.fulfill() } + snapshotWithPost.fulfill() } }.store(in: &cancellables) - await waitForExpectations([observeQueryReadyForTest], timeout: 100) + await fulfillment(of: [observeQueryReadyForTest], timeout: 100) _ = try await Amplify.DataStore.save(post) - await waitForExpectations([snapshotWithPost], timeout: 100) + await fulfillment(of: [snapshotWithPost], timeout: 100) } /// Apply a query predicate "title begins with 'xyz'" @@ -182,9 +189,10 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { try await clearDataStore() var snapshots = [DataStoreQuerySnapshot]() - let snapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let snapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") var snapshotWithIsSyncedFulfilled = false - let receivedPostFromObserveQuery = asyncExpectation(description: "received Post") + let receivedPostFromObserveQuery = expectation(description: "received Post") + receivedPostFromObserveQuery.assertForOverFulfill = false let post4 = Post(title: "\(randomTitle) 4", content: "content", createdAt: .now()) let predicate = Post.keys.title.beginsWith(randomTitle) Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self, where: predicate)).sink { completed in @@ -198,21 +206,21 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { snapshots.append(querySnapshot) if !snapshotWithIsSyncedFulfilled && querySnapshot.isSynced { snapshotWithIsSyncedFulfilled = true - Task { await snapshotWithIsSynced.fulfill() } + snapshotWithIsSynced.fulfill() } else if snapshotWithIsSyncedFulfilled { if querySnapshot.items.count >= 4 && querySnapshot.items.allSatisfy({ $0.title.contains(randomTitle)}) { - Task { await receivedPostFromObserveQuery.fulfill() } + receivedPostFromObserveQuery.fulfill() } } }.store(in: &cancellables) - await waitForExpectations([snapshotWithIsSynced], timeout: 100) + await fulfillment(of: [snapshotWithIsSynced], timeout: 100) try await savePostAndWaitForSync(post4) - await waitForExpectations([receivedPostFromObserveQuery], timeout: 100) + await fulfillment(of: [receivedPostFromObserveQuery], timeout: 100) XCTAssertTrue(snapshots.count >= 2) } @@ -232,7 +240,8 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { try await clearDataStore() let post1 = Post(title: "title", content: "content", createdAt: .now()) let post2 = Post(title: "title", content: "content", createdAt: .now().add(value: 1, to: .second)) - let snapshotWithSavedPost = asyncExpectation(description: "query snapshot with saved post") + let snapshotWithSavedPost = expectation(description: "query snapshot with saved post") + snapshotWithSavedPost.assertForOverFulfill = false Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self, sort: .ascending(Post.keys.createdAt))) .sink { completed in switch completed { @@ -250,13 +259,13 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { let actualPost1 = items.removeLast() XCTAssertEqual(actualPost2.id, post2.id) XCTAssertEqual(actualPost1.id, post1.id) - Task { await snapshotWithSavedPost.fulfill() } + snapshotWithSavedPost.fulfill() } }.store(in: &cancellables) try await savePostAndWaitForSync(post1) try await savePostAndWaitForSync(post2) - await waitForExpectations([snapshotWithSavedPost], timeout: 100) + await fulfillment(of: [snapshotWithSavedPost], timeout: 100) } /// ObserveQuery with DataStore delta sync. Ensure datastore has synced the models and stopped. @@ -276,7 +285,7 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { let numberOfPosts = try await queryNumberOfPosts() XCTAssertTrue(numberOfPosts > 0) try await stopDataStore() - let snapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let snapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") var snapshots = [DataStoreQuerySnapshot]() Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)).sink { completed in switch completed { @@ -288,13 +297,13 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } receiveValue: { querySnapshot in snapshots.append(querySnapshot) if querySnapshot.isSynced { - Task { await snapshotWithIsSynced.fulfill() } + snapshotWithIsSynced.fulfill() } }.store(in: &cancellables) - let receivedPost = asyncExpectation(description: "received Post") + let receivedPost = expectation(description: "received Post") try await savePostAndWaitForSync(Post(title: "title", content: "content", createdAt: .now()), postSyncedExpctation: receivedPost) - await waitForExpectations([snapshotWithIsSynced, receivedPost], timeout: 30) + await fulfillment(of: [snapshotWithIsSynced], timeout: 30) XCTAssertTrue(snapshots.count >= 2) XCTAssertFalse(snapshots[0].isSynced) XCTAssertTrue(snapshots.last!.isSynced) @@ -317,7 +326,7 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { try await startAmplifyAndWaitForReady() try await clearDataStore() - let snapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let snapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") var snapshots = [DataStoreQuerySnapshot]() Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)).sink { completed in @@ -330,16 +339,16 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } receiveValue: { querySnapshot in snapshots.append(querySnapshot) if querySnapshot.isSynced { - Task { await snapshotWithIsSynced.fulfill() } + snapshotWithIsSynced.fulfill() } }.store(in: &cancellables) let newPost = Post(title: "title", content: "content", createdAt: .now()) - let receivedPost = asyncExpectation(description: "received Post") + let receivedPost = expectation(description: "received Post") try await savePostAndWaitForSync(newPost, postSyncedExpctation: receivedPost) - await waitForExpectations([snapshotWithIsSynced, receivedPost], timeout: 30) + await fulfillment(of: [snapshotWithIsSynced], timeout: 30) XCTAssertTrue(snapshots.count >= 2) XCTAssertFalse(snapshots[0].isSynced) XCTAssertEqual(1, snapshots.filter({ $0.isSynced }).count) @@ -368,14 +377,14 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { let testId = UUID().uuidString var snapshotCount = 0 let predicate = Post.keys.title.beginsWith("xyz") && Post.keys.content == testId - let snapshotExpectation1 = asyncExpectation(description: "received snapshot 1") - let snapshotExpectation23 = asyncExpectation(description: "received snapshot 2 / 3", - expectedFulfillmentCount: 2) - let snapshotExpectation4 = asyncExpectation(description: "received snapshot 4") - let snapshotExpectation56 = asyncExpectation(description: "received snapshot 5 / 6", - expectedFulfillmentCount: 2) - let snapshotExpectation7 = asyncExpectation(description: "received snapshot 7") - let snapshotExpectation8 = asyncExpectation(description: "received snapshot 8") + let snapshotExpectation1 = expectation(description: "received snapshot 1") + let snapshotExpectation23 = expectation(description: "received snapshot 2 / 3") + snapshotExpectation23.expectedFulfillmentCount = 2 + let snapshotExpectation4 = expectation(description: "received snapshot 4") + let snapshotExpectation56 = expectation(description: "received snapshot 5 / 6") + snapshotExpectation56.expectedFulfillmentCount = 2 + let snapshotExpectation7 = expectation(description: "received snapshot 7") + let snapshotExpectation8 = expectation(description: "received snapshot 8") Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self, where: predicate)).sink { completed in switch completed { case .finished: @@ -389,74 +398,74 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { if snapshotCount == 1 { self.log.info("\(#function) 1. \(querySnapshot)") XCTAssertEqual(items.count, 0) - Task { await snapshotExpectation1.fulfill() } + snapshotExpectation1.fulfill() } else if snapshotCount == 2 || snapshotCount == 3 { // See (1), subsequent snapshot should have item with "xyz 1". self.log.info("\(#function) 2/3. \(querySnapshot)") XCTAssertEqual(items.count, 1) XCTAssertEqual(items[0].title, "xyz 1") - Task { await snapshotExpectation23.fulfill() } + snapshotExpectation23.fulfill() } else if snapshotCount == 4 { // See (2), should not be added to the snapshot. // See (3), should be removed from the snapshot. So the resulting snapshot is empty. self.log.info("\(#function) 4. \(querySnapshot)") XCTAssertEqual(items.count, 0) - Task { await snapshotExpectation4.fulfill() } + snapshotExpectation4.fulfill() } else if snapshotCount == 5 || snapshotCount == 6 { // See (4). the post that now matches the snapshot should be added self.log.info("\(#function) 5/6. \(querySnapshot)") XCTAssertEqual(items.count, 1) XCTAssertEqual(items[0].title, "xyz 2") - Task { await snapshotExpectation56.fulfill() } + snapshotExpectation56.fulfill() } else if snapshotCount == 7 { // See (5). the post that matched the predicate was deleted self.log.info("\(#function) 7. \(querySnapshot)") XCTAssertEqual(items.count, 0) - Task { await snapshotExpectation7.fulfill() } + snapshotExpectation7.fulfill() } else if snapshotCount == 8 { // See (6). Snapshot that is emitted due to "xyz 3" should not contain the deleted model self.log.info("\(#function) 8. \(querySnapshot)") XCTAssertEqual(items.count, 1) XCTAssertEqual(items[0].title, "xyz 3") - Task { await snapshotExpectation8.fulfill() } + snapshotExpectation8.fulfill() } }.store(in: &cancellables) - await waitForExpectations([snapshotExpectation1], timeout: 10) + await fulfillment(of: [snapshotExpectation1], timeout: 10) // (1) Add model that matches predicate - should be received on the snapshot let postMatchPredicate = Post(title: "xyz 1", content: testId, createdAt: .now()) try await savePostAndWaitForSync(postMatchPredicate) - await waitForExpectations([snapshotExpectation23], timeout: 10) + await fulfillment(of: [snapshotExpectation23], timeout: 10) // (2) Add model that does not match predicate - should not be received on the snapshot // (3) Update model that used to match the predicate to no longer match - should be removed from snapshot let postDoesNotMatch = Post(title: "doesNotMatch", content: testId, createdAt: .now()) - let postDoesNotMatchExpectation = asyncExpectation(description: "received postDoesNotMatchExpectation") + let postDoesNotMatchExpectation = expectation(description: "received postDoesNotMatchExpectation") try await savePostAndWaitForSync(postDoesNotMatch, postSyncedExpctation: postDoesNotMatchExpectation) var postMatchPredicateNoLongerMatches = postMatchPredicate postMatchPredicateNoLongerMatches.title = "doesNotMatch" try await savePostAndWaitForSync(postMatchPredicateNoLongerMatches) - await waitForExpectations([snapshotExpectation4], timeout: 10) + await fulfillment(of: [snapshotExpectation4], timeout: 10) // (4) Update model that does not match predicate to match - should be added to snapshot var postDoesNotMatchNowMatches = postDoesNotMatch postDoesNotMatchNowMatches.title = "xyz 2" try await savePostAndWaitForSync(postDoesNotMatchNowMatches) - await waitForExpectations([snapshotExpectation56], timeout: 10) + await fulfillment(of: [snapshotExpectation56], timeout: 10) // (5) Delete the model that matches the predicate - should be removed try await deletePostAndWaitForSync(postDoesNotMatchNowMatches) - await waitForExpectations([snapshotExpectation7], timeout: 10) + await fulfillment(of: [snapshotExpectation7], timeout: 10) // (6) Delete the model that does not match predicate - should have no snapshot emitted - let postMatchPredicateNoLongerMatchesExpectation = asyncExpectation(description: " received") + let postMatchPredicateNoLongerMatchesExpectation = expectation(description: " received") try await deletePostAndWaitForSync(postMatchPredicateNoLongerMatches, postSyncedExpctation: postMatchPredicateNoLongerMatchesExpectation) // Save "xyz 3" to force a snapshot to be emitted try await savePostAndWaitForSync(Post(title: "xyz 3", content: testId, createdAt: .now())) - await waitForExpectations([snapshotExpectation8], timeout: 10) + await fulfillment(of: [snapshotExpectation8], timeout: 10) } /// ObserveQuery is set up with a sort order. @@ -575,28 +584,28 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { func testObserveQueryShouldResetOnDataStoreStop() async throws { await setUp(withModels: TestModelRegistration()) try await startAmplifyAndWaitForReady() - let firstSnapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let firstSnapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") var onComplete: ((Subscribers.Completion) -> Void) = { _ in } Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)) .sink { onComplete($0) } receiveValue: { querySnapshot in if querySnapshot.isSynced { - Task { await firstSnapshotWithIsSynced.fulfill() } + firstSnapshotWithIsSynced.fulfill() } }.store(in: &cancellables) - await waitForExpectations([firstSnapshotWithIsSynced], timeout: 10) + await fulfillment(of: [firstSnapshotWithIsSynced], timeout: 10) - let observeQueryReceivedCompleted = asyncExpectation(description: "observeQuery received completed", - isInverted: true) + let observeQueryReceivedCompleted = expectation(description: "observeQuery received completed") + observeQueryReceivedCompleted.isInverted = true onComplete = { completed in switch completed { case .finished: - Task { await observeQueryReceivedCompleted.fulfill() } + observeQueryReceivedCompleted.fulfill() case .failure(let error): XCTFail("\(error)") } } try await Amplify.DataStore.stop() - await waitForExpectations([observeQueryReceivedCompleted], timeout: 10) + await fulfillment(of: [observeQueryReceivedCompleted], timeout: 10) } /// Ensure clearing datastore will not complete the observeQuery subscribers. @@ -610,72 +619,78 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { func testObserveQueryShouldResetOnDataStoreClear() async throws { await setUp(withModels: TestModelRegistration()) try await startAmplifyAndWaitForReady() - let firstSnapshotWithIsSynced = asyncExpectation(description: "query snapshot with isSynced true") + let firstSnapshotWithIsSynced = expectation(description: "query snapshot with isSynced true") var onComplete: ((Subscribers.Completion) -> Void) = { _ in } Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)) .sink { onComplete($0) } receiveValue: { querySnapshot in if querySnapshot.isSynced { - Task { await firstSnapshotWithIsSynced.fulfill() } + firstSnapshotWithIsSynced.fulfill() } }.store(in: &cancellables) - await waitForExpectations([firstSnapshotWithIsSynced], timeout: 100) + await fulfillment(of: [firstSnapshotWithIsSynced], timeout: 100) - let observeQueryReceivedCompleted = asyncExpectation(description: "observeQuery received completed", - isInverted: true) + let observeQueryReceivedCompleted = expectation(description: "observeQuery received completed") + observeQueryReceivedCompleted.isInverted = true onComplete = { completed in switch completed { case .finished: - Task { await observeQueryReceivedCompleted.fulfill() } + observeQueryReceivedCompleted.fulfill() case .failure(let error): XCTFail("\(error)") } } try await Amplify.DataStore.clear() - await waitForExpectations([observeQueryReceivedCompleted], timeout: 10) + await fulfillment(of: [observeQueryReceivedCompleted], timeout: 10) } func testObserveQueryShouldStartOnDataStoreStart() async throws { - try await setUp(withModels: TestModelRegistration()) + await setUp(withModels: TestModelRegistration()) try await startAmplifyAndWaitForReady() - let firstSnapshot = asyncExpectation(description: "first query snapshot") - let secondSnapshot = asyncExpectation(description: "second query snapshot") - let observeQueryReceivedCompleted = asyncExpectation(description: "observeQuery received completed", isInverted: true) + let firstSnapshot = expectation(description: "first query snapshot") + let secondSnapshot = expectation(description: "second query snapshot") + let observeQueryReceivedCompleted = expectation(description: "observeQuery received completed") + observeQueryReceivedCompleted.isInverted = true var querySnapshots = [DataStoreQuerySnapshot]() let sink = Amplify.Publisher.create(Amplify.DataStore.observeQuery(for: Post.self)).sink { completed in switch completed { case .finished: - Task { await observeQueryReceivedCompleted.fulfill() } + observeQueryReceivedCompleted.fulfill() case .failure(let error): XCTFail("\(error)") } } receiveValue: { querySnapshot in querySnapshots.append(querySnapshot) if querySnapshots.count == 1 { - Task { await firstSnapshot.fulfill() } + firstSnapshot.fulfill() } else if querySnapshots.count == 2 { - Task { await secondSnapshot.fulfill() } + secondSnapshot.fulfill() } } - await waitForExpectations([firstSnapshot], timeout: 100) + await fulfillment(of: [firstSnapshot], timeout: 100) try await Amplify.DataStore.stop() - await waitForExpectations([observeQueryReceivedCompleted], timeout: 10) + await fulfillment(of: [observeQueryReceivedCompleted], timeout: 10) try await Amplify.DataStore.start() - await waitForExpectations([secondSnapshot], timeout: 10) + await fulfillment(of: [secondSnapshot], timeout: 10) sink.cancel() } // MARK: - Helpers - func savePostAndWaitForSync(_ post: Post, postSyncedExpctation: AsyncExpectation? = nil) async throws { + func savePostAndWaitForSync(_ post: Post, postSyncedExpctation: XCTestExpectation? = nil) async throws { // Wait for a fulfillment count of 2 (first event due to the locally source mutation saved to the local store // and the second event due to the subscription event received from the remote store) - let receivedPost = postSyncedExpctation ?? asyncExpectation(description: "received Post", - expectedFulfillmentCount: 2) + let receivedPost = postSyncedExpctation ?? { + let e = expectation(description: "received Post") + e.expectedFulfillmentCount = 2 + return e + }() + receivedPost.assertForOverFulfill = false + Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.modelId == post.id { - await receivedPost.fulfill() + receivedPost.fulfill() } } } catch { @@ -684,22 +699,25 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } _ = try await Amplify.DataStore.save(post) - if postSyncedExpctation == nil { - await waitForExpectations([receivedPost], timeout: 100) - } + await fulfillment(of: [receivedPost], timeout: 100) } - func deletePostAndWaitForSync(_ post: Post, postSyncedExpctation: AsyncExpectation? = nil) async throws { + func deletePostAndWaitForSync(_ post: Post, postSyncedExpctation: XCTestExpectation? = nil) async throws { // Wait for a fulfillment count of 2 (first event due to the locally source mutation deleted from the local // store and the second event due to the subscription event received from the remote store) - let deletedPost = postSyncedExpctation ?? asyncExpectation(description: "deleted Post", - expectedFulfillmentCount: 2) + let deletedPost = postSyncedExpctation ?? { + let e = expectation(description: "deleted Post") + e.expectedFulfillmentCount = 2 + return e + }() + deletedPost.assertForOverFulfill = false + Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.modelId == post.id { - await deletedPost.fulfill() + deletedPost.fulfill() } } } catch { @@ -708,9 +726,7 @@ class DataStoreObserveQueryTests: SyncEngineIntegrationTestBase { } _ = try await Amplify.DataStore.delete(post) - if postSyncedExpctation == nil { - await waitForExpectations([deletedPost], timeout: 100) - } + await fulfillment(of: [deletedPost], timeout: 100) } func queryNumberOfPosts() async throws -> Int { diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/AWSDataStoreLazyLoadPostComment4V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/AWSDataStoreLazyLoadPostComment4V2.swift index e385a3c54a..9a315baed5 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/AWSDataStoreLazyLoadPostComment4V2.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/AWSDataStoreLazyLoadPostComment4V2.swift @@ -18,13 +18,13 @@ class AWSDataStoreLazyLoadPostComment4V2: AWSDataStoreLazyLoadBaseTest { let post = Post4V2(title: "title") let comment = Comment4V2(content: "content", post: post) - let commentSynced = asyncExpectation(description: "DataStore start success") + let commentSynced = expectation(description: "DataStore start success") let mutationEvents = Amplify.DataStore.observe(Comment4V2.self) Task { do { for try await mutationEvent in mutationEvents { if mutationEvent.version == 1 && mutationEvent.modelId == comment.id { - await commentSynced.fulfill() + commentSynced.fulfill() } } } catch { @@ -35,7 +35,7 @@ class AWSDataStoreLazyLoadPostComment4V2: AWSDataStoreLazyLoadBaseTest { try await Amplify.DataStore.save(post) try await Amplify.DataStore.save(comment) - await waitForExpectations([commentSynced], timeout: 10) + await fulfillment(of: [commentSynced], timeout: 10) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift index 9618964696..837c304030 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift @@ -232,7 +232,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let post = Post(title: "title") let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -254,7 +254,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(comments.count, 1) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -266,7 +266,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -276,7 +276,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { let post = Post(title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -285,7 +285,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.id == comment.id { try await assertComment(receivedComment, canLazyLoad: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -297,7 +297,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -306,7 +306,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let post = Post(title: "title") let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.id == post.id) Task { for try await querySnapshot in querySnapshots { @@ -323,7 +323,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(comments.count, 1) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -335,7 +335,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -346,13 +346,13 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { let post = Post(title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.id == comment.id) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { try await assertComment(receivedComment, canLazyLoad: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -364,7 +364,7 @@ class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift index c8978f6fa8..89462e259c 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift @@ -195,7 +195,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest try await startAndWaitForReady() let post = Post(postId: UUID().uuidString, title: "title") let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -216,7 +216,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest } XCTAssertEqual(comments.count, 1) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -228,7 +228,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -238,7 +238,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest let post = Post(postId: UUID().uuidString, title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -247,7 +247,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.commentId == comment.commentId { try await assertComment(receivedComment, canLazyLoad: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -259,7 +259,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -268,7 +268,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest try await startAndWaitForReady() let post = Post(postId: UUID().uuidString, title: "title") let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.postId == post.postId) Task { for try await querySnapshot in querySnapshots { @@ -285,7 +285,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest } XCTAssertEqual(comments.count, 1) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -297,7 +297,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -308,13 +308,13 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest let post = Post(postId: UUID().uuidString, title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.commentId == comment.commentId) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { try await assertComment(receivedComment, canLazyLoad: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -326,7 +326,7 @@ final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift index a2ef53f290..a6375821d1 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift @@ -177,7 +177,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest content: "content", postId: post.postId, postTitle: post.title) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -187,7 +187,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest receivedPost.postId == post.postId { let savedComment = try await createAndWaitForSync(comment) try await assertPost(receivedPost, canLazyLoad: savedComment) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -199,7 +199,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -212,7 +212,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest content: "content", postId: post.postId, postTitle: post.title) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -221,7 +221,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.commentId == comment.commentId { assertComment(receivedComment, contains: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -233,7 +233,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -245,14 +245,14 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest content: "content", postId: post.postId, postTitle: post.title) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.postId == post.postId) Task { for try await querySnapshot in querySnapshots { if let receivedPost = querySnapshot.items.first { let savedComment = try await createAndWaitForSync(comment) try await assertPost(receivedPost, canLazyLoad: savedComment) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -264,7 +264,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -278,13 +278,13 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest content: "content", postId: post.postId, postTitle: post.title) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.commentId == comment.commentId) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { assertComment(receivedComment, contains: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -296,7 +296,7 @@ final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPK/AWSDataStoreLazyLoadDefaultPKTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPK/AWSDataStoreLazyLoadDefaultPKTests.swift index 65dcd37aa7..c769340da9 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPK/AWSDataStoreLazyLoadDefaultPKTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPK/AWSDataStoreLazyLoadDefaultPKTests.swift @@ -230,7 +230,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let parent = Parent() let child = Child(parent: parent) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Parent.self) Task { for try await mutationEvent in mutationEvents { @@ -252,7 +252,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(children.count, 1) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -264,7 +264,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -274,7 +274,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { let parent = Parent() let savedParent = try await createAndWaitForSync(parent) let child = Child(parent: parent) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Child.self) Task { for try await mutationEvent in mutationEvents { @@ -283,7 +283,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { let receivedChild = try? mutationEvent.decodeModel(as: Child.self), receivedChild.id == child.id { try await assertChild(receivedChild, canLazyLoad: savedParent) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -295,7 +295,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -304,7 +304,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let parent = Parent() let child = Child(parent: parent) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Parent.self, where: Parent.keys.id == parent.id) Task { for try await querySnapshot in querySnapshots { @@ -321,7 +321,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(children.count, 1) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -333,7 +333,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -344,13 +344,13 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { let parent = Parent() let savedParent = try await createAndWaitForSync(parent) let child = Child(parent: parent) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Child.self, where: Child.keys.id == child.id) Task { for try await querySnapshot in querySnapshots { if let receivedChild = querySnapshot.items.first { try await assertChild(receivedChild, canLazyLoad: savedParent) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -362,7 +362,7 @@ class AWSDataStoreLazyLoadDefaultPKTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParentChild/AWSDataStoreLazyLoadHasOneTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParentChild/AWSDataStoreLazyLoadHasOneTests.swift index 01b7332f4a..72b44fb797 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParentChild/AWSDataStoreLazyLoadHasOneTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParentChild/AWSDataStoreLazyLoadHasOneTests.swift @@ -154,7 +154,7 @@ class AWSDataStoreLazyLoadHasOneTests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: HasOneModels()) try await startAndWaitForReady() let child = HasOneChild() - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(HasOneChild.self) Task { @@ -164,7 +164,7 @@ class AWSDataStoreLazyLoadHasOneTests: AWSDataStoreLazyLoadBaseTest { let receivedChild = try? mutationEvent.decodeModel(as: HasOneChild.self), receivedChild.identifier == child.identifier { - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -176,7 +176,7 @@ class AWSDataStoreLazyLoadHasOneTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/AWSDataStoreLazyLoadBlogPostComment8V2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/AWSDataStoreLazyLoadBlogPostComment8V2Tests.swift index 38864e9921..160931e764 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/AWSDataStoreLazyLoadBlogPostComment8V2Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/AWSDataStoreLazyLoadBlogPostComment8V2Tests.swift @@ -187,7 +187,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas try await startAndWaitForReady() let post = Post(name: "name", randomId: "randomId") let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -209,7 +209,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas } XCTAssertEqual(comments.count, 1) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -221,7 +221,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -231,7 +231,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas let post = Post(name: "name", randomId: "randomId") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -240,7 +240,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.id == comment.id { try await assertComment(receivedComment, canLazyLoad: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -252,7 +252,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -261,7 +261,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas try await startAndWaitForReady() let post = Post(name: "name", randomId: "randomId") let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.id == post.id) Task { for try await querySnapshot in querySnapshots { @@ -278,7 +278,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas } XCTAssertEqual(comments.count, 1) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -290,7 +290,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -301,13 +301,13 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas let post = Post(name: "name", randomId: "randomId") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.id == comment.id) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { try await assertComment(receivedComment, canLazyLoad: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -319,7 +319,7 @@ final class AWSDataStoreLazyLoadBlogPostComment8V2Tests: AWSDataStoreLazyLoadBas XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift index bc29512137..d55cd9356f 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift @@ -180,7 +180,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa try await startAndWaitForReady() let post = Post(title: "title") let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -201,7 +201,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa } XCTAssertEqual(comments.count, 1) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -213,7 +213,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -223,7 +223,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa let post = Post(title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -232,7 +232,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.id == comment.id { try await assertComment(receivedComment, canLazyLoad: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -244,7 +244,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -253,7 +253,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa try await startAndWaitForReady() let post = Post(title: "title") let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.id == post.id) Task { for try await querySnapshot in querySnapshots { @@ -270,7 +270,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa } XCTAssertEqual(comments.count, 1) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -282,7 +282,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -293,13 +293,13 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa let post = Post(title: "title") let savedPost = try await createAndWaitForSync(post) let comment = Comment(content: "content", post: post) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.id == comment.id) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { try await assertComment(receivedComment, canLazyLoad: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -311,7 +311,7 @@ final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLa XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift index 5ef2cce8ba..b750c45194 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift @@ -213,7 +213,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: PostTagModels()) try await startAndWaitForReady() let post = Post(postId: UUID().uuidString, title: "title") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -233,7 +233,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(tags.count, 0) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -244,7 +244,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -253,7 +253,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let tag = Tag(name: "name") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Tag.self) Task { for try await mutationEvent in mutationEvents { @@ -272,7 +272,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(posts.count, 0) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -283,7 +283,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -294,10 +294,11 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { let tag = Tag(name: "name") let savedPost = try await createAndWaitForSync(post) let savedTag = try await createAndWaitForSync(tag) - + _ = savedPost; _ = savedTag + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(PostTag.self) Task { for try await mutationEvent in mutationEvents { @@ -307,7 +308,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { receivedPostTag.id == postTag.id { try await assertPostTag(receivedPostTag, canLazyLoadTag: tag, canLazyLoadPost: post) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -319,7 +320,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -327,7 +328,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: PostTagModels()) try await startAndWaitForReady() let post = Post(postId: UUID().uuidString, title: "title") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.postId == post.postId) Task { for try await querySnapshot in querySnapshots { @@ -343,7 +344,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(tags.count, 0) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -354,7 +355,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -363,7 +364,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { try await startAndWaitForReady() let tag = Tag(name: "name") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Tag.self, where: Tag.keys.id == tag.id) Task { for try await querySnapshot in querySnapshots { @@ -379,7 +380,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { } XCTAssertEqual(posts.count, 0) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -390,7 +391,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -404,13 +405,13 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: PostTag.self, where: PostTag.keys.id == postTag.id) Task { for try await querySnapshot in querySnapshots { if let receivedPostTag = querySnapshot.items.first { try await assertPostTag(receivedPostTag, canLazyLoadTag: tag, canLazyLoadPost: post) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -422,7 +423,7 @@ final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift index 821b2ff7ba..0d0929e5f5 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift @@ -220,7 +220,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Project.self) Task { for try await mutationEvent in mutationEvents { @@ -229,7 +229,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { let receivedProject = try? mutationEvent.decodeModel(as: Project.self), receivedProject.projectId == project.projectId { assertProject(receivedProject, hasTeam: savedTeam) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -241,7 +241,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -249,7 +249,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam1Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Team.self) Task { for try await mutationEvent in mutationEvents { @@ -258,7 +258,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { let receivedTeam = try? mutationEvent.decodeModel(as: Team.self), receivedTeam.teamId == team.teamId { - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -270,7 +270,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -281,13 +281,13 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Project.self, where: Project.keys.projectId == project.projectId) Task { for try await querySnapshot in querySnapshots { if let receivedProject = querySnapshot.items.first { assertProject(receivedProject, hasTeam: savedTeam) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -299,7 +299,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -307,13 +307,13 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam1Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Team.self, where: Team.keys.teamId == team.teamId) Task { for try await querySnapshot in querySnapshots { if let receivedTeam = querySnapshot.items.first { XCTAssertEqual(receivedTeam.teamId, team.teamId) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -325,7 +325,7 @@ class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift index c9ba988d2f..8a2344a0fd 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift @@ -187,7 +187,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Project.self) Task { for try await mutationEvent in mutationEvents { @@ -196,7 +196,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { let receivedProject = try? mutationEvent.decodeModel(as: Project.self), receivedProject.projectId == project.projectId { assertProject(receivedProject, hasTeam: savedTeam) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -208,7 +208,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -216,7 +216,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam2Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Team.self) Task { for try await mutationEvent in mutationEvents { @@ -225,7 +225,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { let receivedTeam = try? mutationEvent.decodeModel(as: Team.self), receivedTeam.teamId == team.teamId { - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -237,7 +237,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -248,13 +248,13 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Project.self, where: Project.keys.projectId == project.projectId) Task { for try await querySnapshot in querySnapshots { if let receivedProject = querySnapshot.items.first { assertProject(receivedProject, hasTeam: savedTeam) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -266,7 +266,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -274,13 +274,13 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam2Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Team.self, where: Team.keys.teamId == team.teamId) Task { for try await querySnapshot in querySnapshots { if let receivedTeam = querySnapshot.items.first { XCTAssertEqual(receivedTeam.teamId, team.teamId) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -292,7 +292,7 @@ class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift index d74c222e97..3a56f66da5 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift @@ -180,7 +180,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest content: "content", post4CommentsPostId: post.postId, post4CommentsTitle: post.title) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Post.self) Task { for try await mutationEvent in mutationEvents { @@ -190,7 +190,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest receivedPost.postId == post.postId { let savedComment = try await createAndWaitForSync(comment) try await assertPost(receivedPost, canLazyLoad: savedComment) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -202,7 +202,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -215,7 +215,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest content: "content", post4CommentsPostId: post.postId, post4CommentsTitle: post.title) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Comment.self) Task { for try await mutationEvent in mutationEvents { @@ -224,7 +224,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest let receivedComment = try? mutationEvent.decodeModel(as: Comment.self), receivedComment.commentId == comment.commentId { assertComment(receivedComment, contains: savedPost) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -236,7 +236,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -248,14 +248,14 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest content: "content", post4CommentsPostId: post.postId, post4CommentsTitle: post.title) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Post.self, where: Post.keys.postId == post.postId) Task { for try await querySnapshot in querySnapshots { if let receivedPost = querySnapshot.items.first { let savedComment = try await createAndWaitForSync(comment) try await assertPost(receivedPost, canLazyLoad: savedComment) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -267,7 +267,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -281,13 +281,13 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest content: "content", post4CommentsPostId: post.postId, post4CommentsTitle: post.title) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Comment.self, where: Comment.keys.commentId == comment.commentId) Task { for try await querySnapshot in querySnapshots { if let receivedComment = querySnapshot.items.first { assertComment(receivedComment, contains: savedPost) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -299,7 +299,7 @@ final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift index 6503d6009d..ade3115dac 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift @@ -138,6 +138,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { queriedProject.teamId = newTeam.teamId queriedProject.teamName = newTeam.name let savedProjectWithNewTeam = try await updateAndWaitForSync(queriedProject) + _ = savedProjectWithNewTeam assertProject(queriedProject, hasTeam: savedNewTeam) } @@ -189,7 +190,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Project.self) Task { for try await mutationEvent in mutationEvents { @@ -198,7 +199,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { let receivedProject = try? mutationEvent.decodeModel(as: Project.self), receivedProject.projectId == project.projectId { assertProject(receivedProject, hasTeam: savedTeam) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -210,7 +211,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -218,7 +219,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam5Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Team.self) Task { for try await mutationEvent in mutationEvents { @@ -227,7 +228,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { let receivedTeam = try? mutationEvent.decodeModel(as: Team.self), receivedTeam.teamId == team.teamId { - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -239,7 +240,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -250,13 +251,13 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Project.self, where: Project.keys.projectId == project.projectId) Task { for try await querySnapshot in querySnapshots { if let receivedProject = querySnapshot.items.first { assertProject(receivedProject, hasTeam: savedTeam) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -268,7 +269,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -276,13 +277,13 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam5Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Team.self, where: Team.keys.teamId == team.teamId) Task { for try await querySnapshot in querySnapshots { if let receivedTeam = querySnapshot.items.first { XCTAssertEqual(receivedTeam.teamId, team.teamId) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -294,7 +295,7 @@ class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift index 0555d463cf..574f2c9d55 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift @@ -185,7 +185,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Project.self) Task { for try await mutationEvent in mutationEvents { @@ -194,7 +194,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { let receivedProject = try? mutationEvent.decodeModel(as: Project.self), receivedProject.projectId == project.projectId { assertProject(receivedProject, hasTeam: savedTeam) - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -206,7 +206,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -214,7 +214,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam6Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let mutationEventReceived = asyncExpectation(description: "Received mutation event") + let mutationEventReceived = expectation(description: "Received mutation event") let mutationEvents = Amplify.DataStore.observe(Team.self) Task { for try await mutationEvent in mutationEvents { @@ -222,8 +222,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { version == 1, let receivedTeam = try? mutationEvent.decodeModel(as: Team.self), receivedTeam.teamId == team.teamId { - - await mutationEventReceived.fulfill() + mutationEventReceived.fulfill() } } } @@ -235,7 +234,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([mutationEventReceived], timeout: 60) + await fulfillment(of: [mutationEventReceived], timeout: 60) mutationEvents.cancel() } @@ -246,13 +245,13 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { let savedTeam = try await createAndWaitForSync(team) let project = initializeProjectWithTeam(team) - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Project.self, where: Project.keys.projectId == project.projectId) Task { for try await querySnapshot in querySnapshots { if let receivedProject = querySnapshot.items.first { assertProject(receivedProject, hasTeam: savedTeam) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -264,7 +263,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } @@ -272,13 +271,13 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { await setup(withModels: ProjectTeam6Models()) try await startAndWaitForReady() let team = Team(teamId: UUID().uuidString, name: "name") - let snapshotReceived = asyncExpectation(description: "Received query snapshot") + let snapshotReceived = expectation(description: "Received query snapshot") let querySnapshots = Amplify.DataStore.observeQuery(for: Team.self, where: Team.keys.teamId == team.teamId) Task { for try await querySnapshot in querySnapshots { if let receivedTeam = querySnapshot.items.first { XCTAssertEqual(receivedTeam.teamId, team.teamId) - await snapshotReceived.fulfill() + snapshotReceived.fulfill() } } } @@ -290,7 +289,7 @@ class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { XCTFail("Failed to send mutation request \(error)") } - await waitForExpectations([snapshotReceived], timeout: 60) + await fulfillment(of: [snapshotReceived], timeout: 60) querySnapshots.cancel() } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreAuthBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreAuthBaseTest.swift index 5149203716..04bfce9c8e 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreAuthBaseTest.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreAuthBaseTest.swift @@ -120,18 +120,18 @@ class AWSDataStoreAuthBaseTest: XCTestCase { // MARK: - Test Helpers func makeExpectations() -> AuthTestExpectations { AuthTestExpectations( - subscriptionsEstablished: AsyncExpectation(description: "Subscriptions established"), - modelsSynced: AsyncExpectation(description: "Models synced"), + subscriptionsEstablished: expectation(description: "Subscriptions established"), + modelsSynced: expectation(description: "Models synced"), - query: AsyncExpectation(description: "Query success"), + query: expectation(description: "Query success"), - mutationSave: AsyncExpectation(description: "Mutation save success"), - mutationSaveProcessed: AsyncExpectation(description: "Mutation save processed"), + mutationSave: expectation(description: "Mutation save success"), + mutationSaveProcessed: expectation(description: "Mutation save processed"), - mutationDelete: AsyncExpectation(description: "Mutation delete success"), - mutationDeleteProcessed: AsyncExpectation(description: "Mutation delete processed"), + mutationDelete: expectation(description: "Mutation delete success"), + mutationDeleteProcessed: expectation(description: "Mutation delete processed"), - ready: AsyncExpectation(description: "Ready") + ready: expectation(description: "Ready") ) } @@ -241,21 +241,17 @@ extension AWSDataStoreAuthBaseTest { XCTFail("Invalid user", file: file, line: line) return } - let signInInvoked = AsyncExpectation(description: "sign in completed") + let signInInvoked = expectation(description: "sign in completed") do { _ = try await Amplify.Auth.signIn(username: user.username, password: user.password, options: nil) - Task { - await signInInvoked.fulfill() - } + signInInvoked.fulfill() } catch(let error) { XCTFail("Signin failure \(error)", file: file, line: line) - Task { - await signInInvoked.fulfill() // won't count as pass - } + signInInvoked.fulfill() // won't count as pass } - await waitForExpectations([signInInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [signInInvoked], timeout: TestCommonConstants.networkTimeout) let signedIn = await isSignedIn() XCTAssert(signedIn, file: file, line: line) @@ -264,13 +260,13 @@ extension AWSDataStoreAuthBaseTest { /// Signout current signed-in user func signOut(file: StaticString = #file, line: UInt = #line) async { - let signoutInvoked = AsyncExpectation(description: "sign out completed") + let signoutInvoked = expectation(description: "sign out completed") Task { _ = await Amplify.Auth.signOut() - await signoutInvoked.fulfill() + signoutInvoked.fulfill() } - await waitForExpectations([signoutInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [signoutInvoked], timeout: TestCommonConstants.networkTimeout) let signedIn = await isSignedIn() XCTAssert(!signedIn, file: file, line: line) @@ -278,19 +274,17 @@ extension AWSDataStoreAuthBaseTest { func isSignedIn() async -> Bool { - let checkIsSignedInCompleted = AsyncExpectation(description: "retrieve auth session completed") + let checkIsSignedInCompleted = expectation(description: "retrieve auth session completed") var resultOptional: Bool? do { let authSession = try await Amplify.Auth.fetchAuthSession() resultOptional = authSession.isSignedIn - Task { - await checkIsSignedInCompleted.fulfill() - } + checkIsSignedInCompleted.fulfill() } catch(let error) { fatalError("Failed to get auth session \(error)") } - await waitForExpectations([checkIsSignedInCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [checkIsSignedInCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get isSignedIn for user") return false @@ -300,7 +294,7 @@ extension AWSDataStoreAuthBaseTest { } func getUserSub() async -> String { - let retrieveUserSubCompleted = AsyncExpectation(description: "retrieve userSub completed") + let retrieveUserSubCompleted = expectation(description: "retrieve userSub completed") var resultOptional: String? do { let authSession = try await Amplify.Auth.fetchAuthSession() @@ -311,9 +305,7 @@ extension AWSDataStoreAuthBaseTest { switch cognitoAuthSession.getUserSub() { case .success(let userSub): resultOptional = userSub - Task { - await retrieveUserSubCompleted.fulfill() - } + retrieveUserSubCompleted.fulfill() case .failure(let error): XCTFail("Failed to get auth session \(error)") } @@ -321,7 +313,7 @@ extension AWSDataStoreAuthBaseTest { XCTFail("Failed to get auth session \(error)") } - await waitForExpectations([retrieveUserSubCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [retrieveUserSubCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get userSub for user") return "" @@ -331,7 +323,7 @@ extension AWSDataStoreAuthBaseTest { } func getIdentityId() async -> String { - let retrieveIdentityCompleted = AsyncExpectation(description: "retrieve identity completed") + let retrieveIdentityCompleted = expectation(description: "retrieve identity completed") var resultOptional: String? do { let authSession = try await Amplify.Auth.fetchAuthSession() @@ -342,16 +334,14 @@ extension AWSDataStoreAuthBaseTest { switch cognitoAuthSession.getIdentityId() { case .success(let identityId): resultOptional = identityId - Task { - await retrieveIdentityCompleted.fulfill() - } + retrieveIdentityCompleted.fulfill() case .failure(let error): XCTFail("Failed to get auth session \(error)") } } catch(let error) { XCTFail("Failed to get auth session \(error)") } - await waitForExpectations([retrieveIdentityCompleted], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [retrieveIdentityCompleted], timeout: TestCommonConstants.networkTimeout) guard let result = resultOptional else { XCTFail("Could not get identityId for user") return "" @@ -365,19 +355,17 @@ extension AWSDataStoreAuthBaseTest { file: StaticString = #file, line: UInt = #line) async -> M? { var queriedModel: M? - let queriedInvoked = AsyncExpectation(description: "Model queried") + let queriedInvoked = expectation(description: "Model queried") do { let model = try await Amplify.DataStore.query(M.self, byId: id) queriedModel = model - Task { - await queriedInvoked.fulfill() - } + queriedInvoked.fulfill() } catch(let error) { XCTFail("Failed to query model \(error)", file: file, line: line) } - await waitForExpectations([queriedInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [queriedInvoked], timeout: TestCommonConstants.networkTimeout) return queriedModel } } @@ -401,11 +389,9 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.query.fulfill() - } + expectations.query.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.query], + await fulfillment(of: [expectations.query], timeout: 60) } @@ -421,25 +407,19 @@ extension AWSDataStoreAuthBaseTest { .sink { event in // subscription fulfilled if event.eventName == dataStoreEvents.subscriptionsEstablished { - Task { - await expectations.subscriptionsEstablished.fulfill() - } + expectations.subscriptionsEstablished.fulfill() } // modelsSynced fulfilled if event.eventName == dataStoreEvents.modelSynced { modelSyncedCount += 1 if modelSyncedCount == expectedModelSynced { - Task { - await expectations.modelsSynced.fulfill() - } + expectations.modelsSynced.fulfill() } } if event.eventName == dataStoreEvents.ready { - Task { - await expectations.ready.fulfill() - } + expectations.ready.fulfill() } } .store(in: &requests) @@ -449,7 +429,7 @@ extension AWSDataStoreAuthBaseTest { } catch(let error) { XCTFail("Failure due to error: \(error)") } - await waitForExpectations([expectations.subscriptionsEstablished, + await fulfillment(of: [expectations.subscriptionsEstablished, expectations.modelsSynced, expectations.ready], timeout: 60) @@ -474,16 +454,12 @@ extension AWSDataStoreAuthBaseTest { } if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { - Task { - await expectations.mutationSaveProcessed.fulfill() - } + expectations.mutationSaveProcessed.fulfill() return } if mutationEvent.mutationType == GraphQLMutationType.delete.rawValue { - Task { - await expectations.mutationDeleteProcessed.fulfill() - } + expectations.mutationDeleteProcessed.fulfill() return } } @@ -498,12 +474,10 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.mutationSave.fulfill() - } + expectations.mutationSave.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.mutationSave, expectations.mutationSaveProcessed], timeout: 60) + await fulfillment(of: [expectations.mutationSave, expectations.mutationSaveProcessed], timeout: 60) Amplify.Publisher.create { try await Amplify.DataStore.delete(model) @@ -514,12 +488,10 @@ extension AWSDataStoreAuthBaseTest { } receiveValue: { posts in XCTAssertNotNil(posts) - Task { - await expectations.mutationDelete.fulfill() - } + expectations.mutationDelete.fulfill() }.store(in: &requests) - await waitForExpectations([expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60) + await fulfillment(of: [expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60) } func assertUsedAuthTypes( @@ -549,15 +521,15 @@ extension AWSDataStoreAuthBaseTest { // MARK: - Expectations extension AWSDataStoreAuthBaseTest { struct AuthTestExpectations { - var subscriptionsEstablished: AsyncExpectation - var modelsSynced: AsyncExpectation - var query: AsyncExpectation - var mutationSave: AsyncExpectation - var mutationSaveProcessed: AsyncExpectation - var mutationDelete: AsyncExpectation - var mutationDeleteProcessed: AsyncExpectation - var ready: AsyncExpectation - var expectations: [AsyncExpectation] { + var subscriptionsEstablished: XCTestExpectation + var modelsSynced: XCTestExpectation + var query: XCTestExpectation + var mutationSave: XCTestExpectation + var mutationSaveProcessed: XCTestExpectation + var mutationDelete: XCTestExpectation + var mutationDeleteProcessed: XCTestExpectation + var ready: XCTestExpectation + var expectations: [XCTestExpectation] { return [subscriptionsEstablished, modelsSynced, query, diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthCombinationTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthCombinationTests.swift index 560c3abb70..9ba5f8e61a 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthCombinationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthCombinationTests.swift @@ -22,7 +22,7 @@ class AWSDataStoreMultiAuthCombinationTests: AWSDataStoreAuthBaseTest { await signIn(user: user1) let expectations = makeExpectations() - let startExpectation = asyncExpectation(description: "DataStore start success") + let startExpectation = expectation(description: "DataStore start success") await assertDataStoreReady(expectations) @@ -30,20 +30,20 @@ class AWSDataStoreMultiAuthCombinationTests: AWSDataStoreAuthBaseTest { Task { do { try await Amplify.DataStore.start() - await startExpectation.fulfill() + startExpectation.fulfill() } catch(let error) { XCTFail("DataStore start failure \(error)") } } // we're only interested in "ready-state" expectations - await expectations.query.fulfill() - await expectations.mutationSave.fulfill() - await expectations.mutationSaveProcessed.fulfill() - await expectations.mutationDelete.fulfill() - await expectations.mutationDeleteProcessed.fulfill() + expectations.query.fulfill() + expectations.mutationSave.fulfill() + expectations.mutationSaveProcessed.fulfill() + expectations.mutationDelete.fulfill() + expectations.mutationDeleteProcessed.fulfill() - await waitForExpectations([ + await fulfillment(of: [ startExpectation, expectations.query, expectations.mutationSave, diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthSingleRuleTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthSingleRuleTests.swift index 9e4b71e10d..fc9264598c 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthSingleRuleTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginMultiAuthTests/AWSDataStoreMultiAuthSingleRuleTests.swift @@ -97,19 +97,19 @@ class AWSDataStoreMultiAuthSingleRuleTests: AWSDataStoreAuthBaseTest { let expectations = makeExpectations() // we're only interested in "ready-state" expectations - await expectations.query.fulfill() - await expectations.mutationSave.fulfill() - await expectations.mutationSaveProcessed.fulfill() - await expectations.mutationDelete.fulfill() - await expectations.mutationDeleteProcessed.fulfill() + expectations.query.fulfill() + expectations.mutationSave.fulfill() + expectations.mutationSaveProcessed.fulfill() + expectations.mutationDelete.fulfill() + expectations.mutationDeleteProcessed.fulfill() // GroupUPPost won't sync for user2 but DataStore should reach a // "ready" state - await expectations.modelsSynced.fulfill() + expectations.modelsSynced.fulfill() await assertDataStoreReady(expectations, expectedModelSynced: 0) await fulfillment(of: [authTypeExpecation], timeout: 5) - await waitForExpectations([ + await fulfillment(of: [ expectations.query, expectations.mutationSave, expectations.mutationSaveProcessed, diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithCustomTimestampTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithCustomTimestampTests.swift index 0e3862ead6..cae0ffcc0a 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithCustomTimestampTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithCustomTimestampTests.swift @@ -93,7 +93,7 @@ class DataStoreModelWithCustomTimestampTests: SyncEngineIntegrationV2TestBase { } } - wait(for: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) /* This failed with "The variables input contains a field name \'id\' that is not defined for input object @@ -114,7 +114,7 @@ class DataStoreModelWithCustomTimestampTests: SyncEngineIntegrationV2TestBase { // XCTFail("Failed \(error)") // } // } -// wait(for: [updateCompleted, updateReceived], timeout: TestCommonConstants.networkTimeout) +// await fulfillment(of: [updateCompleted, updateReceived], timeout: TestCommonConstants.networkTimeout) let deleteCompleted = expectation(description: "delete completed") Amplify.DataStore.delete(TodoCustomTimestampV2.self, withId: todo.id) { event in @@ -125,7 +125,7 @@ class DataStoreModelWithCustomTimestampTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [deleteCompleted, deleteReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteCompleted, deleteReceived], timeout: TestCommonConstants.networkTimeout) } func saveTodo(content: String) -> TodoCustomTimestampV2? { @@ -141,7 +141,7 @@ class DataStoreModelWithCustomTimestampTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithDefaultValueTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithDefaultValueTests.swift index 1b209a86ad..9a4643ff8c 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithDefaultValueTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithDefaultValueTests.swift @@ -75,7 +75,7 @@ class DataStoreModelWithDefaultValueTests: SyncEngineIntegrationV2TestBase { } } - wait(for: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) } func testSaveModelWithoutExplicitContentAndSync() async throws { @@ -126,7 +126,7 @@ class DataStoreModelWithDefaultValueTests: SyncEngineIntegrationV2TestBase { } } - wait(for: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getTodoCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) } func saveTodo(content: String?) -> TodoWithDefaultValueV2? { @@ -142,7 +142,7 @@ class DataStoreModelWithDefaultValueTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithSecondaryIndexTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithSecondaryIndexTests.swift index a477ae8fda..88545d49c8 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithSecondaryIndexTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreModelWithSecondaryIndexTests.swift @@ -86,7 +86,7 @@ class DataStoreModelWithSecondaryIndexTests: SyncEngineIntegrationV2TestBase { } } - wait(for: [getCustomerCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [getCustomerCompleted, createReceived], timeout: TestCommonConstants.networkTimeout) customer.name = updatedName let updateCompleted = expectation(description: "update completed") @@ -99,7 +99,7 @@ class DataStoreModelWithSecondaryIndexTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [updateCompleted, updateReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [updateCompleted, updateReceived], timeout: TestCommonConstants.networkTimeout) let deleteCompleted = expectation(description: "delete completed") Amplify.DataStore.delete(CustomerSecondaryIndexV2.self, withId: customer.id) { event in @@ -110,7 +110,7 @@ class DataStoreModelWithSecondaryIndexTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [deleteCompleted, deleteReceived], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [deleteCompleted, deleteReceived], timeout: TestCommonConstants.networkTimeout) } func saveCustomer(name: String, accountRepresentativeID: String) -> CustomerSecondaryIndexV2? { @@ -126,7 +126,7 @@ class DataStoreModelWithSecondaryIndexTests: SyncEngineIntegrationV2TestBase { XCTFail("Failed \(error)") } } - wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreSchemaDriftTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreSchemaDriftTests.swift index b18565bac5..e52dad6dd6 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreSchemaDriftTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginV2Tests/TransformerV2/DataStoreSchemaDriftTests.swift @@ -47,7 +47,7 @@ class DataStoreSchemaDriftTests: SyncEngineIntegrationV2TestBase { try startAmplify { startSuccess.fulfill() } - wait(for: [startSuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [startSuccess], timeout: TestCommonConstants.networkTimeout) // Save some data with the missing enum case, do this by directly calling API // with a custom variables object. Later, decoding will fail. let saveSuccessWithTransformationError = expectation(description: "saved success with transformation error") @@ -87,7 +87,7 @@ class DataStoreSchemaDriftTests: SyncEngineIntegrationV2TestBase { } } - wait(for: [saveSuccessWithTransformationError], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [saveSuccessWithTransformationError], timeout: TestCommonConstants.networkTimeout) let dataStoreStartSuccess = expectation(description: "DataStore start success") Amplify.DataStore.start { result in @@ -96,7 +96,7 @@ class DataStoreSchemaDriftTests: SyncEngineIntegrationV2TestBase { } dataStoreStartSuccess.fulfill() } - wait(for: [dataStoreStartSuccess], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [dataStoreStartSuccess], timeout: TestCommonConstants.networkTimeout) // Assert that the sync engine does not retry on schema drift scenario guard let remoteSyncEngine = DataStoreInternal.getRemoteSyncEngine() else { @@ -122,7 +122,7 @@ class DataStoreSchemaDriftTests: SyncEngineIntegrationV2TestBase { syncEngineRestarting.fulfill() } }.store(in: &subscriptions) - wait(for: [syncEngineCleanedUp, syncEngineFailed], timeout: TestCommonConstants.networkTimeout) - wait(for: [syncEngineRestarting], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [syncEngineCleanedUp, syncEngineFailed], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [syncEngineRestarting], timeout: TestCommonConstants.networkTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressBaseTest.swift index 613e17fb10..e6923fd0ed 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressBaseTest.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressBaseTest.swift @@ -74,6 +74,6 @@ class DataStoreStressBaseTest: XCTestCase { try await Amplify.DataStore.start() - await waitForExpectations(timeout: networkTimeout) + await fulfillment(of: [eventReceived], timeout: networkTimeout) } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressTests.swift index 8bec855239..444bb65f76 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressTests.swift +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreStressTests/DataStoreStressTests.swift @@ -48,8 +48,8 @@ final class DataStoreStressTests: DataStoreStressBaseTest { posts.append(post) } - let postsSyncedToCloud = asyncExpectation(description: "All posts saved and synced to cloud", - expectedFulfillmentCount: concurrencyLimit) + let postsSyncedToCloud = expectation(description: "All posts saved and synced to cloud") + postsSyncedToCloud.expectedFulfillmentCount = concurrencyLimit let postsCopy = posts let mutationEvents = Amplify.DataStore.observe(Post.self) @@ -62,7 +62,7 @@ final class DataStoreStressTests: DataStoreStressBaseTest { if mutationEvent.mutationType == MutationEvent.MutationType.create.rawValue, mutationEvent.version == 1 { - await postsSyncedToCloud.fulfill() + postsSyncedToCloud.fulfill() } } } catch { @@ -78,7 +78,7 @@ final class DataStoreStressTests: DataStoreStressBaseTest { } } - await waitForExpectations([postsSyncedToCloud], timeout: networkTimeout) + await fulfillment(of: [postsSyncedToCloud], timeout: networkTimeout) } /// Perform concurrent saves and observe the data successfuly synced from cloud @@ -96,9 +96,9 @@ final class DataStoreStressTests: DataStoreStressBaseTest { let posts = await saveAndSyncPosts(concurrencyLimit: concurrencyLimit) - let localQueryForPosts = asyncExpectation(description: "Query for the post is successful", - expectedFulfillmentCount: concurrencyLimit) - + let localQueryForPosts = expectation(description: "Query for the post is successful") + localQueryForPosts.expectedFulfillmentCount = concurrencyLimit + DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { let queriedPost = try await Amplify.DataStore.query(Post.self, byId: posts[index].id) @@ -106,11 +106,11 @@ final class DataStoreStressTests: DataStoreStressBaseTest { XCTAssertEqual(posts[index].id, queriedPost?.id) XCTAssertEqual(posts[index].title, queriedPost?.title) XCTAssertEqual(posts[index].content, queriedPost?.content) - await localQueryForPosts.fulfill() + localQueryForPosts.fulfill() } } - await waitForExpectations([localQueryForPosts], timeout: networkTimeout) + await fulfillment(of: [localQueryForPosts], timeout: networkTimeout) } /// Perform concurrent saves and observe the data successfuly synced from cloud @@ -128,9 +128,8 @@ final class DataStoreStressTests: DataStoreStressBaseTest { let posts = await saveAndSyncPosts(concurrencyLimit: concurrencyLimit) - let localQueryForPosts = asyncExpectation(description: "Query for the post is successful", - expectedFulfillmentCount: concurrencyLimit) - + let localQueryForPosts = expectation(description: "Query for the post is successful") + localQueryForPosts.expectedFulfillmentCount = concurrencyLimit DispatchQueue.concurrentPerform(iterations: concurrencyLimit) { index in Task { let predicate = Post.keys.id.eq(posts[index].id).and(Post.keys.title.eq(posts[index].title)) @@ -140,11 +139,11 @@ final class DataStoreStressTests: DataStoreStressBaseTest { XCTAssertEqual(posts[index].id, queriedPosts[0].id) XCTAssertEqual(posts[index].title, queriedPosts[0].title) XCTAssertEqual(posts[index].content, queriedPosts[0].content) - await localQueryForPosts.fulfill() + localQueryForPosts.fulfill() } } - await waitForExpectations([localQueryForPosts], timeout: networkTimeout) + await fulfillment(of: [localQueryForPosts], timeout: networkTimeout) } /// Perform concurrent saves and observe the data successfuly synced from cloud. Then delete the items afterwards @@ -164,12 +163,12 @@ final class DataStoreStressTests: DataStoreStressBaseTest { let posts = await saveAndSyncPosts(concurrencyLimit: concurrencyLimit) - let postsDeletedLocally = asyncExpectation(description: "All posts deleted locally", - expectedFulfillmentCount: concurrencyLimit) - - let postsDeletedFromCloud = asyncExpectation(description: "All posts deleted and synced to cloud", - expectedFulfillmentCount: concurrencyLimit) + let postsDeletedLocally = expectation(description: "All posts deleted locally") + postsDeletedLocally.expectedFulfillmentCount = concurrencyLimit + let postsDeletedFromCloud = expectation(description: "All posts deleted and synced to cloud") + postsDeletedFromCloud.expectedFulfillmentCount = concurrencyLimit + let mutationEvents = Amplify.DataStore.observe(Post.self) Task { do { @@ -180,10 +179,10 @@ final class DataStoreStressTests: DataStoreStressBaseTest { if mutationEvent.mutationType == MutationEvent.MutationType.delete.rawValue, mutationEvent.version == 1 { - await postsDeletedLocally.fulfill() + postsDeletedLocally.fulfill() } else if mutationEvent.mutationType == MutationEvent.MutationType.delete.rawValue, mutationEvent.version == 2 { - await postsDeletedFromCloud.fulfill() + postsDeletedFromCloud.fulfill() } } } catch { @@ -197,7 +196,7 @@ final class DataStoreStressTests: DataStoreStressBaseTest { } } - await waitForExpectations([postsDeletedLocally, postsDeletedFromCloud], timeout: networkTimeout) + await fulfillment(of: [postsDeletedLocally, postsDeletedFromCloud], timeout: networkTimeout) } @@ -213,9 +212,9 @@ final class DataStoreStressTests: DataStoreStressBaseTest { posts.append(post) } - let postsSyncedToCloud = asyncExpectation(description: "All posts saved and synced to cloud", - expectedFulfillmentCount: concurrencyLimit) - + let postsSyncedToCloud = expectation(description: "All posts saved and synced to cloud") + postsSyncedToCloud.expectedFulfillmentCount = concurrencyLimit + let postsCopy = posts let mutationEvents = Amplify.DataStore.observe(Post.self) Task { @@ -227,7 +226,7 @@ final class DataStoreStressTests: DataStoreStressBaseTest { if mutationEvent.mutationType == MutationEvent.MutationType.create.rawValue, mutationEvent.version == 1 { - await postsSyncedToCloud.fulfill() + postsSyncedToCloud.fulfill() } } } catch { @@ -242,7 +241,7 @@ final class DataStoreStressTests: DataStoreStressBaseTest { _ = try await Amplify.DataStore.save(capturedPosts[index]) } } - await waitForExpectations([postsSyncedToCloud], timeout: networkTimeout) + await fulfillment(of: [postsSyncedToCloud], timeout: networkTimeout) return capturedPosts } diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+ClientBehavior.swift b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+ClientBehavior.swift index 8fc5ec2d33..f79c09981f 100644 --- a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+ClientBehavior.swift +++ b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+ClientBehavior.swift @@ -97,9 +97,14 @@ extension AWSLocationGeoPlugin { country: $0.country) } return places + } catch let error as GeoErrorConvertible { + throw error.geoError } catch { - let geoError = GeoErrorHelper.mapAWSLocationError(error) - throw geoError + throw Geo.Error.unknown( + error.localizedDescription, + "See underlying error.", + error + ) } } @@ -167,9 +172,14 @@ extension AWSLocationGeoPlugin { country: $0.country) } return places + } catch let error as GeoErrorConvertible { + throw error.geoError } catch { - let geoError = GeoErrorHelper.mapAWSLocationError(error) - throw geoError + throw Geo.Error.unknown( + error.localizedDescription, + "See underlying error.", + error + ) } } diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift index c210cdd23f..5bf5d34e23 100644 --- a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift +++ b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift @@ -8,7 +8,7 @@ import Foundation import Amplify import AWSPluginsCore -@_spi(FoundationClientEngine) import AWSPluginsCore +@_spi(PluginHTTPClientEngine) import AWSPluginsCore import AWSLocation import AWSClientRuntime @@ -30,17 +30,13 @@ extension AWSLocationGeoPlugin { let authService = AWSAuthService() let credentialsProvider = authService.getCredentialsProvider() let region = configuration.regionName + // TODO: FrameworkMetadata Replacement let serviceConfiguration = try LocationClient.LocationClientConfiguration( - credentialsProvider: credentialsProvider, - frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(), - region: region) + region: region, + credentialsProvider: credentialsProvider + ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - serviceConfiguration.httpClientEngine = FoundationClientEngine() - #endif + serviceConfiguration.httpClientEngine = .userAgentEngine(for: serviceConfiguration) let location = LocationClient(config: serviceConfiguration) let locationService = AWSLocationAdapter(location: location) diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorConvertible.swift b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorConvertible.swift new file mode 100644 index 0000000000..ad35aea33a --- /dev/null +++ b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorConvertible.swift @@ -0,0 +1,79 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation +import AWSLocation +import AWSClientRuntime + +protocol GeoErrorConvertible { + var geoError: Geo.Error { get } +} + +extension AWSLocation.AccessDeniedException: GeoErrorConvertible { + var geoError: Geo.Error { + .accessDenied( + message ?? "", + GeoPluginErrorConstants.accessDenied, + self + ) + } +} + +extension AWSLocation.InternalServerException: GeoErrorConvertible { + var geoError: Geo.Error { + .serviceError( + message ?? "", + GeoPluginErrorConstants.internalServer, + self + ) + } +} + +extension AWSLocation.ResourceNotFoundException: GeoErrorConvertible { + var geoError: Geo.Error { + .serviceError( + message ?? "", + GeoPluginErrorConstants.resourceNotFound, + self + ) + } +} + +extension AWSLocation.ThrottlingException: GeoErrorConvertible { + var geoError: Geo.Error { + .serviceError( + message ?? "", + GeoPluginErrorConstants.throttling, + self + ) + } +} + +extension AWSLocation.ValidationException: GeoErrorConvertible { + var geoError: Geo.Error { + .serviceError( + message ?? "", + GeoPluginErrorConstants.validation, + self + ) + } +} + +extension AWSClientRuntime.UnknownAWSHTTPServiceError: GeoErrorConvertible { + var geoError: Geo.Error { + .unknown( + """ + Unknown service error occured with: + - status: \(httpResponse.statusCode) + - message: \(message ?? "") + """, + "", + self + ) + } +} diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorHelper.swift b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorHelper.swift deleted file mode 100644 index c21666a68e..0000000000 --- a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/Support/Utils/GeoErrorHelper.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -import AWSLocation - -class GeoErrorHelper { - static func getDefaultError(_ error: Error) -> Geo.Error { - return Geo.Error.unknown(error.localizedDescription, "See underlying error.", error) - } - - static func mapAWSLocationError(_ error: Error) -> Geo.Error { - let defaultError = GeoErrorHelper.getDefaultError(error) - - if let searchPlaceIndexForTextOutputError = error as? SearchPlaceIndexForTextOutputError { - return GeoErrorHelper.mapError(error: searchPlaceIndexForTextOutputError) ?? defaultError - } else if let searchPlaceIndexForPositionOutputError = error as? SearchPlaceIndexForPositionOutputError { - return GeoErrorHelper.mapError(error: searchPlaceIndexForPositionOutputError) ?? defaultError - } - - return defaultError - } - - static func mapError(error: SearchPlaceIndexForTextOutputError) -> Geo.Error? { - switch error { - case .accessDeniedException(let accessDeniedException): - return Geo.Error.accessDenied(accessDeniedException.message ?? "", GeoPluginErrorConstants.accessDenied, error) - case .internalServerException(let internalServerException): - return Geo.Error.serviceError(internalServerException.message ?? "", GeoPluginErrorConstants.internalServer, error) - case .resourceNotFoundException(let resournceNotFoundException): - return Geo.Error.serviceError(resournceNotFoundException.message ?? "", GeoPluginErrorConstants.resourceNotFound, error) - case .throttlingException(let throttlingException): - return Geo.Error.serviceError(throttlingException.message ?? "", GeoPluginErrorConstants.throttling, error) - case .validationException(let validationException): - return Geo.Error.serviceError(validationException.message ?? "", GeoPluginErrorConstants.validation, error) - case .unknown(let unknownAWSHttpServiceError): - return Geo.Error.unknown(unknownAWSHttpServiceError._message ?? "", "See underlying error.", error) - } - } - - static func mapError(error: SearchPlaceIndexForPositionOutputError) -> Geo.Error? { - switch error { - case .accessDeniedException(let accessDeniedException): - return Geo.Error.accessDenied(accessDeniedException.message ?? "", GeoPluginErrorConstants.accessDenied, error) - case .internalServerException(let internalServerException): - return Geo.Error.serviceError(internalServerException.message ?? "", GeoPluginErrorConstants.internalServer, error) - case .resourceNotFoundException(let resournceNotFoundException): - return Geo.Error.serviceError(resournceNotFoundException.message ?? "", GeoPluginErrorConstants.resourceNotFound, error) - case .throttlingException(let throttlingException): - return Geo.Error.serviceError(throttlingException.message ?? "", GeoPluginErrorConstants.throttling, error) - case .validationException(let validationException): - return Geo.Error.serviceError(validationException.message ?? "", GeoPluginErrorConstants.validation, error) - case .unknown(let unknownAWSHttpServiceError): - return Geo.Error.unknown(unknownAWSHttpServiceError._message ?? "", "See underlying error.", error) - } - } -} diff --git a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Configuration/AWSLocationGeoPluginConfigurationTests.swift b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Configuration/AWSLocationGeoPluginConfigurationTests.swift index a1566b2fe2..0d69798866 100644 --- a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Configuration/AWSLocationGeoPluginConfigurationTests.swift +++ b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Configuration/AWSLocationGeoPluginConfigurationTests.swift @@ -382,12 +382,10 @@ class AWSLocationGeoPluginConfigurationTests: XCTestCase { (AWSLocationGeoPluginConfiguration.Section.maps.key, mapsConfigJSON), (AWSLocationGeoPluginConfiguration.Section.searchIndices.key, GeoPluginTestConfig.searchConfigJSON)) XCTAssertThrowsError(try AWSLocationGeoPluginConfiguration(config: config)) { error in - guard case let PluginError.pluginConfigurationError(errorDescription, _, _) = error else { + guard case PluginError.pluginConfigurationError(_, _, _) = error else { XCTFail("Expected PluginError pluginConfigurationError, got: \(error)") return } - XCTAssertEqual(errorDescription, - GeoPluginConfigError.mapStyleURLInvalid(mapName: mapName).errorDescription) } } diff --git a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/GeoErrorHelperTests.swift b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/GeoErrorHelperTests.swift deleted file mode 100644 index 9ae8f0ec1f..0000000000 --- a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/GeoErrorHelperTests.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -@testable import AWSClientRuntime -@testable import AWSLocation -@testable import Amplify -@testable import AWSLocationGeoPlugin -import XCTest - -class GeoErrorHelperTests: AWSLocationGeoPluginTestBase { - /// - Given: a generic error - /// - When: GeoErrorHelper.mapAWSLocationError is called with the generic error - /// - Then: a default Geo.Error.unknown is returned - func testGeoErrorHelperMapsDefaultError() { - let error = GeoErrorHelper.mapAWSLocationError(GenericError.validation) - switch error { - case .unknown(_, _, _): - break - default: - XCTFail("Failed to map to default error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of access denied - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.accessDenied is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorAccessDenied() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.accessDeniedException(AccessDeniedException())) - switch error { - case .accessDenied(_, _, _): - break - default: - XCTFail("Failed to map to default error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of internal server - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorInternalServer() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.internalServerException(InternalServerException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to default error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of resource not found - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorResourceNotFound() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.resourceNotFoundException(ResourceNotFoundException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of throttling - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorThrottling() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.throttlingException(ThrottlingException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of validation - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorValidation() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.validationException(ValidationException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForTextOutputError of unknown - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForTextOutputError - /// - Then: a default Geo.Error.unknown is returned - func testGeoErrorHelperMapsSearchPlaceIndexForTextOutputErrorUnknown() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForTextOutputError.unknown(UnknownAWSHttpServiceError())) - switch error { - case .unknown(_, _, _): - break - default: - XCTFail("Failed to map to unknown error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of access denied - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.accessDenied is returned - func testGeoErrorHelperMapsSearchPlaceIndexForPositionOutputErrorAccessDenied() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.accessDeniedException(AccessDeniedException())) - switch error { - case .accessDenied(_, _, _): - break - default: - XCTFail("Failed to map to default error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of internal server - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForPositionOutputErrorInternalServer() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.internalServerException(InternalServerException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to default error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of resource not found - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForPositionOutputErrorErrorResourceNotFound() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.resourceNotFoundException(ResourceNotFoundException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of throttling - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForPositionOutputErrorThrottling() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.throttlingException(ThrottlingException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of validation - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.serviceError is returned - func testGeoErrorHelperMapsSearchPlaceIndexForPositionOutputErrorValidation() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.validationException(ValidationException())) - switch error { - case .serviceError(_, _, _): - break - default: - XCTFail("Failed to map to service error") - } - } - - /// - Given: SearchPlaceIndexForPositionOutputError of unknown - /// - When: GeoErrorHelper.mapAWSLocationError is called with the SearchPlaceIndexForPositionOutputError - /// - Then: a default Geo.Error.unknown is returned - func testGeoErrorHelperMapsSSearchPlaceIndexForPositionOutputErrorUnknown() { - let error = GeoErrorHelper.mapAWSLocationError(SearchPlaceIndexForPositionOutputError.unknown(UnknownAWSHttpServiceError())) - switch error { - case .unknown(_, _, _): - break - default: - XCTFail("Failed to map to unknown error") - } - } -} - -enum GenericError: Error { - case validation -} diff --git a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSClientConfiguration.swift b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSClientConfiguration.swift index 378d825711..ec24a7486d 100644 --- a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSClientConfiguration.swift +++ b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSClientConfiguration.swift @@ -14,56 +14,17 @@ import XCTest @testable import AWSLocationGeoPlugin @testable import AWSPluginsTestCommon -class MockAWSClientConfiguration: LocationClientConfigurationProtocol { - var encoder: ClientRuntime.RequestEncoder? - - var decoder: ClientRuntime.ResponseDecoder? - - var httpClientEngine: ClientRuntime.HttpClientEngine - - var httpClientConfiguration: ClientRuntime.HttpClientConfiguration - - var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator - - var clientLogMode: ClientRuntime.ClientLogMode - - var partitionID: String? - - var useFIPS: Bool? - - var useDualStack: Bool? - - var endpoint: String? - - var credentialsProvider: CredentialsProvider - - var region: String? - - var signingRegion: String? - - var endpointResolver: EndpointResolver - - var regionResolver: RegionResolver? - - var frameworkMetadata: FrameworkMetadata? - - var logger: LogAgent - - var retryer: SDKRetryer - - init(config: AWSLocationGeoPluginConfiguration) throws { - let defaultSDKRuntimeConfig = try DefaultSDKRuntimeConfiguration("MockAWSClientConfiguration") - - self.httpClientEngine = defaultSDKRuntimeConfig.httpClientEngine - self.httpClientConfiguration = defaultSDKRuntimeConfig.httpClientConfiguration - self.idempotencyTokenGenerator = defaultSDKRuntimeConfig.idempotencyTokenGenerator - self.clientLogMode = defaultSDKRuntimeConfig.clientLogMode - self.credentialsProvider = MockAWSAuthService().getCredentialsProvider() - self.region = config.regionName - self.signingRegion = "" - self.endpointResolver = MockEndPointResolver() - self.logger = MockLogAgent() - self.retryer = try SDKRetryer(options: RetryOptions(jitterMode: .default)) +extension LocationClient.LocationClientConfiguration { + static func mock(region: String) throws -> LocationClient.LocationClientConfiguration { + try .init( + region: region, + credentialsProvider: MockAWSAuthService().getCredentialsProvider(), + serviceSpecific: .init( + endpointResolver: MockEndPointResolver() + ), + signingRegion: "", + retryMode: .standard + ) } } diff --git a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSLocation.swift b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSLocation.swift index fe7a6cb9d8..0dc4080432 100644 --- a/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSLocation.swift +++ b/AmplifyPlugins/Geo/Tests/AWSLocationGeoPluginTests/Mocks/MockAWSLocation.swift @@ -27,7 +27,8 @@ public class MockAWSLocation: AWSLocationBehavior { public init(pluginConfig: AWSLocationGeoPluginConfiguration) throws { self.locationClient = try LocationClient( - config: MockAWSClientConfiguration(config: pluginConfig)) + config: .mock(region: pluginConfig.regionName) + ) } public func getEscapeHatch() -> LocationClient { diff --git a/AmplifyPlugins/Geo/Tests/GeoHostApp/GeoStressTests/GeoStressTests.swift b/AmplifyPlugins/Geo/Tests/GeoHostApp/GeoStressTests/GeoStressTests.swift index 0f9becbf46..a512832e52 100644 --- a/AmplifyPlugins/Geo/Tests/GeoHostApp/GeoStressTests/GeoStressTests.swift +++ b/AmplifyPlugins/Geo/Tests/GeoHostApp/GeoStressTests/GeoStressTests.swift @@ -44,21 +44,22 @@ final class GeoStressTests: XCTestCase { /// - Place results are returned. /// func testMultipleSearchForText() async { - let successExpectation = asyncExpectation(description: "searchForText was successful", expectedFulfillmentCount: concurrencyLimit) + let successExpectation = expectation(description: "searchForText was successful") + successExpectation.expectedFulfillmentCount = concurrencyLimit for _ in 1...concurrencyLimit { Task { do { let options = Geo.SearchForTextOptions(area: .near(coordinates)) let places = try await Amplify.Geo.search(for: searchText, options: options) XCTAssertFalse(places.isEmpty) - await successExpectation.fulfill() + successExpectation.fulfill() } catch { XCTFail("Failed with error: \(error)") } } } - await waitForExpectations([successExpectation], timeout: timeout) + await fulfillment(of: [successExpectation], timeout: timeout) } /// Test if concurrent execution of search(for: coordinates) is successful @@ -69,21 +70,22 @@ final class GeoStressTests: XCTestCase { /// - Place results are returned. /// func testMultipleSearchForCoordinates() async { - let successExpectation = asyncExpectation(description: "searchForCoordinates was successful", expectedFulfillmentCount: concurrencyLimit) + let successExpectation = expectation(description: "searchForCoordinates was successful") + successExpectation.expectedFulfillmentCount = concurrencyLimit for _ in 1...concurrencyLimit { Task { do { let places = try await Amplify.Geo.search(for: coordinates, options: nil) XCTAssertFalse(places.isEmpty) XCTAssertNotNil(places.first?.coordinates) - await successExpectation.fulfill() + successExpectation.fulfill() } catch { XCTFail("Failed with error: \(error)") } } } - await waitForExpectations([successExpectation], timeout: timeout) + await fulfillment(of: [successExpectation], timeout: timeout) } } diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/ClientError+IsRetryable.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/ClientError+IsRetryable.swift new file mode 100644 index 0000000000..77f1cb2c5e --- /dev/null +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/ClientError+IsRetryable.swift @@ -0,0 +1,29 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import ClientRuntime +import Foundation + +extension ClientError { + // TODO: Should some of these really be retried? + var isRetryable: Bool { + switch self { + case .authError: + return true + case .dataNotFound: + return true + case .pathCreationFailed: + return true + case .queryItemCreationFailed: + return true + case .serializationFailed: + return false + case .unknownError: + return true + } + } +} diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/EventRecorder.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/EventRecorder.swift index 39397cb0e5..21ee1cc88e 100644 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/EventRecorder.swift +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/EventRecorder.swift @@ -6,8 +6,10 @@ // import Amplify +import AWSCognitoAuthPlugin import AWSPinpoint import ClientRuntime +import enum AwsCommonRuntimeKit.CommonRunTimeError import Foundation /// AnalyticsEventRecording saves and submits pinpoint events @@ -185,47 +187,81 @@ class EventRecorder: AnalyticsEventRecording { } catch let analyticsError as AnalyticsError { // This is a known error explicitly thrown inside the do/catch block, so just rethrow it so it can be handled by the consumer throw analyticsError + } catch let authError as AuthError { + // This means all events were rejected due to an Auth error + log.error("Unable to submit \(pinpointEvents.count) events. Error: \(authError.errorDescription). \(authError.recoverySuggestion)") + switch authError { + case .signedOut, + .sessionExpired: + // Session Expired and Signed Out errors should be retried indefinitely, so we won't update the database + log.verbose("Events will be retried") + case .service: + if case .invalidAccountTypeException = authError.underlyingError as? AWSCognitoAuthError { + // Unsupported Guest Access errors should be retried indefinitely, so we won't update the database + log.verbose("Events will be retried") + } else { + fallthrough + } + default: + if let underlyingError = authError.underlyingError { + // Handle the underlying error accordingly + handleError(underlyingError, for: pinpointEvents) + } else { + // Otherwise just mark all events as dirty + log.verbose("Events will be discarded") + markEventsAsDirty(pinpointEvents) + } + } + + // Rethrow the original error so it can be handled by the consumer + throw authError } catch { // This means all events were rejected - if isConnectivityError(error) { - // Connectivity errors should be retried indefinitely, so we won't update the database - log.error("Unable to submit \(pinpointEvents.count) events. Error: \(AWSPinpointErrorConstants.deviceOffline.errorDescription)") - } else if isErrorRetryable(error) { - // For retryable errors, increment the events retry count - log.error("Unable to submit \(pinpointEvents.count) events. Error: \(errorDescription(error)).") - incrementRetryCounter(for: pinpointEvents) - } else { - // For remaining errors, mark events as dirty - log.error("Server rejected the submission of \(pinpointEvents.count) events. Error: \(errorDescription(error)).") - markEventsAsDirty(pinpointEvents) - } + log.error("Unable to submit \(pinpointEvents.count) events. Error: \(errorDescription(error)).") + handleError(error, for: pinpointEvents) // Rethrow the original error so it can be handled by the consumer throw error } } + + private func handleError(_ error: Error, for pinpointEvents: [PinpointEvent]) { + if isConnectivityError(error) { + // Connectivity errors should be retried indefinitely, so we won't update the database + log.verbose("Events will be retried") + return + } + + if isErrorRetryable(error) { + // For retryable errors, increment the events retry count + log.verbose("Events' retry count will be increased") + incrementRetryCounter(for: pinpointEvents) + } else { + // For remaining errors, mark events as dirty + log.verbose("Events will be discarded") + markEventsAsDirty(pinpointEvents) + } + } private func isErrorRetryable(_ error: Error) -> Bool { - switch error { - case let clientError as ClientError: - return clientError.isRetryable - case let putEventsOutputError as PutEventsOutputError: - return putEventsOutputError.isRetryable - case let sdkPutEventsOutputError as SdkError: - return sdkPutEventsOutputError.isRetryable - case let sdkError as SdkError: - return sdkError.isRetryable - default: + guard case let modeledError as ModeledError = error else { return false } + return type(of: modeledError).isRetryable } private func errorDescription(_ error: Error) -> String { + if isConnectivityError(error) { + return AWSPinpointErrorConstants.deviceOffline.errorDescription + } switch error { - case let sdkPutEventsOutputError as SdkError: - return sdkPutEventsOutputError.errorDescription - case let sdkError as SdkError: - return sdkError.errorDescription + case let error as ModeledErrorDescribable: + return error.errorDescription + case let error as CommonRunTimeError: + switch error { + case .crtError(let crtError): + return crtError.message + } default: return error.localizedDescription } @@ -233,15 +269,8 @@ class EventRecorder: AnalyticsEventRecording { private func isConnectivityError(_ error: Error) -> Bool { switch error { - case let clientError as ClientError: - if case .networkError(_) = clientError { - return true - } - return false - case let sdkPutEventsOutputError as SdkError: - return sdkPutEventsOutputError.isConnectivityError - case let sdkError as SdkError: - return sdkError.isConnectivityError + case let error as CommonRunTimeError: + return error.isConnectivityError case let error as NSError: let networkErrorCodes = [ NSURLErrorCannotFindHost, diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/SdkError+IsRetryable.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/SdkError+IsRetryable.swift deleted file mode 100644 index 5db7bde825..0000000000 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/SdkError+IsRetryable.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSPinpoint -import ClientRuntime -import Foundation - -@_spi(InternalAWSPinpoint) -public extension SdkError { - var isRetryable: Bool { - switch self { - case .service(let error, _): - return (error as? PutEventsOutputError)?.isRetryable == true - case .client(let error, _): - return error.isRetryable - default: - return true - } - } -} - -extension ClientError { - var isRetryable: Bool { - switch self { - case .authError: - return true - case .crtError: - return true - case .dataNotFound: - return true - case .deserializationFailed: - return false - case .networkError: - return true - case .pathCreationFailed: - return true - case .queryItemCreationFailed(_): - return true - case .retryError: - return true - case .serializationFailed: - return false - case .unknownError: - return true - } - } -} - -extension PutEventsOutputError { - var isRetryable: Bool { - switch self { - case .badRequestException(let exception): - return exception._retryable - case .forbiddenException(let exception): - return exception._retryable - case .internalServerErrorException(let exception): - return exception._retryable - case .methodNotAllowedException(let exception): - return exception._retryable - case .notFoundException(let exception): - return exception._retryable - case .payloadTooLargeException(let exception): - return exception._retryable - case .tooManyRequestsException(let exception): - return exception._retryable - case .unknown(let exception): - return exception._retryable - } - } -} diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Context/PinpointContext.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Context/PinpointContext.swift index b64742422f..70303f278e 100644 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Context/PinpointContext.swift +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Context/PinpointContext.swift @@ -76,14 +76,14 @@ struct PinpointContextConfiguration { /// The Pinpoint region let region: String /// Used to retrieve the proper AWSCredentials when creating the PinpointCLient - let credentialsProvider: CredentialsProvider + let credentialsProvider: CredentialsProviding /// Indicates if the App is in Debug or Release build. Defaults to `false` /// Setting this flag to true will set the Endpoint Profile to have a channel type of "APNS_SANDBOX". let isDebug: Bool init(appId: String, region: String, - credentialsProvider: CredentialsProvider, + credentialsProvider: CredentialsProviding, isDebug: Bool = false) { self.appId = appId self.region = region diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/CommonRunTimeError+isConnectivityError.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/CommonRunTimeError+isConnectivityError.swift new file mode 100644 index 0000000000..c3a3c3dd20 --- /dev/null +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/CommonRunTimeError+isConnectivityError.swift @@ -0,0 +1,43 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import AwsCIo +import AwsCHttp +import AwsCommonRuntimeKit +import AWSPinpoint +import ClientRuntime +import Foundation + +@_spi(InternalAWSPinpoint) +extension CommonRunTimeError { + static let connectivityErrorCodes: Set = [ + AWS_ERROR_HTTP_CONNECTION_CLOSED.rawValue, + AWS_ERROR_HTTP_SERVER_CLOSED.rawValue, + AWS_IO_DNS_INVALID_NAME.rawValue, + AWS_IO_DNS_NO_ADDRESS_FOR_HOST.rawValue, + AWS_IO_DNS_QUERY_FAILED.rawValue, + AWS_IO_SOCKET_CONNECT_ABORTED.rawValue, + AWS_IO_SOCKET_CONNECTION_REFUSED.rawValue, + AWS_IO_SOCKET_CLOSED.rawValue, + AWS_IO_SOCKET_NETWORK_DOWN.rawValue, + AWS_IO_SOCKET_NO_ROUTE_TO_HOST.rawValue, + AWS_IO_SOCKET_NOT_CONNECTED.rawValue, + AWS_IO_SOCKET_TIMEOUT.rawValue, + AWS_IO_TLS_NEGOTIATION_TIMEOUT.rawValue, + UInt32(AWS_HTTP_STATUS_CODE_408_REQUEST_TIMEOUT.rawValue) + ] + + public var isConnectivityError: Bool { + switch self { + case .crtError(let error): + return Self.connectivityErrorCodes.contains( + UInt32(error.code) + ) + } + } +} diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift index 447d092b61..e93baf21c2 100644 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift @@ -8,21 +8,17 @@ import AWSClientRuntime import AWSPluginsCore import AWSPinpoint -@_spi(FoundationClientEngine) import AWSPluginsCore +@_spi(PluginHTTPClientEngine) import AWSPluginsCore extension PinpointClient { - convenience init(region: String, credentialsProvider: CredentialsProvider) throws { + convenience init(region: String, credentialsProvider: CredentialsProviding) throws { + // TODO: FrameworkMetadata Replacement let configuration = try PinpointClientConfiguration( - credentialsProvider: credentialsProvider, - frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(), - region: region + region: region, + credentialsProvider: credentialsProvider ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - configuration.httpClientEngine = FoundationClientEngine() - #endif + + configuration.httpClientEngine = .userAgentEngine(for: configuration) PinpointRequestsRegistry.shared.setCustomHttpEngine(on: configuration) self.init(config: configuration) } diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/SdkError+Pinpoint.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/SdkError+Pinpoint.swift deleted file mode 100644 index a24a602cff..0000000000 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/SdkError+Pinpoint.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import AwsCIo -import AwsCHttp -import AwsCommonRuntimeKit -import AWSPinpoint -import ClientRuntime -import Foundation - -@_spi(InternalAWSPinpoint) -extension SdkError { - private var clientError: ClientError? { - guard case .client(let clientError, _) = self else { - return nil - } - - return clientError - } - - private var commonRunTimeError: CommonRunTimeError? { - if case .crtError(let commonRunTimeError) = clientError { - return commonRunTimeError - } - - if case .retryError(let commonRunTimeError as CommonRunTimeError) = clientError { - return commonRunTimeError - } - - return nil - } - - private var crtError: CRTError? { - if case .crtError(let crtError) = commonRunTimeError { - return crtError - } - - return nil - } - - private var putEventsOutputError: PutEventsOutputError? { - guard case .retryError(let sdkError as SdkError) = clientError, - case .service(let putEventsError as PutEventsOutputError, _) = sdkError else { - return nil - } - - return putEventsError - } - - public var errorDescription: String { - guard let putEventsOutputError = putEventsOutputError else { - return crtError?.message ?? localizedDescription - } - - switch putEventsOutputError { - case .badRequestException(let exception as ServiceError), - .forbiddenException(let exception as ServiceError), - .internalServerErrorException(let exception as ServiceError), - .methodNotAllowedException(let exception as ServiceError), - .notFoundException(let exception as ServiceError), - .payloadTooLargeException(let exception as ServiceError), - .tooManyRequestsException(let exception as ServiceError), - .unknown(let exception as ServiceError): - return exception._message ?? localizedDescription - } - } - - public var rootError: Error? { - if putEventsOutputError != nil { - return putEventsOutputError - } - - if commonRunTimeError != nil { - return commonRunTimeError - } - - guard let clientError = clientError else { - return nil - } - - switch clientError { - case .networkError(let error), - .deserializationFailed(let error), - .retryError(let error): - return error - default: - return nil - } - } - - public var isConnectivityError: Bool { - if case .networkError(_) = clientError { - return true - } - - guard let crtError = crtError else { - return false - } - - let connectivityErrorCodes: [UInt32] = [ - AWS_ERROR_HTTP_CONNECTION_CLOSED.rawValue, - AWS_ERROR_HTTP_SERVER_CLOSED.rawValue, - AWS_IO_DNS_INVALID_NAME.rawValue, - AWS_IO_DNS_NO_ADDRESS_FOR_HOST.rawValue, - AWS_IO_DNS_QUERY_FAILED.rawValue, - AWS_IO_SOCKET_CONNECT_ABORTED.rawValue, - AWS_IO_SOCKET_CONNECTION_REFUSED.rawValue, - AWS_IO_SOCKET_CLOSED.rawValue, - AWS_IO_SOCKET_NETWORK_DOWN.rawValue, - AWS_IO_SOCKET_NO_ROUTE_TO_HOST.rawValue, - AWS_IO_SOCKET_NOT_CONNECTED.rawValue, - AWS_IO_SOCKET_TIMEOUT.rawValue, - AWS_IO_TLS_NEGOTIATION_TIMEOUT.rawValue, - UInt32(AWS_HTTP_STATUS_CODE_408_REQUEST_TIMEOUT.rawValue) - ] - - return connectivityErrorCodes.contains(where: { $0 == crtError.code }) - } -} diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c69ba3b3a --- /dev/null +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/ModeledErrorDescribable.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/ModeledErrorDescribable.swift new file mode 100644 index 0000000000..60608de459 --- /dev/null +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/ModeledErrorDescribable.swift @@ -0,0 +1,42 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSPinpoint + +protocol ModeledErrorDescribable { + var errorDescription: String { get } +} + +extension AWSPinpoint.BadRequestException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.ForbiddenException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.InternalServerErrorException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.MethodNotAllowedException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.NotFoundException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.PayloadTooLargeException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} + +extension AWSPinpoint.TooManyRequestsException: ModeledErrorDescribable { + var errorDescription: String { properties.message ?? "" } +} diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift index 7bcd61a0a5..6c0b33dc99 100644 --- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift +++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift @@ -8,6 +8,8 @@ import Foundation import AWSPinpoint import ClientRuntime +@_spi(PluginHTTPClientEngine) +import AWSPluginsCore @globalActor actor PinpointRequestsRegistry { static let shared = PinpointRequestsRegistry() @@ -66,14 +68,11 @@ private struct CustomPinpointHttpClientEngine: HttpClientEngine { return try await httpClientEngine.execute(request: request) } - var headers = request.headers - let currentUserAgent = headers.value(for: userAgentHeader) ?? "" - headers.update(name: userAgentHeader, - value: "\(currentUserAgent)\(userAgentSuffix)") - request.headers = headers + let currentUserAgent = request.headers.value(for: userAgentHeader) ?? "" + let updatedRequest = request.updatingUserAgent(with: "\(currentUserAgent) \(userAgentSuffix)") await PinpointRequestsRegistry.shared.unregisterSources(for: pinpointApi) - return try await httpClientEngine.execute(request: request) + return try await httpClientEngine.execute(request: updatedRequest) } private func userAgent(for api: PinpointRequestsRegistry.API) async -> String? { diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EventRecorderTests.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EventRecorderTests.swift index 04452948b9..004860b037 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EventRecorderTests.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EventRecorderTests.swift @@ -7,7 +7,9 @@ import XCTest import AWSPinpoint +import AwsCommonRuntimeKit @testable import Amplify +import ClientRuntime @_spi(InternalAWSPinpoint) @testable import InternalAWSPinpoint class EventRecorderTests: XCTestCase { @@ -26,6 +28,13 @@ class EventRecorderTests: XCTestCase { XCTFail("Failed to setup EventRecorderTests") } } + + override func tearDown() { + pinpointClient = nil + endpointClient = nil + storage = nil + recorder = nil + } /// - Given: a event recorder /// - When: instance is constructed @@ -56,4 +65,129 @@ class EventRecorderTests: XCTestCase { XCTAssertEqual(event, storage.events[0]) XCTAssertEqual(storage.checkDiskSizeCallCount, 2) } + + /// - Given: a event recorder with events saved in the local storage + /// - When: submitAllEvents is invoked and successful + /// - Then: the events are removed from the local storage + func testSubmitAllEvents_withSuccess_shouldRemoveEventsFromStorage() async throws { + Amplify.Logging.logLevel = .verbose + let session = PinpointSession(sessionId: "1", startTime: Date(), stopTime: nil) + storage.events = [ + .init(id: "1", eventType: "eventType1", eventDate: Date(), session: session), + .init(id: "2", eventType: "eventType2", eventDate: Date(), session: session) + ] + + pinpointClient.putEventsResult = .success(.init(eventsResponse: .init(results: [ + "endpointId": PinpointClientTypes.ItemResponse( + endpointItemResponse: .init(message: "Accepted", statusCode: 202), + eventsItemResponse: [ + "1": .init(message: "Accepted", statusCode: 202), + "2": .init(message: "Accepted", statusCode: 202) + ] + ) + ]))) + let events = try await recorder.submitAllEvents() + + XCTAssertEqual(events.count, 2) + XCTAssertEqual(pinpointClient.putEventsCount, 1) + XCTAssertTrue(storage.events.isEmpty) + XCTAssertEqual(storage.deleteEventCallCount, 2) + } + + /// - Given: a event recorder with events saved in the local storage + /// - When: submitAllEvents is invoked and fails with a non-retryable error + /// - Then: the events are marked as dirty + func testSubmitAllEvents_withRetryableError_shouldSetEventsAsDirty() async throws { + Amplify.Logging.logLevel = .verbose + let session = PinpointSession(sessionId: "1", startTime: Date(), stopTime: nil) + let event1 = PinpointEvent(id: "1", eventType: "eventType1", eventDate: Date(), session: session) + let event2 = PinpointEvent(id: "2", eventType: "eventType2", eventDate: Date(), session: session) + storage.events = [ event1, event2 ] + pinpointClient.putEventsResult = .failure(NonRetryableError()) + do { + let events = try await recorder.submitAllEvents() + XCTFail("Expected error") + } catch { + XCTAssertEqual(pinpointClient.putEventsCount, 1) + XCTAssertEqual(storage.events.count, 2) + XCTAssertEqual(storage.deleteEventCallCount, 0) + XCTAssertEqual(storage.eventRetryDictionary.count, 0) + XCTAssertEqual(storage.dirtyEventDictionary.count, 2) + XCTAssertEqual(storage.dirtyEventDictionary["1"], 1) + XCTAssertEqual(storage.dirtyEventDictionary["2"], 1) + } + } + + /// - Given: a event recorder with events saved in the local storage + /// - When: submitAllEvents is invoked and fails with a retryable error + /// - Then: the events' retry count is increased + func testSubmitAllEvents_withRetryableError_shouldIncreaseRetryCount() async throws { + Amplify.Logging.logLevel = .verbose + let session = PinpointSession(sessionId: "1", startTime: Date(), stopTime: nil) + let event1 = PinpointEvent(id: "1", eventType: "eventType1", eventDate: Date(), session: session) + let event2 = PinpointEvent(id: "2", eventType: "eventType2", eventDate: Date(), session: session) + storage.events = [ event1, event2 ] + pinpointClient.putEventsResult = .failure(RetryableError()) + do { + let events = try await recorder.submitAllEvents() + XCTFail("Expected error") + } catch { + XCTAssertEqual(pinpointClient.putEventsCount, 1) + XCTAssertEqual(storage.events.count, 2) + XCTAssertEqual(storage.deleteEventCallCount, 0) + XCTAssertEqual(storage.eventRetryDictionary.count, 2) + XCTAssertEqual(storage.eventRetryDictionary["1"], 1) + XCTAssertEqual(storage.eventRetryDictionary["2"], 1) + XCTAssertEqual(storage.dirtyEventDictionary.count, 0) + } + } + + /// - Given: a event recorder with events saved in the local storage + /// - When: submitAllEvents is invoked and fails with a connectivity error + /// - Then: the events are not removed from the local storage + func testSubmitAllEvents_withConnectivityError_shouldNotIncreaseRetryCount_andNotSetEventsAsDirty() async throws { + Amplify.Logging.logLevel = .verbose + let session = PinpointSession(sessionId: "1", startTime: Date(), stopTime: nil) + let event1 = PinpointEvent(id: "1", eventType: "eventType1", eventDate: Date(), session: session) + let event2 = PinpointEvent(id: "2", eventType: "eventType2", eventDate: Date(), session: session) + storage.events = [ event1, event2 ] + pinpointClient.putEventsResult = .failure(ConnectivityError()) + do { + let events = try await recorder.submitAllEvents() + XCTFail("Expected error") + } catch { + XCTAssertEqual(pinpointClient.putEventsCount, 1) + XCTAssertEqual(storage.events.count, 2) + XCTAssertEqual(storage.deleteEventCallCount, 0) + XCTAssertEqual(storage.eventRetryDictionary.count, 0) + XCTAssertEqual(storage.dirtyEventDictionary.count, 0) + } + } +} + +private struct RetryableError: Error, ModeledError { + static var typeName = "RetriableError" + static var fault = ErrorFault.client + static var isRetryable = true + static var isThrottling = false +} + +private struct NonRetryableError: Error, ModeledError { + static var typeName = "RetriableError" + static var fault = ErrorFault.client + static var isRetryable = false + static var isThrottling = false +} + +private class ConnectivityError: NSError { + init() { + super.init( + domain: "ConnectivityError", + code: NSURLErrorNotConnectedToInternet + ) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } } diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsClient.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsClient.swift index 4a12397f71..72c48c74c5 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsClient.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsClient.swift @@ -73,10 +73,10 @@ actor MockAnalyticsClient: AnalyticsClientBehaviour { } - private var recordExpectation: AsyncExpectation? - func setRecordExpectation(_ expectation: AsyncExpectation, count: Int = 1) async { + private var recordExpectation: XCTestExpectation? + func setRecordExpectation(_ expectation: XCTestExpectation, count: Int = 1) { recordExpectation = expectation - await recordExpectation?.setExpectedFulfillmentCount(count) + recordExpectation?.expectedFulfillmentCount = count } var recordCount = 0 @@ -86,23 +86,19 @@ actor MockAnalyticsClient: AnalyticsClientBehaviour { recordCount += 1 lastRecordedEvent = event recordedEvents.append(event) - Task { - await recordExpectation?.fulfill() - } + recordExpectation?.fulfill() } - private var submitEventsExpectation: AsyncExpectation? - func setSubmitEventsExpectation(_ expectation: AsyncExpectation, count: Int = 1) async { + private var submitEventsExpectation: XCTestExpectation? + func setSubmitEventsExpectation(_ expectation: XCTestExpectation, count: Int = 1) { submitEventsExpectation = expectation - await submitEventsExpectation?.setExpectedFulfillmentCount(count) + submitEventsExpectation?.expectedFulfillmentCount = count } var submitEventsCount = 0 func submitEvents() async throws -> [PinpointEvent] { submitEventsCount += 1 - Task { - await submitEventsExpectation?.fulfill() - } + submitEventsExpectation?.fulfill() return [] } diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsEventStorage.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsEventStorage.swift index 3b65a2de58..97d0a99d72 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsEventStorage.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockAnalyticsEventStorage.swift @@ -9,6 +9,7 @@ class MockAnalyticsEventStorage: AnalyticsEventStorage { var deletedEvent: String = "" + var deleteEventCallCount = 0 var deleteDirtyEventCallCount = 0 var initializeStorageCallCount = 0 var deleteOldestEventCallCount = 0 @@ -22,6 +23,8 @@ class MockAnalyticsEventStorage: AnalyticsEventStorage { func deleteEvent(eventId: String) throws { deletedEvent = eventId + deleteEventCallCount += 1 + events.removeAll { $0.id == eventId } } func deleteDirtyEvents() throws { diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockEndpointClient.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockEndpointClient.swift index bac6c68b15..4af9110ff9 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockEndpointClient.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockEndpointClient.swift @@ -13,7 +13,7 @@ import Foundation actor MockEndpointClient: EndpointClientBehaviour { let pinpointClient: PinpointClientProtocol = MockPinpointClient() - class MockCredentialsProvider: CredentialsProvider { + class MockCredentialsProvider: CredentialsProviding { func getCredentials() async throws -> AWSCredentials { return AWSCredentials(accessKey: "", secret: "", expirationTimeout: Date().addingTimeInterval(1000)) } diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockPinpointClient.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockPinpointClient.swift index 605c4031b8..1069db5214 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockPinpointClient.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockPinpointClient.swift @@ -9,6 +9,18 @@ import AWSPinpoint import Foundation class MockPinpointClient: PinpointClientProtocol { + func getJourneyRunExecutionActivityMetrics(input: GetJourneyRunExecutionActivityMetricsInput) async throws -> GetJourneyRunExecutionActivityMetricsOutputResponse { + fatalError("Not supported") + } + + func getJourneyRunExecutionMetrics(input: GetJourneyRunExecutionMetricsInput) async throws -> GetJourneyRunExecutionMetricsOutputResponse { + fatalError("Not supported") + } + + func getJourneyRuns(input: GetJourneyRunsInput) async throws -> GetJourneyRunsOutputResponse { + fatalError("Not supported") + } + func createApp(input: CreateAppInput) async throws -> CreateAppOutputResponse { fatalError("Not supported") } @@ -353,8 +365,11 @@ class MockPinpointClient: PinpointClientProtocol { fatalError("Not supported") } + var putEventsCount = 0 + var putEventsResult: Result = .failure(CancellationError()) func putEvents(input: PutEventsInput) async throws -> PutEventsOutputResponse { - fatalError("Not supported") + putEventsCount += 1 + return try putEventsResult.get() } func putEventStream(input: PutEventStreamInput) async throws -> PutEventStreamOutputResponse { diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/PinpointRequestsRegistryTests.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/PinpointRequestsRegistryTests.swift index 9a7bd28978..d5ddb3974b 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/PinpointRequestsRegistryTests.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/PinpointRequestsRegistryTests.swift @@ -35,12 +35,12 @@ class PinpointRequestsRegistryTests: XCTestCase { await PinpointRequestsRegistry.shared.registerSource(.pushNotifications, for: .recordEvent) let sdkRequest = try createSdkRequest(for: .recordEvent) _ = try await httpClientEngine.execute(request: sdkRequest) + let executedRequest = mockedHttpSdkClient.request XCTAssertEqual(mockedHttpSdkClient.executeCount, 1) - guard let userAgent = sdkRequest.headers.value(for: "User-Agent") else { - XCTFail("Expected User-Agent") - return - } + let userAgent = try XCTUnwrap( + executedRequest?.headers.value(for: "User-Agent") + ) XCTAssertTrue(userAgent.contains(AWSPinpointSource.analytics.rawValue)) XCTAssertTrue(userAgent.contains(AWSPinpointSource.pushNotifications.rawValue)) } @@ -54,12 +54,12 @@ class PinpointRequestsRegistryTests: XCTestCase { let oldUserAgent = sdkRequest.headers.value(for: "User-Agent") _ = try await httpClientEngine.execute(request: sdkRequest) + let executedRequest = mockedHttpSdkClient.request XCTAssertEqual(mockedHttpSdkClient.executeCount, 1) - guard let newUserAgent = sdkRequest.headers.value(for: "User-Agent") else { - XCTFail("Expected User-Agent") - return - } + let newUserAgent = try XCTUnwrap( + executedRequest?.headers.value(for: "User-Agent") + ) XCTAssertEqual(newUserAgent, oldUserAgent) XCTAssertFalse(newUserAgent.contains(AWSPinpointSource.analytics.rawValue)) @@ -72,13 +72,14 @@ class PinpointRequestsRegistryTests: XCTestCase { private func createSdkRequest(for api: PinpointRequestsRegistry.API?) throws -> SdkHttpRequest { let apiPath = api?.rawValue ?? "otherApi" - let endpoint = try Endpoint(urlString: "https://host:42/path/\(apiPath)/suffix") - let headers = [ - "User-Agent": "mocked_user_agent" - ] - return SdkHttpRequest(method: .put, - endpoint: endpoint, - headers: .init(headers)) + let endpoint = try Endpoint( + urlString: "https://host:42/path/\(apiPath)/suffix", + headers: .init(["User-Agent": "mocked_user_agent"]) + ) + return SdkHttpRequest( + method: .put, + endpoint: endpoint + ) } } @@ -88,39 +89,13 @@ private extension HttpClientEngine { } } -private class MockSDKRuntimeConfiguration: SDKRuntimeConfiguration { - var encoder: ClientRuntime.RequestEncoder? - var decoder: ClientRuntime.ResponseDecoder? - var httpClientConfiguration: ClientRuntime.HttpClientConfiguration - var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator - var clientLogMode: ClientRuntime.ClientLogMode - var partitionID: String? - - let logger: LogAgent - let retryer: SDKRetryer - var endpoint: String? = nil - private let mockedHttpClientEngine : HttpClientEngine - - init(httpClientEngine: HttpClientEngine) throws { - let defaultSDKRuntimeConfig = try DefaultSDKRuntimeConfiguration("MockSDKRuntimeConfiguration") - httpClientConfiguration = defaultSDKRuntimeConfig.httpClientConfiguration - idempotencyTokenGenerator = defaultSDKRuntimeConfig.idempotencyTokenGenerator - clientLogMode = defaultSDKRuntimeConfig.clientLogMode - - logger = MockLogAgent() - retryer = try SDKRetryer(options: RetryOptions(jitterMode: .default)) - mockedHttpClientEngine = httpClientEngine - } - - var httpClientEngine: HttpClientEngine { - mockedHttpClientEngine - } -} - private class MockHttpClientEngine: HttpClientEngine { var executeCount = 0 + var request: SdkHttpRequest? + func execute(request: SdkHttpRequest) async throws -> HttpResponse { executeCount += 1 + self.request = request return .init(body: .none, statusCode: .accepted) } diff --git a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/SessionClientTests.swift b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/SessionClientTests.swift index 4a72a6dd2f..c48b4f76aa 100644 --- a/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/SessionClientTests.swift +++ b/AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/SessionClientTests.swift @@ -84,7 +84,7 @@ class SessionClientTests: XCTestCase { } func testCurrentSession_withoutStoredSession_shouldStartNewSession() async { - let expectationStartSession = AsyncExpectation(description: "Start event for new session") + let expectationStartSession = expectation(description: "Start event for new session") await analyticsClient.setRecordExpectation(expectationStartSession) let currentSession = client.currentSession XCTAssertFalse(currentSession.isPaused) @@ -93,7 +93,7 @@ class SessionClientTests: XCTestCase { XCTAssertEqual(activityTracker.beginActivityTrackingCount, 0) XCTAssertEqual(userDefaults.saveCount, 1) - await waitForExpectations([expectationStartSession], timeout: 1) + await fulfillment(of: [expectationStartSession], timeout: 1) let updateEndpointProfileCount = await endpointClient.updateEndpointProfileCount XCTAssertEqual(updateEndpointProfileCount, 1) let createEventCount = await analyticsClient.createEventCount @@ -157,10 +157,10 @@ class SessionClientTests: XCTestCase { func testStartPinpointSession_shouldRecordStartEvent() async { await resetCounters() - let expectationStartSession = AsyncExpectation(description: "Start event for new session") + let expectationStartSession = expectation(description: "Start event for new session") await analyticsClient.setRecordExpectation(expectationStartSession) client.startPinpointSession() - await waitForExpectations([expectationStartSession], timeout: 1) + await fulfillment(of: [expectationStartSession], timeout: 1) let updateEndpointProfileCount = await endpointClient.updateEndpointProfileCount XCTAssertEqual(updateEndpointProfileCount, 1) let createCount = await analyticsClient.createEventCount @@ -178,10 +178,10 @@ class SessionClientTests: XCTestCase { storeSession() createNewSessionClient() await resetCounters() - let expectationStopStart = AsyncExpectation(description: "Stop event for current session and Start event for a new one") + let expectationStopStart = expectation(description: "Stop event for current session and Start event for a new one") await analyticsClient.setRecordExpectation(expectationStopStart, count: 2) client.startPinpointSession() - await waitForExpectations([expectationStopStart], timeout: 1) + await fulfillment(of: [expectationStopStart], timeout: 1) let createCount = await analyticsClient.createEventCount XCTAssertEqual(createCount, 2) let recordCount = await analyticsClient.recordCount @@ -194,16 +194,16 @@ class SessionClientTests: XCTestCase { #if !os(macOS) func testApplicationMovedToBackground_notStale_shouldSaveSession_andRecordPauseEvent() async { - let expectationStartSession = AsyncExpectation(description: "Start event for new session") + let expectationStartSession = expectation(description: "Start event for new session") await analyticsClient.setRecordExpectation(expectationStartSession) client.startPinpointSession() client.startTrackingSessions(backgroundTimeout: sessionTimeout) - await waitForExpectations([expectationStartSession], timeout: 1) + await fulfillment(of: [expectationStartSession], timeout: 1) await resetCounters() - let expectationPauseSession = AsyncExpectation(description: "Pause event for current session") + let expectationPauseSession = expectation(description: "Pause event for current session") await analyticsClient.setRecordExpectation(expectationPauseSession) activityTracker.callback?(.runningInBackground(isStale: false)) - await waitForExpectations([expectationPauseSession], timeout: 1) + await fulfillment(of: [expectationPauseSession], timeout: 1) XCTAssertEqual(archiver.encodeCount, 1) XCTAssertEqual(userDefaults.saveCount, 1) @@ -219,19 +219,19 @@ class SessionClientTests: XCTestCase { } func testApplicationMovedToBackground_stale_shouldRecordStopEvent_andSubmit() async { - let expectationStartSession = AsyncExpectation(description: "Start event for new session") + let expectationStartSession = expectation(description: "Start event for new session") client.startPinpointSession() client.startTrackingSessions(backgroundTimeout: sessionTimeout) await analyticsClient.setRecordExpectation(expectationStartSession) - await waitForExpectations([expectationStartSession], timeout: 1) + await fulfillment(of: [expectationStartSession], timeout: 1) await resetCounters() - let expectationStopSession = AsyncExpectation(description: "Stop event for current session") + let expectationStopSession = expectation(description: "Stop event for current session") await analyticsClient.setRecordExpectation(expectationStopSession) - let expectationSubmitEvents = AsyncExpectation(description: "Submit events") + let expectationSubmitEvents = expectation(description: "Submit events") await analyticsClient.setSubmitEventsExpectation(expectationSubmitEvents) activityTracker.callback?(.runningInBackground(isStale: true)) - await waitForExpectations([expectationStopSession, expectationSubmitEvents], timeout: 1) + await fulfillment(of: [expectationStopSession, expectationSubmitEvents], timeout: 1) XCTAssertEqual(archiver.encodeCount, 0) XCTAssertEqual(userDefaults.saveCount, 0) @@ -249,10 +249,10 @@ class SessionClientTests: XCTestCase { } func testApplicationMovedToForeground_withNonPausedSession_shouldDoNothing() async { - let expectationStartSession = AsyncExpectation(description: "Start event for new session") + let expectationStartSession = expectation(description: "Start event for new session") await analyticsClient.setRecordExpectation(expectationStartSession) client.startPinpointSession() - await waitForExpectations([expectationStartSession], timeout: 1) + await fulfillment(of: [expectationStartSession], timeout: 1) await resetCounters() activityTracker.callback?(.runningInForeground) @@ -267,7 +267,7 @@ class SessionClientTests: XCTestCase { } func testApplicationMovedToForeground_withNonExpiredSession_shouldRecordResumeEvent() async { - let expectationStartandPause = AsyncExpectation(description: "Start and Pause event for new session") + let expectationStartandPause = expectation(description: "Start and Pause event for new session") await analyticsClient.setRecordExpectation(expectationStartandPause, count: 2) sessionTimeout = 1000 createNewSessionClient() @@ -276,13 +276,13 @@ class SessionClientTests: XCTestCase { // First pause the session activityTracker.callback?(.runningInBackground(isStale: false)) - await waitForExpectations([expectationStartandPause], timeout: 1) + await fulfillment(of: [expectationStartandPause], timeout: 1) await resetCounters() - let expectationResume = AsyncExpectation(description: "Resume event for non-expired session") + let expectationResume = expectation(description: "Resume event for non-expired session") await analyticsClient.setRecordExpectation(expectationResume) activityTracker.callback?(.runningInForeground) - await waitForExpectations([expectationResume], timeout: 1) + await fulfillment(of: [expectationResume], timeout: 1) XCTAssertEqual(archiver.encodeCount, 1) XCTAssertEqual(userDefaults.saveCount, 1) @@ -298,7 +298,7 @@ class SessionClientTests: XCTestCase { } func testApplicationMovedToForeground_withExpiredSession_shouldStartNewSession() async { - let expectationStartandPause = AsyncExpectation(description: "Start and Pause event for new session") + let expectationStartandPause = expectation(description: "Start and Pause event for new session") await analyticsClient.setRecordExpectation(expectationStartandPause, count: 2) sessionTimeout = 0 createNewSessionClient() @@ -307,13 +307,13 @@ class SessionClientTests: XCTestCase { // First pause the session activityTracker.callback?(.runningInBackground(isStale: false)) - await waitForExpectations([expectationStartandPause], timeout: 1) + await fulfillment(of: [expectationStartandPause], timeout: 1) await resetCounters() - let expectationStopAndStart = AsyncExpectation(description: "Stop event for expired session and Start event for a new one") + let expectationStopAndStart = expectation(description: "Stop event for expired session and Start event for a new one") await analyticsClient.setRecordExpectation(expectationStopAndStart, count: 2) activityTracker.callback?(.runningInForeground) - await waitForExpectations([expectationStopAndStart], timeout: 1) + await fulfillment(of: [expectationStopAndStart], timeout: 1) XCTAssertEqual(archiver.encodeCount, 1) XCTAssertEqual(userDefaults.saveCount, 1) @@ -328,17 +328,17 @@ class SessionClientTests: XCTestCase { } #endif func testApplicationTerminated_shouldRecordStopEvent() async { - let expectationStart = AsyncExpectation(description: "Start event for new session") + let expectationStart = expectation(description: "Start event for new session") await analyticsClient.setRecordExpectation(expectationStart) client.startPinpointSession() client.startTrackingSessions(backgroundTimeout: sessionTimeout) - await waitForExpectations([expectationStart], timeout: 1) + await fulfillment(of: [expectationStart], timeout: 1) await resetCounters() - let expectationStop = AsyncExpectation(description: "Stop event for current session") + let expectationStop = expectation(description: "Stop event for current session") await analyticsClient.setRecordExpectation(expectationStop) activityTracker.callback?(.terminated) - await waitForExpectations([expectationStop], timeout: 1) + await fulfillment(of: [expectationStop], timeout: 1) XCTAssertEqual(archiver.encodeCount, 0) XCTAssertEqual(userDefaults.saveCount, 0) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift index 9416ecae34..4503f4eaf1 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift @@ -26,7 +26,7 @@ final class AWSCloudWatchLoggingCategoryClient { private let lock = NSLock() private let logGroupName: String private let region: String - private let credentialsProvider: CredentialsProvider + private let credentialsProvider: CredentialsProviding private let authentication: AuthCategoryUserBehavior private var loggersByKey: [LoggerKey: AWSCloudWatchLoggingSessionController] = [:] private let localStoreMaxSizeInMB: Int @@ -38,7 +38,7 @@ final class AWSCloudWatchLoggingCategoryClient { init( enable: Bool, - credentialsProvider: CredentialsProvider, + credentialsProvider: CredentialsProviding, authentication: AuthCategoryUserBehavior, loggingConstraintsResolver: AWSCloudWatchLoggingConstraintsResolver, logGroupName: String, diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift index b12e9c134e..7cb6c4a74e 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift @@ -6,7 +6,7 @@ // import AWSPluginsCore -@_spi(FoundationClientEngine) import AWSPluginsCore +@_spi(PluginHTTPClientEngine) import AWSPluginsCore import Amplify import Combine import Foundation @@ -25,7 +25,7 @@ final class AWSCloudWatchLoggingSessionController { private let logGroupName: String private let region: String private let localStoreMaxSizeInMB: Int - private let credentialsProvider: CredentialsProvider + private let credentialsProvider: CredentialsProviding private let authentication: AuthCategoryUserBehavior private let category: String private var session: AWSCloudWatchLoggingSession? @@ -59,7 +59,7 @@ final class AWSCloudWatchLoggingSessionController { } /// - Tag: CloudWatchLogSessionController.init - init(credentialsProvider: CredentialsProvider, + init(credentialsProvider: CredentialsProviding, authentication: AuthCategoryUserBehavior, logFilter: AWSCloudWatchLoggingFilterBehavior, category: String, @@ -103,18 +103,13 @@ final class AWSCloudWatchLoggingSessionController { private func createConsumer() throws -> LogBatchConsumer? { if self.client == nil { + // TODO: FrameworkMetadata Replacement let configuration = try CloudWatchLogsClient.CloudWatchLogsClientConfiguration( - credentialsProvider: credentialsProvider, - frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(), - region: region + region: region, + credentialsProvider: credentialsProvider ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - configuration.httpClientEngine = FoundationClientEngine() - #endif + configuration.httpClientEngine = .userAgentEngine(for: configuration) self.client = CloudWatchLogsClient(config: configuration) } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift index 540bc7e3f9..5f8b29c344 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift @@ -14,7 +14,7 @@ import ClientRuntime public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsProvider { public let refreshIntervalInSeconds: Int private let endpoint: URL - private let credentialProvider: CredentialsProvider? + private let credentialProvider: CredentialsProviding? private let region: String private let loggingConstraintsLocalStore: LoggingConstraintsLocalStore = UserDefaults.standard @@ -31,7 +31,7 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr public init( endpoint: URL, region: String, - credentialProvider: CredentialsProvider? = nil, + credentialProvider: CredentialsProviding? = nil, refreshIntervalInSeconds: Int = 1200 ) { self.endpoint = endpoint @@ -79,7 +79,9 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr let httpMethod = (request.httpMethod?.uppercased()) .flatMap(HttpMethodType.init(rawValue:)) ?? .get - let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? [] + let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems? + .map { ClientRuntime.URLQueryItem(name: $0.name, value: $0.value) } ?? [] let requestBuilder = SdkHttpRequestBuilder() .withHost(host) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c69ba3b3a --- /dev/null +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/AWSCloudWatchLoggingPluginIntegrationTests/AWSCloudWatchLoggingPluginIntegrationTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/AWSCloudWatchLoggingPluginIntegrationTests/AWSCloudWatchLoggingPluginIntegrationTests.swift index 7befffdcc9..98668ce038 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/AWSCloudWatchLoggingPluginIntegrationTests/AWSCloudWatchLoggingPluginIntegrationTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/AWSCloudWatchLoggingPluginIntegrationTests/AWSCloudWatchLoggingPluginIntegrationTests.swift @@ -13,7 +13,13 @@ import AWSCloudWatchLogs class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { let amplifyConfigurationFile = "testconfiguration/AWSCloudWatchLoggingPluginIntegrationTests-amplifyconfiguration" + #if os(tvOS) + let amplifyConfigurationLoggingFile = "testconfiguration/AWSCloudWatchLoggingPluginIntegrationTests-amplifyconfiguration_logging_tvOS" + #elseif os(watchOS) + let amplifyConfigurationLoggingFile = "testconfiguration/AWSCloudWatchLoggingPluginIntegrationTests-amplifyconfiguration_logging_watchOS" + #else let amplifyConfigurationLoggingFile = "testconfiguration/AWSCloudWatchLoggingPluginIntegrationTests-amplifyconfiguration_logging" + #endif var loggingConfiguration: AWSCloudWatchLoggingPluginConfiguration? override func setUp() async throws { @@ -36,6 +42,10 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { override func tearDown() async throws { await Amplify.reset() + let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.path ?? NSTemporaryDirectory() + let directory = documents.appendingPathComponent("amplify") + .appendingPathComponent("logging") + try FileManager.default.removeItem(atPath: directory) } /// - Given: a AWS CloudWatch Logging plugin @@ -52,91 +62,16 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { } /// - Given: a AWS CloudWatch Logging plugin - /// - When: an error log message is logged and flushed - /// - Then: the error log message is logged and sent to AWS CloudWatch - func testFlushLogWithErrorMessage() async throws { + /// - When: log messages is logged and flushed + /// - Then: the log messages are logged and sent to AWS CloudWatch + func testFlushLogWithMessages() async throws { let category = "Analytics" let namespace = UUID().uuidString let message = "this is an error message in the integration test \(Date().epochMilliseconds)" let logger = Amplify.Logging.logger(forCategory: category, forNamespace: namespace) logger.error(message) - let plugin = try Amplify.Logging.getPlugin(for: "awsCloudWatchLoggingPlugin") - guard let loggingPlugin = plugin as? AWSCloudWatchLoggingPlugin else { - XCTFail("Could not get plugin of type AWSCloudWatchLoggingPlugin") - return - } - try await loggingPlugin.flushLogs() - try await Task.sleep(seconds: 30) - let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageSent(client: cloudWatchClient, - logGroupName: loggingConfiguration?.logGroupName, - logLevel: "error", - message: message, - category: category, - namespace: namespace) - } - - /// - Given: a AWS CloudWatch Logging plugin - /// - When: an warn log message is logged and flushed - /// - Then: the warn log message is logged and sent to AWS CloudWatch - func testFlushLogWithWarnMessage() async throws { - let category = "API" - let namespace = UUID().uuidString - let message = "this is an warn message in the integration test \(Date().epochMilliseconds)" - let logger = Amplify.Logging.logger(forCategory: category, forNamespace: namespace) - logger.warn(message) - let plugin = try Amplify.Logging.getPlugin(for: "awsCloudWatchLoggingPlugin") - guard let loggingPlugin = plugin as? AWSCloudWatchLoggingPlugin else { - XCTFail("Could not get plugin of type AWSCloudWatchLoggingPlugin") - return - } - try await loggingPlugin.flushLogs() - try await Task.sleep(seconds: 30) - let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageSent(client: cloudWatchClient, - logGroupName: loggingConfiguration?.logGroupName, - logLevel: "warn", - message: message, - category: category, - namespace: namespace) - } - - /// - Given: a AWS CloudWatch Logging plugin - /// - When: an debug log message is logged and flushed - /// - Then: the debug log message is logged and sent to AWS CloudWatch - func testFlushLogWithDebugMessage() async throws { - let category = "Geo" - let namespace = UUID().uuidString - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .long - let message = "this is an debug message in the integration test \(Date().epochMilliseconds)" - let logger = Amplify.Logging.logger(forCategory: category, forNamespace: namespace) logger.debug(message) - let plugin = try Amplify.Logging.getPlugin(for: "awsCloudWatchLoggingPlugin") - guard let loggingPlugin = plugin as? AWSCloudWatchLoggingPlugin else { - XCTFail("Could not get plugin of type AWSCloudWatchLoggingPlugin") - return - } - try await loggingPlugin.flushLogs() - try await Task.sleep(seconds: 30) - let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageSent(client: cloudWatchClient, - logGroupName: loggingConfiguration?.logGroupName, - logLevel: "debug", - message: message, - category: category, - namespace: namespace) - } - - /// - Given: a AWS CloudWatch Logging plugin - /// - When: an info log message is logged and flushed - /// - Then: the info log message is logged and sent to AWS CloudWatch - func testFlushLogWithInfoMessage() async throws { - let category = "Auth" - let namespace = UUID().uuidString - let message = "this is an info message in the integration test \(Date().epochMilliseconds)" - let logger = Amplify.Logging.logger(forCategory: category, forNamespace: namespace) + logger.warn(message) logger.info(message) let plugin = try Amplify.Logging.getPlugin(for: "awsCloudWatchLoggingPlugin") guard let loggingPlugin = plugin as? AWSCloudWatchLoggingPlugin else { @@ -146,13 +81,15 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { try await loggingPlugin.flushLogs() try await Task.sleep(seconds: 30) let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageSent(client: cloudWatchClient, - logGroupName: loggingConfiguration?.logGroupName, - logLevel: "info", - message: message, - category: category, - namespace: namespace) + try await verifyMessagesSent(plugin: loggingPlugin, + client: cloudWatchClient, + logGroupName: loggingConfiguration?.logGroupName, + messageCount: 4, + message: message, + category: category, + namespace: namespace) } + /// - Given: a AWS CloudWatch Logging plugin with logging enabled /// - When: an error log message is logged and flushed @@ -172,7 +109,8 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { try await loggingPlugin.flushLogs() try await Task.sleep(seconds: 30) let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageSent(client: cloudWatchClient, + try await verifyMessageSent(plugin: loggingPlugin, + client: cloudWatchClient, logGroupName: loggingConfiguration?.logGroupName, logLevel: "verbose", message: message, @@ -198,19 +136,52 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { try await loggingPlugin.flushLogs() try await Task.sleep(seconds: 30) let cloudWatchClient = loggingPlugin.getEscapeHatch() - try await verifyMessageNotSent(client: cloudWatchClient, + try await verifyMessageNotSent(plugin: loggingPlugin, + client: cloudWatchClient, logGroupName: loggingConfiguration?.logGroupName, message: message) } - func verifyMessageSent(client: CloudWatchLogsClientProtocol?, + func verifyMessagesSent(plugin: AWSCloudWatchLoggingPlugin, + client: CloudWatchLogsClientProtocol?, + logGroupName: String?, + messageCount: Int, + message: String, + category: String, + namespace: String) async throws { + + let events = try await getLastMessageSent( + plugin: plugin, + client: client, + logGroupName: logGroupName, + expectedMessageCount: messageCount, + message: message, + requestAttempt: 0) + XCTAssertEqual(events?.count, messageCount) + guard let sentLogMessage = events?.first?.message else { + XCTFail("Unable to verify last log message") + return + } + XCTAssertTrue(sentLogMessage.contains(message)) + XCTAssertTrue(sentLogMessage.contains(category)) + XCTAssertTrue(sentLogMessage.contains(namespace)) + } + + func verifyMessageSent(plugin: AWSCloudWatchLoggingPlugin, + client: CloudWatchLogsClientProtocol?, logGroupName: String?, logLevel: String, message: String, category: String, namespace: String) async throws { - let events = try await getLastMessageSent(client: client, logGroupName: logGroupName, message: message, requestAttempt: 0) + let events = try await getLastMessageSent( + plugin: plugin, + client: client, + logGroupName: logGroupName, + expectedMessageCount: 1, + message: message, + requestAttempt: 0) XCTAssertEqual(events?.count, 1) guard let sentLogMessage = events?.first?.message else { XCTFail("Unable to verify last log message") @@ -222,16 +193,25 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { XCTAssertTrue(sentLogMessage.contains(namespace)) } - func verifyMessageNotSent(client: CloudWatchLogsClientProtocol?, + func verifyMessageNotSent(plugin: AWSCloudWatchLoggingPlugin, + client: CloudWatchLogsClientProtocol?, logGroupName: String?, message: String) async throws { - let events = try await getLastMessageSent(client: client, logGroupName: logGroupName, message: message, requestAttempt: 0) + let events = try await getLastMessageSent( + plugin: plugin, + client: client, + logGroupName: logGroupName, + expectedMessageCount: 1, + message: message, + requestAttempt: 0) XCTAssertEqual(events?.count, 0) } - func getLastMessageSent(client: CloudWatchLogsClientProtocol?, + func getLastMessageSent(plugin: AWSCloudWatchLoggingPlugin, + client: CloudWatchLogsClientProtocol?, logGroupName: String?, + expectedMessageCount: Int, message: String, requestAttempt: Int) async throws -> [CloudWatchLogsClientTypes.FilteredLogEvent]? { let endTime = Date() @@ -239,12 +219,15 @@ class AWSCloudWatchLoggingPluginIntergrationTests: XCTestCase { let startTime = endTime.addingTimeInterval(TimeInterval(-durationInMinutes*60)) var events = try await AWSCloudWatchClientHelper.getFilterLogEventCount(client: client, filterPattern: message, startTime: startTime, endTime: endTime, logGroupName: logGroupName) - if events?.count == 0 && requestAttempt <= 5 { + if events?.count != expectedMessageCount && requestAttempt <= 5 { + try await plugin.flushLogs() try await Task.sleep(seconds: 30) let attempted = requestAttempt + 1 events = try await getLastMessageSent( + plugin: plugin, client: client, logGroupName: logGroupName, + expectedMessageCount: expectedMessageCount, message: message, requestAttempt: attempted) } diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/project.pbxproj index 5e3d1d941f..066f1c118a 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/project.pbxproj @@ -8,9 +8,6 @@ /* Begin PBXBuildFile section */ 730C2E772AAA8A4B00878E67 /* AWSCloudWatchClientHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 730C2E762AAA8A4B00878E67 /* AWSCloudWatchClientHelper.swift */; }; - 733390E92AAB8A6A006E3625 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 733390E82AAB8A6A006E3625 /* Amplify */; }; - 733390EB2AAB8A6E006E3625 /* AWSCloudWatchLoggingPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 733390EA2AAB8A6E006E3625 /* AWSCloudWatchLoggingPlugin */; }; - 733390ED2AAB8A74006E3625 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 733390EC2AAB8A74006E3625 /* AWSCognitoAuthPlugin */; }; 73578A2C2AAB945E00505FB3 /* CloudWatchLoggingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AD223128230B98001AFCC1 /* CloudWatchLoggingApp.swift */; }; 73578A2D2AAB946300505FB3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AD223328230B98001AFCC1 /* ContentView.swift */; }; 73578A3B2AAB94ED00505FB3 /* AWSCloudWatchLoggingPluginIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB824628233A1D00FC2228 /* AWSCloudWatchLoggingPluginIntegrationTests.swift */; }; @@ -19,9 +16,9 @@ 73607C792AA93466005105E6 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 73607C782AA93466005105E6 /* Amplify */; }; 73607C7B2AA93469005105E6 /* AWSCloudWatchLoggingPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73607C7A2AA93469005105E6 /* AWSCloudWatchLoggingPlugin */; }; 73607C7D2AA9346D005105E6 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73607C7C2AA9346D005105E6 /* AWSCognitoAuthPlugin */; }; - 73C43A2E2AB4ED800010F1B3 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 73C43A2D2AB4ED800010F1B3 /* Amplify */; }; - 73C43A302AB4ED850010F1B3 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73C43A2F2AB4ED850010F1B3 /* AWSCognitoAuthPlugin */; }; - 73C43A322AB4ED8A0010F1B3 /* AWSCloudWatchLoggingPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73C43A312AB4ED8A0010F1B3 /* AWSCloudWatchLoggingPlugin */; }; + 73C756A82AD8EB1C0042F7D4 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 73C756A72AD8EB1C0042F7D4 /* Amplify */; }; + 73C756AA2AD8EB200042F7D4 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73C756A92AD8EB200042F7D4 /* AWSCognitoAuthPlugin */; }; + 73C756AC2AD8EB240042F7D4 /* AWSCloudWatchLoggingPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 73C756AB2AD8EB240042F7D4 /* AWSCloudWatchLoggingPlugin */; }; 97AD223228230B98001AFCC1 /* CloudWatchLoggingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AD223128230B98001AFCC1 /* CloudWatchLoggingApp.swift */; }; 97AD223428230B98001AFCC1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AD223328230B98001AFCC1 /* ContentView.swift */; }; 97AD223628230B9A001AFCC1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97AD223528230B9A001AFCC1 /* Assets.xcassets */; }; @@ -69,9 +66,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 733390E92AAB8A6A006E3625 /* Amplify in Frameworks */, - 733390EB2AAB8A6E006E3625 /* AWSCloudWatchLoggingPlugin in Frameworks */, - 733390ED2AAB8A74006E3625 /* AWSCognitoAuthPlugin in Frameworks */, + 73C756A82AD8EB1C0042F7D4 /* Amplify in Frameworks */, + 73C756AA2AD8EB200042F7D4 /* AWSCognitoAuthPlugin in Frameworks */, + 73C756AC2AD8EB240042F7D4 /* AWSCloudWatchLoggingPlugin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -79,9 +76,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 73C43A2E2AB4ED800010F1B3 /* Amplify in Frameworks */, - 73C43A302AB4ED850010F1B3 /* AWSCognitoAuthPlugin in Frameworks */, - 73C43A322AB4ED8A0010F1B3 /* AWSCloudWatchLoggingPlugin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -189,9 +183,9 @@ ); name = CloudWatchLoggingWatchApp; packageProductDependencies = ( - 733390E82AAB8A6A006E3625 /* Amplify */, - 733390EA2AAB8A6E006E3625 /* AWSCloudWatchLoggingPlugin */, - 733390EC2AAB8A74006E3625 /* AWSCognitoAuthPlugin */, + 73C756A72AD8EB1C0042F7D4 /* Amplify */, + 73C756A92AD8EB200042F7D4 /* AWSCognitoAuthPlugin */, + 73C756AB2AD8EB240042F7D4 /* AWSCloudWatchLoggingPlugin */, ); productName = "CloudWatchLoggingWatchApp Watch App"; productReference = 733390D32AAB8A3B006E3625 /* CloudWatchLoggingWatchApp.app */; @@ -213,9 +207,6 @@ ); name = AWSCloudWatchLoggingPluginIntegrationTestsWatch; packageProductDependencies = ( - 73C43A2D2AB4ED800010F1B3 /* Amplify */, - 73C43A2F2AB4ED850010F1B3 /* AWSCognitoAuthPlugin */, - 73C43A312AB4ED8A0010F1B3 /* AWSCloudWatchLoggingPlugin */, ); productName = AWSCloudWatchLoggingPluginIntegrationTestsWatch; productReference = 73578A322AAB94D100505FB3 /* AWSCloudWatchLoggingPluginIntegrationTestsWatch.xctest */; @@ -466,7 +457,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; @@ -498,7 +489,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; @@ -520,7 +511,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CloudWatchLoggingWatchApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CloudWatchLoggingWatchApp"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; @@ -542,7 +533,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CloudWatchLoggingWatchApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CloudWatchLoggingWatchApp"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; @@ -846,18 +837,6 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 733390E82AAB8A6A006E3625 /* Amplify */ = { - isa = XCSwiftPackageProductDependency; - productName = Amplify; - }; - 733390EA2AAB8A6E006E3625 /* AWSCloudWatchLoggingPlugin */ = { - isa = XCSwiftPackageProductDependency; - productName = AWSCloudWatchLoggingPlugin; - }; - 733390EC2AAB8A74006E3625 /* AWSCognitoAuthPlugin */ = { - isa = XCSwiftPackageProductDependency; - productName = AWSCognitoAuthPlugin; - }; 73607C782AA93466005105E6 /* Amplify */ = { isa = XCSwiftPackageProductDependency; productName = Amplify; @@ -870,15 +849,15 @@ isa = XCSwiftPackageProductDependency; productName = AWSCognitoAuthPlugin; }; - 73C43A2D2AB4ED800010F1B3 /* Amplify */ = { + 73C756A72AD8EB1C0042F7D4 /* Amplify */ = { isa = XCSwiftPackageProductDependency; productName = Amplify; }; - 73C43A2F2AB4ED850010F1B3 /* AWSCognitoAuthPlugin */ = { + 73C756A92AD8EB200042F7D4 /* AWSCognitoAuthPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSCognitoAuthPlugin; }; - 73C43A312AB4ED8A0010F1B3 /* AWSCloudWatchLoggingPlugin */ = { + 73C756AB2AD8EB240042F7D4 /* AWSCloudWatchLoggingPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSCloudWatchLoggingPlugin; }; diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTests.xcscheme b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTests.xcscheme index dd5e2ff1c9..90eed8a12a 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTests.xcscheme +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTests.xcscheme @@ -28,6 +28,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTestsWatch.xcscheme b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTestsWatch.xcscheme index fba1f5ca96..19b32774a3 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTestsWatch.xcscheme +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/AWSCloudWatchLoggingPluginIntegrationTestsWatch.xcscheme @@ -30,6 +30,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/CloudWatchLoggingHostApp.xcscheme b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/CloudWatchLoggingHostApp.xcscheme index adc08ae428..4d37192fe1 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/CloudWatchLoggingHostApp.xcscheme +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginHostApp/CloudWatchLoggingHostApp.xcodeproj/xcshareddata/xcschemes/CloudWatchLoggingHostApp.xcscheme @@ -44,6 +44,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/MockCloudWatchLogsClient.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/MockCloudWatchLogsClient.swift index e71877c8b6..1d290c6c00 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/MockCloudWatchLogsClient.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/MockCloudWatchLogsClient.swift @@ -224,5 +224,15 @@ class MockCloudWatchLogsClient: CloudWatchLogsClientProtocol { throw MockError.unimplemented } - + func deleteAccountPolicy(input: AWSCloudWatchLogs.DeleteAccountPolicyInput) async throws -> AWSCloudWatchLogs.DeleteAccountPolicyOutputResponse { + throw MockError.unimplemented + } + + func describeAccountPolicies(input: AWSCloudWatchLogs.DescribeAccountPoliciesInput) async throws -> AWSCloudWatchLogs.DescribeAccountPoliciesOutputResponse { + throw MockError.unimplemented + } + + func putAccountPolicy(input: AWSCloudWatchLogs.PutAccountPolicyInput) async throws -> AWSCloudWatchLogs.PutAccountPolicyOutputResponse { + throw MockError.unimplemented + } } diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift new file mode 100644 index 0000000000..0c07856389 --- /dev/null +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift @@ -0,0 +1,28 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AwsCommonRuntimeKit +@_spi(InternalAWSPinpoint) import InternalAWSPinpoint + +extension CommonRunTimeError: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + if isConnectivityError { + return .network( + PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, + PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, + self + ) + } + + switch self { + case .crtError(let crtError): + return .unknown(crtError.message, self) + } + } +} diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/Pinpoint+PushNotificationsErrorConvertible.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/Pinpoint+PushNotificationsErrorConvertible.swift new file mode 100644 index 0000000000..9c3aaf2749 --- /dev/null +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/Pinpoint+PushNotificationsErrorConvertible.swift @@ -0,0 +1,97 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSPinpoint +import ClientRuntime +import AWSClientRuntime + +private func recoverySuggestion(for error: ClientRuntime.ModeledError) -> String { + type(of: error).isRetryable + ? PushNotificationsPluginErrorConstants.retryableServiceError.recoverySuggestion + : PushNotificationsPluginErrorConstants.nonRetryableServiceError.recoverySuggestion +} + +extension AWSPinpoint.BadRequestException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.ForbiddenException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.InternalServerErrorException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.MethodNotAllowedException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.NotFoundException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.PayloadTooLargeException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSPinpoint.TooManyRequestsException: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .service( + properties.message ?? "", + recoverySuggestion(for: self), + self + ) + } +} + +extension AWSClientRuntime.UnknownAWSHTTPServiceError: PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { + .unknown( + message ?? "An unknown error has occurred.", + self + ) + } +} diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/PushNotificationsErrorConvertible.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/PushNotificationsErrorConvertible.swift new file mode 100644 index 0000000000..3043828e1b --- /dev/null +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/PushNotificationsErrorConvertible.swift @@ -0,0 +1,13 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +protocol PushNotificationsErrorConvertible { + var pushNotificationsError: PushNotificationsError { get } +} diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Extensions/Error+PushNotifications.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Extensions/Error+PushNotifications.swift index 9694ece00a..f18f092489 100644 --- a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Extensions/Error+PushNotifications.swift +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Extensions/Error+PushNotifications.swift @@ -13,60 +13,27 @@ import Foundation extension Error { var pushNotificationsError: PushNotificationsError { - if let sdkError = self as? SdkError { - return sdkError.pushNotificationsError + switch self { + case let error as PushNotificationsErrorConvertible: + return error.pushNotificationsError + default: + let networkErrorCodes = [ + NSURLErrorCannotFindHost, + NSURLErrorCannotConnectToHost, + NSURLErrorNetworkConnectionLost, + NSURLErrorDNSLookupFailed, + NSURLErrorNotConnectedToInternet + ] + + if networkErrorCodes.contains(where: { $0 == (self as NSError).code }) { + return .network( + PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, + PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, + self + ) + } + + return PushNotificationsError(error: self) } - - if let sdkError = self as? SdkError { - return sdkError.pushNotificationsError - } - - if let clientError = self as? ClientError, - case .networkError(_) = clientError { - return .network( - PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, - PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, - clientError - ) - } - - let networkErrorCodes = [ - NSURLErrorCannotFindHost, - NSURLErrorCannotConnectToHost, - NSURLErrorNetworkConnectionLost, - NSURLErrorDNSLookupFailed, - NSURLErrorNotConnectedToInternet - ] - if networkErrorCodes.contains(where: { $0 == (self as NSError).code }) { - return .network( - PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, - PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, - self - ) - } - - return PushNotificationsError(error: self) - } -} - -extension SdkError { - var pushNotificationsError: PushNotificationsError { - if isConnectivityError { - return .network( - PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, - PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, - rootError ?? self - ) - } - - let recoverySuggestion = isRetryable ? - PushNotificationsPluginErrorConstants.retryableServiceError.recoverySuggestion : - PushNotificationsPluginErrorConstants.nonRetryableServiceError.recoverySuggestion - - return .service( - errorDescription, - recoverySuggestion, - rootError ?? self - ) } } diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/ErrorPushNotificationsTests.swift b/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/ErrorPushNotificationsTests.swift new file mode 100644 index 0000000000..14963a0cc1 --- /dev/null +++ b/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/ErrorPushNotificationsTests.swift @@ -0,0 +1,105 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +import AWSClientRuntime +import AwsCommonRuntimeKit +import AWSPinpoint +@testable import AWSPinpointPushNotificationsPlugin +import ClientRuntime +import Foundation +import XCTest + +class ErrorPushNotificationsTests: XCTestCase { + /// Given: A NSError error + /// When: pushNotificationsError is invoked + /// Then: An .unknown error is returned + func testPushNotificationsError_withUnknownError_shouldReturnUnknownError() { + let error = NSError(domain: "MyError", code: 1234) + let pushNotificationsError = error.pushNotificationsError + switch pushNotificationsError { + case .unknown(let errorDescription, let underlyingError): + XCTAssertEqual(errorDescription, "An unknown error occurred") + XCTAssertEqual(error.localizedDescription, underlyingError?.localizedDescription) + default: + XCTFail("Expected error of type .unknown, got \(pushNotificationsError)") + } + } + + /// Given: A NSError error with a connectivity-related error code + /// When: pushNotificationsError is invoked + /// Then: A .network error is returned + func testPushNotificationsError_withConnectivityError_shouldReturnNetworkError() { + let error = NSError(domain: "ConnectivityError", code: NSURLErrorNotConnectedToInternet) + let pushNotificationsError = error.pushNotificationsError + switch pushNotificationsError { + case .network(let errorDescription, let recoverySuggestion, let underlyingError): + XCTAssertEqual(errorDescription, PushNotificationsPluginErrorConstants.deviceOffline.errorDescription) + XCTAssertEqual(recoverySuggestion, PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion) + XCTAssertEqual(error.localizedDescription, underlyingError?.localizedDescription) + default: + XCTFail("Expected error of type .network, got \(pushNotificationsError)") + } + } + + /// Given: An Error defined by the SDK + /// When: pushNotificationsError is invoked + /// Then: A .service error is returned + func testPushNotificationError_withServiceError_shouldReturnServiceError() { + let errors: [(String, PushNotificationsErrorConvertible & Error)] = [ + ("BadRequestException", BadRequestException(message: "BadRequestException")), + ("InternalServerErrorException", InternalServerErrorException(message: "InternalServerErrorException")), + ("ForbiddenException", ForbiddenException(message: "ForbiddenException")), + ("MethodNotAllowedException", MethodNotAllowedException(message: "MethodNotAllowedException")), + ("NotFoundException", NotFoundException(message: "NotFoundException")), + ("PayloadTooLargeException", PayloadTooLargeException(message: "PayloadTooLargeException")), + ("TooManyRequestsException", TooManyRequestsException(message: "TooManyRequestsException")) + ] + + for (expectedMessage, error) in errors { + let pushNotificationsError = error.pushNotificationsError + switch pushNotificationsError { + case .service(let errorDescription, let recoverySuggestion, let underlyingError): + XCTAssertEqual(errorDescription, expectedMessage) + XCTAssertEqual(recoverySuggestion, PushNotificationsPluginErrorConstants.nonRetryableServiceError.recoverySuggestion) + XCTAssertEqual(error.localizedDescription, underlyingError?.localizedDescription) + default: + XCTFail("Expected error of type .service, got \(pushNotificationsError)") + } + } + } + + /// Given: An UnknownAWSHTTPServiceError + /// When: pushNotificationsError is invoked + /// Then: A .unknown error is returned + func testPushNotificationError_withUnknownAWSHTTPServiceError_shouldReturnUnknownError() { + let error = UnknownAWSHTTPServiceError(httpResponse: .init(body: .none, statusCode: .accepted), message: "UnknownAWSHTTPServiceError", requestID: nil, typeName: nil) + let pushNotificationsError = error.pushNotificationsError + switch pushNotificationsError { + case .unknown(let errorDescription, let underlyingError): + XCTAssertEqual(errorDescription, "UnknownAWSHTTPServiceError") + XCTAssertEqual(error.localizedDescription, underlyingError?.localizedDescription) + default: + XCTFail("Expected error of type .unknown, got \(pushNotificationsError)") + } + } + + /// Given: A CommonRunTimeError.crtError + /// When: pushNotificationsError is invoked + /// Then: A .unknown error is returned + func testPushNotificationError_withCommonRunTimeError_shouldReturnUnknownError() { + let error = CommonRunTimeError.crtError(.init(code: 12345)) + let pushNotificationsError = error.pushNotificationsError + switch pushNotificationsError { + case .unknown(let errorDescription, let underlyingError): + XCTAssertEqual(errorDescription, "Unknown Error Code") + XCTAssertEqual(error.localizedDescription, underlyingError?.localizedDescription) + default: + XCTFail("Expected error of type .unknown, got \(pushNotificationsError)") + } + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Dependency/AWSTranscribeStreamingAdapter.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Dependency/AWSTranscribeStreamingAdapter.swift index ada2ff5135..97aed80ff8 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Dependency/AWSTranscribeStreamingAdapter.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Dependency/AWSTranscribeStreamingAdapter.swift @@ -22,10 +22,10 @@ class AWSTranscribeStreamingAdapter: AWSTranscribeStreamingBehavior { let mediaSampleRateHertz: Int } - let credentialsProvider: CredentialsProvider + let credentialsProvider: CredentialsProviding let region: String - init(credentialsProvider: CredentialsProvider, region: String) { + init(credentialsProvider: CredentialsProviding, region: String) { self.credentialsProvider = credentialsProvider self.region = region } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Comprehend.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Comprehend.swift index ed04516078..d44e7729f3 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Comprehend.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Comprehend.swift @@ -38,8 +38,8 @@ extension AWSPredictionsService: AWSComprehendServiceBehavior { .map(Predictions.Language.init(locale:)) ?? .undetermined return (predictionsLanguage, dominantLanguage?.score.map(Double.init)) - } catch let error as DetectDominantLanguageOutputError { - throw ServiceErrorMapping.detectDominantLanguage.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Polly.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Polly.swift index ef070be2e2..62dbf18308 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Polly.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Polly.swift @@ -24,7 +24,7 @@ extension AWSPredictionsService: AWSPollyServiceBehavior { do { let synthesizedSpeechResult = try await awsPolly.synthesizeSpeech(input: input) - guard let speech = synthesizedSpeechResult.audioStream + guard let speech = try await synthesizedSpeechResult.audioStream?.readData() else { throw PredictionsError.service( .init( @@ -34,12 +34,9 @@ extension AWSPredictionsService: AWSPollyServiceBehavior { ) } - let textToSpeechResult = Predictions.Convert.TextToSpeech.Result( - audioData: speech.toBytes().getData() - ) - return textToSpeechResult - } catch let error as SynthesizeSpeechOutputError { - throw ServiceErrorMapping.synthesizeSpeech.map(error) + return .init(audioData: speech) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Rekognition.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Rekognition.swift index 8a67da343b..0846015c48 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Rekognition.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Rekognition.swift @@ -24,8 +24,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let labelsResult = try await detectLabels(image: imageData) let newLabels = IdentifyLabelsResultTransformers.processLabels(labelsResult.labels ?? []) return Predictions.Identify.Labels.Result(labels: newLabels, unsafeContent: nil) - } catch let error as DetectLabelsOutputError { - throw ServiceErrorMapping.detectLabels.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -35,8 +35,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let unsafeContent = !moderationLabels.isEmpty let labels = IdentifyLabelsResultTransformers.processModerationLabels(moderationLabels) return Predictions.Identify.Labels.Result(labels: labels, unsafeContent: unsafeContent) - } catch let error as DetectModerationLabelsOutputError { - throw ServiceErrorMapping.detectModerationLabels.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) @@ -67,8 +67,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let newCelebs = IdentifyCelebritiesResultTransformers.processCelebs(celebrities) return Predictions.Identify.Celebrities.Result(celebrities: newCelebs) - } catch let error as RecognizeCelebritiesOutputError { - throw ServiceErrorMapping.detectCelebrities.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -94,8 +94,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { .searchFacesByImage(input: input).faceMatches ?? [] let faceMatches = IdentifyEntitiesResultTransformers.processCollectionFaces(faces) return Predictions.Identify.EntityMatches.Result(entities: faceMatches) - } catch let error as SearchFacesByImageOutputError { - throw ServiceErrorMapping.searchFacesByImage.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -112,8 +112,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let faces = try await awsRekognition.detectFaces(input: input).faceDetails ?? [] let newFaces = IdentifyEntitiesResultTransformers.processFaces(faces) return Predictions.Identify.Entities.Result(entities: newFaces) - } catch let error as DetectFacesOutputError { - throw ServiceErrorMapping.detectFaces.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unknownServiceError(error) } @@ -128,8 +128,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { do { textResult = try await awsRekognition.detectText(input: request) - } catch let error as DetectTextOutputError { - throw ServiceErrorMapping.detectText.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -147,8 +147,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let documentTextResult: DetectDocumentTextOutputResponse do { documentTextResult = try await detectDocumentText(image: imageData) - } catch let error as DetectDocumentTextOutputError { - throw ServiceErrorMapping.detectDocumentText.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -200,8 +200,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let textResult: DetectTextOutputResponse do { textResult = try await awsRekognition.detectText(input: request) - } catch let error as DetectTextOutputError { - throw ServiceErrorMapping.detectText.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -220,8 +220,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let documentTextResult: DetectDocumentTextOutputResponse do { documentTextResult = try await detectDocumentText(image: imageData) - } catch let error as DetectDocumentTextOutputError { - throw ServiceErrorMapping.detectDocumentText.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } @@ -275,10 +275,8 @@ extension AWSPredictionsService: AWSRekognitionServiceBehavior { let moderationLabels = moderationLabelsOutput.moderationLabels ?? [] let unsafeContent = !moderationLabels.isEmpty return Predictions.Identify.Labels.Result(labels: allLabels, unsafeContent: unsafeContent) - } catch let error as DetectLabelsOutputError { - throw ServiceErrorMapping.detectLabels.map(error) - } catch let error as DetectModerationLabelsOutputError { - throw ServiceErrorMapping.detectModerationLabels.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Textract.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Textract.swift index ec1e1b70ce..fd882740d9 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Textract.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Textract.swift @@ -41,8 +41,8 @@ extension AWSPredictionsService: AWSTextractServiceBehavior { let documentResult: AnalyzeDocumentOutputResponse do { documentResult = try await awsTextract.analyzeDocument(input: request) - } catch let error as AnalyzeDocumentOutputError { - throw ServiceErrorMapping.analyzeDocument.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Translate.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Translate.swift index a09318f4ed..81d3a47d20 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Translate.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService+Translate.swift @@ -35,8 +35,8 @@ extension AWSPredictionsService: AWSTranslateServiceBehavior { let textTranslateResult: TranslateTextOutputResponse do { textTranslateResult = try await awsTranslate.translateText(input: request) - } catch let error as TranslateTextOutputError { - throw ServiceErrorMapping.translateText.map(error) + } catch let error as PredictionsErrorConvertible { + throw error.predictionsError } catch { throw PredictionsError.unexpectedServiceErrorType(error) } diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift index 08a3336789..b90c7e0a03 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift @@ -12,7 +12,7 @@ import AWSTextract import AWSComprehend import AWSPolly import AWSPluginsCore -@_spi(FoundationClientEngine) import AWSPluginsCore +@_spi(PluginHTTPClientEngine) import AWSPluginsCore import Foundation import ClientRuntime import AWSClientRuntime @@ -31,67 +31,54 @@ class AWSPredictionsService { convenience init( configuration: PredictionsPluginConfiguration, - credentialsProvider: CredentialsProvider, + credentialsProvider: CredentialsProviding, identifier: String ) throws { let translateClientConfiguration = try TranslateClient.TranslateClientConfiguration( - credentialsProvider: credentialsProvider, - region: configuration.convert.region + region: configuration.convert.region, + credentialsProvider: credentialsProvider ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - translateClientConfiguration.httpClientEngine = FoundationClientEngine() - #endif + translateClientConfiguration.httpClientEngine = .userAgentEngine( + for: translateClientConfiguration + ) + let awsTranslateClient = TranslateClient(config: translateClientConfiguration) let pollyClientConfiguration = try PollyClient.PollyClientConfiguration( - credentialsProvider: credentialsProvider, - region: configuration.convert.region + region: configuration.convert.region, + credentialsProvider: credentialsProvider + ) + pollyClientConfiguration.httpClientEngine = .userAgentEngine( + for: pollyClientConfiguration ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - pollyClientConfiguration.httpClientEngine = FoundationClientEngine() - #endif let awsPollyClient = PollyClient(config: pollyClientConfiguration) let comprehendClientConfiguration = try ComprehendClient.ComprehendClientConfiguration( - credentialsProvider: credentialsProvider, - region: configuration.convert.region + region: configuration.convert.region, + credentialsProvider: credentialsProvider + ) + comprehendClientConfiguration.httpClientEngine = .userAgentEngine( + for: comprehendClientConfiguration ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - comprehendClientConfiguration.httpClientEngine = FoundationClientEngine() - #endif + let awsComprehendClient = ComprehendClient(config: comprehendClientConfiguration) let rekognitionClientConfiguration = try RekognitionClient.RekognitionClientConfiguration( - credentialsProvider: credentialsProvider, - region: configuration.identify.region + region: configuration.identify.region, + credentialsProvider: credentialsProvider + ) + rekognitionClientConfiguration.httpClientEngine = .userAgentEngine( + for: rekognitionClientConfiguration ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - rekognitionClientConfiguration.httpClientEngine = FoundationClientEngine() - #endif let awsRekognitionClient = RekognitionClient(config: rekognitionClientConfiguration) let textractClientConfiguration = try TextractClient.TextractClientConfiguration( - credentialsProvider: credentialsProvider, - region: configuration.identify.region + region: configuration.identify.region, + credentialsProvider: credentialsProvider + ) + textractClientConfiguration.httpClientEngine = .userAgentEngine( + for: textractClientConfiguration ) - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - textractClientConfiguration.httpClientEngine = FoundationClientEngine() - #endif let awsTextractClient = TextractClient(config: textractClientConfiguration) let awsTranscribeStreamingAdapter = AWSTranscribeStreamingAdapter( diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend+PredictionsErrorConvertible.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend+PredictionsErrorConvertible.swift new file mode 100644 index 0000000000..d1ad4a9911 --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend+PredictionsErrorConvertible.swift @@ -0,0 +1,31 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSComprehend +import Amplify + +protocol PredictionsErrorConvertible { + var predictionsError: PredictionsError { get } +} + +extension AWSComprehend.InternalServerException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.internalServerError) + } +} + +extension AWSComprehend.InvalidRequestException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.invalidRequest) + } +} + +extension AWSComprehend.TextSizeLimitExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.textSizeLimitExceeded) + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend/ServiceErrorMapping+DetectDominantLanguageOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend/ServiceErrorMapping+DetectDominantLanguageOutputError.swift deleted file mode 100644 index de5b76f695..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Comprehend/ServiceErrorMapping+DetectDominantLanguageOutputError.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSComprehend -import Amplify - -extension ServiceErrorMapping where T == DetectDominantLanguageOutputError { - static let detectDominantLanguage: Self = .init { error in - switch error { - case .internalServerException: - return PredictionsError.service(.internalServerError) - case .invalidRequestException: - return PredictionsError.service(.invalidRequest) - case .textSizeLimitExceededException: - return PredictionsError.service(.textSizeLimitExceeded) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly+PredictionsErrorConvertible.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly+PredictionsErrorConvertible.swift new file mode 100644 index 0000000000..78796e95ce --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly+PredictionsErrorConvertible.swift @@ -0,0 +1,93 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSPolly +import Amplify + +extension AWSPolly.InvalidSampleRateException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.invalidSampleRate) + } +} + +extension AWSPolly.LanguageNotSupportedException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.unsupportedLanguage) + } +} + +extension AWSPolly.ServiceFailureException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.internalServerError) + } +} + +extension AWSPolly.TextLengthExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.textSizeLimitExceeded) + } +} + +extension AWSPolly.LexiconNotFoundException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "Amazon Polly can't find the specified lexicon. This could be caused by a lexicon that is missing, its name is misspelled or specifying a lexicon that is in a different region.", + recoverySuggestion: "Verify that the lexicon exists, is in the region (see ListLexicons) and that you spelled its name is spelled correctly. Then try again.", + underlyingError: self + ) + ) + } +} + +extension AWSPolly.MarksNotSupportedForFormatException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "Speech marks are not supported for the OutputFormat selected.", + recoverySuggestion: "Speech marks are only available for content in json format.", + underlyingError: self + ) + ) + } +} + +extension AWSPolly.InvalidSsmlException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The SSML you provided is invalid.", + recoverySuggestion: "Verify the SSML syntax, spelling of tags and values, and then try again.", + underlyingError: self + ) + ) + } +} + +extension AWSPolly.SsmlMarksNotSupportedForTextTypeException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "SSML speech marks are not supported for plain text-type input.", + recoverySuggestion: "", + underlyingError: self + ) + ) + } +} + +extension AWSPolly.EngineNotSupportedException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "This engine is not compatible with the voice that you have designated.", + recoverySuggestion: "Choose a new voice that is compatible with the engine or change the engine and restart the operation.", + underlyingError: self + ) + ) + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly/ServiceErrorMapping+SynthesizeSpeechOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly/ServiceErrorMapping+SynthesizeSpeechOutputError.swift deleted file mode 100644 index 60688eb7cd..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Polly/ServiceErrorMapping+SynthesizeSpeechOutputError.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSPolly -import Amplify - -extension ServiceErrorMapping where T == SynthesizeSpeechOutputError { - static let synthesizeSpeech: Self = .init { error in - switch error { - case .invalidSampleRateException: - return PredictionsError.service(.invalidSampleRate) - case .languageNotSupportedException: - return PredictionsError.service(.unsupportedLanguage) - case .serviceFailureException: - return PredictionsError.service(.internalServerError) - case .textLengthExceededException: - return PredictionsError.service(.textSizeLimitExceeded) - case .lexiconNotFoundException: - return PredictionsError.service( - .init( - description: "Amazon Polly can't find the specified lexicon. This could be caused by a lexicon that is missing, its name is misspelled or specifying a lexicon that is in a different region.", - recoverySuggestion: "Verify that the lexicon exists, is in the region (see ListLexicons) and that you spelled its name is spelled correctly. Then try again.", - underlyingError: error - ) - ) - case .marksNotSupportedForFormatException: - return PredictionsError.service( - .init( - description: "Speech marks are not supported for the OutputFormat selected.", - recoverySuggestion: "Speech marks are only available for content in json format.", - underlyingError: error - ) - ) - case .invalidSsmlException: - return PredictionsError.service( - .init( - description: "The SSML you provided is invalid.", - recoverySuggestion: "Verify the SSML syntax, spelling of tags and values, and then try again.", - underlyingError: error - ) - ) - case .ssmlMarksNotSupportedForTextTypeException: - return PredictionsError.service( - .init( - description: "SSML speech marks are not supported for plain text-type input.", - recoverySuggestion: "", - underlyingError: error - ) - ) - case .engineNotSupportedException: - return PredictionsError.service( - .init( - description: "This engine is not compatible with the voice that you have designated.", - recoverySuggestion: "Choose a new voice that is compatible with the engine or change the engine and restart the operation.", - underlyingError: error - ) - ) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition+PredictionsErrorConvertible.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition+PredictionsErrorConvertible.swift new file mode 100644 index 0000000000..464060e9da --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition+PredictionsErrorConvertible.swift @@ -0,0 +1,121 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSRekognition +import Amplify +import ClientRuntime + + +extension AWSRekognition.HumanLoopQuotaExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of in-progress human reviews you have has exceeded the number allowed.", + recoverySuggestion: "Try again later.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSRekognition.ResourceNotFoundException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.resourceNotFound) + } +} + +extension AWSRekognition.ThrottlingException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.throttling) + } +} + +extension AWSRekognition.InternalServerError: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.internalServerError) + } +} + +extension AWSRekognition.AccessDeniedException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.accessDenied) + } +} + +extension AWSRekognition.ImageTooLargeException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The image you sent was too large.", + recoverySuggestion: "Try downsizing the image and sending it again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSRekognition.InvalidImageFormatException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The provided image format isn't supported.", + recoverySuggestion: "Use a supported image format (.JPEG and .PNG) and try again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSRekognition.InvalidParameterException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "An input parameter violated a constraint.", + recoverySuggestion: "Validate your parameters before calling the API operation again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSRekognition.InvalidS3ObjectException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of requests exceeded your throughput limit.", + recoverySuggestion: """ + Decrease the number of calls you are making until it is below the limit for your region. + Check the limits here: + https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition + """, + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSRekognition.ProvisionedThroughputExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of requests exceeded your throughput limit.", + recoverySuggestion: """ + Decrease the number of calls you are making until it is below the limit for your region. + Check the limits here: + https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition + """, + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/RekognitionCommonExceptions.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/RekognitionCommonExceptions.swift deleted file mode 100644 index 3f7933a26c..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/RekognitionCommonExceptions.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify -import ClientRuntime - -enum RekognitionCommonExceptions { - static func imageTooLarge(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "The image you sent was too large.", - recoverySuggestion: "Try downsizing the image and sending it again.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func invalidImageFormat(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "The provided image format isn't supported.", - recoverySuggestion: "Use a supported image format (.JPEG and .PNG) and try again.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func invalidParameter(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "An input parameter violated a constraint.", - recoverySuggestion: "Validate your parameters before calling the API operation again.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func invalidS3Object(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - .service( - .init( - description: "The number of requests exceeded your throughput limit.", - recoverySuggestion: """ - Decrease the number of calls you are making until it is below the limit for your region. - Check the limits here: - https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition - """, - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func provisionedThroughputExceeded(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - .service( - .init( - description: "The number of requests exceeded your throughput limit.", - recoverySuggestion: """ - Decrease the number of calls you are making until it is below the limit for your region. - Check the limits here: - https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition - """, - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectFacesOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectFacesOutputError.swift deleted file mode 100644 index 4b79ce1683..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectFacesOutputError.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == DetectFacesOutputError { - static let detectFaces: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge( - error, statusCode: imageTooLargeException._statusCode - ) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, - statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectLabelsOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectLabelsOutputError.swift deleted file mode 100644 index be00516c14..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectLabelsOutputError.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == DetectLabelsOutputError { - static let detectLabels: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge( - error, statusCode: imageTooLargeException._statusCode - ) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, - statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectModerationLabelsOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectModerationLabelsOutputError.swift deleted file mode 100644 index 37d855ae67..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectModerationLabelsOutputError.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == DetectModerationLabelsOutputError { - static let detectModerationLabels: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge( - error, statusCode: imageTooLargeException._statusCode - ) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, - statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .humanLoopQuotaExceededException(let humanLoopQuotaExceededException): - return .service( - .init( - description: "", - recoverySuggestion: "", - httpStatusCode: humanLoopQuotaExceededException._statusCode?.rawValue, - underlyingError: error - ) - ) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectTextOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectTextOutputError.swift deleted file mode 100644 index 0e76a4a4b9..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+DetectTextOutputError.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == DetectTextOutputError { - static let detectText: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge( - error, statusCode: imageTooLargeException._statusCode - ) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, - statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+RekognizeCelebritiesOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+RekognizeCelebritiesOutputError.swift deleted file mode 100644 index 0faa619191..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+RekognizeCelebritiesOutputError.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == RecognizeCelebritiesOutputError { - static let detectCelebrities: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge(error, statusCode: imageTooLargeException._statusCode) - case .internalServerError(let internalServerError): - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException(let throttlingException): - return PredictionsError.service(.throttling) - case .unknown(let unknown): - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+SearchFacesByImageOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+SearchFacesByImageOutputError.swift deleted file mode 100644 index 9205f15493..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Rekognition/ServiceErrorMapping+SearchFacesByImageOutputError.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSRekognition -import Amplify - -extension ServiceErrorMapping where T == SearchFacesByImageOutputError { - static let searchFacesByImage: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .imageTooLargeException(let imageTooLargeException): - return RekognitionCommonExceptions.imageTooLarge( - error, statusCode: imageTooLargeException._statusCode - ) - case .internalServerError(let internalServerError): - return PredictionsError.service(.internalServerError) - case .invalidImageFormatException(let invalidImageFormatException): - return RekognitionCommonExceptions.invalidImageFormat( - error, statusCode: invalidImageFormatException._statusCode - ) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .resourceNotFoundException(let resourceNotFoundException): - return PredictionsError.service(.resourceNotFound) - case .throttlingException(let throttlingException): - return PredictionsError.service(.throttling) - case .unknown(let unknownAWSHttpServiceError): - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/ServiceErrorMapping.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/ServiceErrorMapping.swift deleted file mode 100644 index 5fec8447cf..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/ServiceErrorMapping.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify - -struct ServiceErrorMapping { - let map: (T) -> PredictionsError - - static func map(_ error: T, with rule: ServiceErrorMapping) -> PredictionsError { - rule.map(error) - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract+PredictionsErrorConvertible.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract+PredictionsErrorConvertible.swift new file mode 100644 index 0000000000..9aa5908460 --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract+PredictionsErrorConvertible.swift @@ -0,0 +1,130 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSTextract +import Amplify +import ClientRuntime + + +extension AWSTextract.HumanLoopQuotaExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of in-progress human reviews you have has exceeded the number allowed.", + recoverySuggestion: "Try again later.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSTextract.ThrottlingException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.throttling) + } +} + +extension AWSTextract.InternalServerError: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.internalServerError) + } +} + +extension AWSTextract.AccessDeniedException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.accessDenied) + } +} + +extension AWSTextract.InvalidParameterException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "An input parameter violated a constraint.", + recoverySuggestion: "Validate your parameters before calling the API operation again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSTextract.InvalidS3ObjectException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of requests exceeded your throughput limit.", + recoverySuggestion: """ + Decrease the number of calls you are making until it is below the limit for your region. + Check the limits here: + https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition + """, + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSTextract.ProvisionedThroughputExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The number of requests exceeded your throughput limit.", + recoverySuggestion: """ + Decrease the number of calls you are making until it is below the limit for your region. + Check the limits here: + https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_rekognition + """, + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + + +extension AWSTextract.BadDocumentException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The image sent over was corrupt or malformed.", + recoverySuggestion: "Please double check the image sent over and try again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + +extension AWSTextract.DocumentTooLargeException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The image sent over was too large.", + recoverySuggestion: "Please decrease the size of the image sent over and try again.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} + + +extension AWSTextract.UnsupportedDocumentException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service( + .init( + description: "The document type sent over is unsupported", + recoverySuggestion: "The formats supported are PNG or JPEG format.", + httpStatusCode: httpResponse.statusCode.rawValue, + underlyingError: self + ) + ) + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+AnalyzeDocumentOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+AnalyzeDocumentOutputError.swift deleted file mode 100644 index 6312a60644..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+AnalyzeDocumentOutputError.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSTextract -import Amplify -import ClientRuntime - -extension ServiceErrorMapping where T == AnalyzeDocumentOutputError { - static let analyzeDocument: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .badDocumentException(let badDocumentException): - return TextractCommonException.badDocument( - error, statusCode: badDocumentException._statusCode - ) - case .documentTooLargeException(let documentTooLargeException): - return TextractCommonException.documentTooLarge( - error, statusCode: documentTooLargeException._statusCode - ) - case .humanLoopQuotaExceededException(let humanLoopQuotaExceededException): - return .service( - .init( - description: "", - recoverySuggestion: "", - httpStatusCode: humanLoopQuotaExceededException._statusCode?.rawValue, - underlyingError: error - ) - ) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return TextractCommonException.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .unsupportedDocumentException(let unsupportedDocumentException): - return TextractCommonException.unsupportedDocument( - error, statusCode: unsupportedDocumentException._statusCode - ) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+DetectDocumentTextOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+DetectDocumentTextOutputError.swift deleted file mode 100644 index d6d48521d9..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/ServiceErrorMapping+DetectDocumentTextOutputError.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSTextract -import Amplify - -extension ServiceErrorMapping where T == DetectDocumentTextOutputError { - static let detectDocumentText: Self = .init { error in - switch error { - case .accessDeniedException(let accessDeniedException): - return PredictionsError.service(.accessDenied) - case .internalServerError: - return PredictionsError.service(.internalServerError) - case .invalidParameterException(let invalidParameterException): - return RekognitionCommonExceptions.invalidParameter( - error, - statusCode: invalidParameterException._statusCode - ) - case .invalidS3ObjectException(let invalidS3ObjectException): - return RekognitionCommonExceptions.invalidS3Object( - error, - statusCode: invalidS3ObjectException._statusCode - ) - case .provisionedThroughputExceededException(let provisionedThroughputExceededException): - return RekognitionCommonExceptions.provisionedThroughputExceeded( - error, - statusCode: provisionedThroughputExceededException._statusCode - ) - case .throttlingException: - return PredictionsError.service(.throttling) - case .unknown: - return PredictionsError.unknownServiceError(error) - case .badDocumentException(let badDocumentException): - return TextractCommonException.badDocument( - error, statusCode: badDocumentException._statusCode - ) - case .documentTooLargeException(let documentTooLargeException): - return TextractCommonException.documentTooLarge( - error, statusCode: documentTooLargeException._statusCode - ) - case .unsupportedDocumentException(let unsupportedDocumentException): - return TextractCommonException.unsupportedDocument( - error, statusCode: unsupportedDocumentException._statusCode - ) - } - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/TextractCommonException.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/TextractCommonException.swift deleted file mode 100644 index 415284f354..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Textract/TextractCommonException.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSTextract -import Amplify -import ClientRuntime - -enum TextractCommonException { - static func badDocument(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "The image sent over was corrupt or malformed.", - recoverySuggestion: "Please double check the image sent over and try again.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func documentTooLarge(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "The image sent over was too large.", - recoverySuggestion: "Please decrease the size of the image sent over and try again.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func unsupportedDocument(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - return .service( - .init( - description: "The document type sent over is unsupported", - recoverySuggestion: "The formats supported are PNG or JPEG format.", - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } - - static func provisionedThroughputExceeded(_ error: Error, statusCode: HttpStatusCode?) -> PredictionsError { - .service( - .init( - description: "The number of requests exceeded your throughput limit.", - recoverySuggestion: """ - Decrease the number of calls you are making until it is below the limit for your region. - Check the limits here: - https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_textract - """, - httpStatusCode: statusCode?.rawValue, - underlyingError: error - ) - ) - } -} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate+PredictionsErrorConvertible.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate+PredictionsErrorConvertible.swift new file mode 100644 index 0000000000..aeb5897aac --- /dev/null +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate+PredictionsErrorConvertible.swift @@ -0,0 +1,52 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSTranslate +import Amplify + +extension AWSTranslate.DetectedLanguageLowConfidenceException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.detectedLanguageLowConfidence) + } +} + +extension AWSTranslate.InternalServerException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.internalServerError) + } +} + + +extension AWSTranslate.InvalidRequestException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.invalidRequest) + } +} + +extension AWSTranslate.ResourceNotFoundException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.resourceNotFound) + } +} + +extension AWSTranslate.TextSizeLimitExceededException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.textSizeLimitExceeded) + } +} + +extension AWSTranslate.TooManyRequestsException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.throttling) + } +} + +extension AWSTranslate.UnsupportedLanguagePairException: PredictionsErrorConvertible { + var predictionsError: PredictionsError { + .service(.unsupportedLanguagePair) + } +} diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate/ServiceErrorMapping+TranslateTextOutputError.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate/ServiceErrorMapping+TranslateTextOutputError.swift deleted file mode 100644 index 15706934c4..0000000000 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Support/Internal/ErrorHandling/Translate/ServiceErrorMapping+TranslateTextOutputError.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import AWSTranslate -import Amplify - -extension ServiceErrorMapping where T == TranslateTextOutputError { - static let translateText: Self = .init { error in - switch error { - case .detectedLanguageLowConfidenceException(let detectedLanguageLowConfidenceException): - return PredictionsError.service(.detectedLanguageLowConfidence) - case .internalServerException(let internalServerException): - return PredictionsError.service(.internalServerError) - case .invalidRequestException(let invalidRequestException): - return PredictionsError.service(.invalidRequest) - case .resourceNotFoundException, .serviceUnavailableException: - return PredictionsError.service(.resourceNotFound) - case .textSizeLimitExceededException(let textSizeLimitExceededException): - return PredictionsError.service(.textSizeLimitExceeded) - case .tooManyRequestsException(let tooManyRequestsException): - return PredictionsError.service(.throttling) - case .unsupportedLanguagePairException(let unsupportedLanguagePairException): - return PredictionsError.service(.unsupportedLanguagePair) - case .unknown: - return PredictionsError.unknownServiceError(error) - } - } -} diff --git a/AmplifyPlugins/Predictions/CoreMLPredictionsPlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Predictions/CoreMLPredictionsPlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Predictions/CoreMLPredictionsPlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/PollyErrorMappingTestCase.swift b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/PollyErrorMappingTestCase.swift index c9576ca6a5..62133dac70 100644 --- a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/PollyErrorMappingTestCase.swift +++ b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/PollyErrorMappingTestCase.swift @@ -12,37 +12,41 @@ import Amplify final class PollyErrorMappingTestCase: XCTestCase { private func assertCatchVariations( - for sdkError: SynthesizeSpeechOutputError, + for sdkError: Error, expecting expectedServiceError: PredictionsError.ServiceError, label: String ) { - let predictionsError = ServiceErrorMapping.synthesizeSpeech.map(sdkError) let unexpected: (Error) -> String = { "Expected PredictionsError.service(.\(label), received \($0)" } + // catch variation 1. - do { throw predictionsError } - catch PredictionsError.service(expectedServiceError) {} + do { throw sdkError } + catch let error as PredictionsErrorConvertible { + guard case .service(expectedServiceError) = error.predictionsError else { + return XCTFail(unexpected(error.predictionsError)) + } + } catch { XCTFail(unexpected(error)) } // catch variation 2. - do { throw predictionsError } - catch let error as PredictionsError { - guard case .service(expectedServiceError) = error else { - return XCTFail(unexpected(error)) + do { throw sdkError } + catch let error as PredictionsErrorConvertible { + guard case .service(expectedServiceError) = error.predictionsError else { + return XCTFail(unexpected(error.predictionsError)) } } catch { XCTFail(unexpected(error)) } // catch variation 3. - do { throw predictionsError } + do { throw sdkError } catch { - guard let error = error as? PredictionsError, - case .service(expectedServiceError) = error + guard let error = error as? PredictionsErrorConvertible, + case .service(expectedServiceError) = error.predictionsError else { return XCTFail(unexpected(error)) } @@ -51,7 +55,7 @@ final class PollyErrorMappingTestCase: XCTestCase { func testSynthesizeSpeech_invalidSampleRateException() throws { assertCatchVariations( - for: .invalidSampleRateException(.init()), + for: InvalidSampleRateException(), expecting: .invalidSampleRate, label: "invalidSampleRate" ) @@ -59,7 +63,7 @@ final class PollyErrorMappingTestCase: XCTestCase { func testSynthesizeSpeech_languageNotSupportedException() throws { assertCatchVariations( - for: .languageNotSupportedException(.init()), + for: LanguageNotSupportedException(), expecting: .unsupportedLanguage, label: "unsupportedLanguage" ) @@ -67,7 +71,7 @@ final class PollyErrorMappingTestCase: XCTestCase { func testSynthesizeSpeech_serviceFailureException() throws { assertCatchVariations( - for: .serviceFailureException(.init()), + for: ServiceFailureException(), expecting: .internalServerError, label: "internalServerError" ) @@ -75,7 +79,7 @@ final class PollyErrorMappingTestCase: XCTestCase { func testSynthesizeSpeech_textLengthExceededException() throws { assertCatchVariations( - for: .textLengthExceededException(.init()), + for: TextLengthExceededException(), expecting: .textSizeLimitExceeded, label: "textSizeLimitExceeded" ) diff --git a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/TextractErrorMappingTestCase.swift b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/TextractErrorMappingTestCase.swift index bafe9a75ce..526b3d37ae 100644 --- a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/TextractErrorMappingTestCase.swift +++ b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/ErrorMapping/TextractErrorMappingTestCase.swift @@ -12,37 +12,40 @@ import Amplify final class TextractErrorMappingTestCase: XCTestCase { private func assertCatchVariations( - for sdkError: AnalyzeDocumentOutputError, + for sdkError: Error, expecting expectedServiceError: PredictionsError.ServiceError, label: String ) { - let predictionsError = ServiceErrorMapping.analyzeDocument.map(sdkError) let unexpected: (Error) -> String = { "Expected PredictionsError.service(.\(label), received \($0)" } // catch variation 1. - do { throw predictionsError } - catch PredictionsError.service(expectedServiceError) {} + do { throw sdkError } + catch let error as PredictionsErrorConvertible { + guard case .service(expectedServiceError) = error.predictionsError else { + return XCTFail(unexpected(error.predictionsError)) + } + } catch { XCTFail(unexpected(error)) } // catch variation 2. - do { throw predictionsError } - catch let error as PredictionsError { - guard case .service(expectedServiceError) = error else { - return XCTFail(unexpected(error)) + do { throw sdkError } + catch let error as PredictionsErrorConvertible { + guard case .service(expectedServiceError) = error.predictionsError else { + return XCTFail(unexpected(error.predictionsError)) } } catch { XCTFail(unexpected(error)) } // catch variation 3. - do { throw predictionsError } + do { throw sdkError } catch { - guard let error = error as? PredictionsError, - case .service(expectedServiceError) = error + guard let error = error as? PredictionsErrorConvertible, + case .service(expectedServiceError) = error.predictionsError else { return XCTFail(unexpected(error)) } @@ -51,7 +54,7 @@ final class TextractErrorMappingTestCase: XCTestCase { func testAnalyzeDocument_internalServerError() throws { assertCatchVariations( - for: .internalServerError(.init()), + for: InternalServerError(), expecting: .internalServerError, label: "internalServerError" ) @@ -59,7 +62,7 @@ final class TextractErrorMappingTestCase: XCTestCase { func testAnalyzeDocument_accessDeniedException() throws { assertCatchVariations( - for: .accessDeniedException(.init()), + for: AccessDeniedException(), expecting: .accessDenied, label: "accessDenied" ) @@ -67,7 +70,7 @@ final class TextractErrorMappingTestCase: XCTestCase { func testAnalyzeDocument_throttlingException() throws { assertCatchVariations( - for: .throttlingException(.init()), + for: ThrottlingException(), expecting: .throttling, label: "throttling" ) diff --git a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockRekognitionBehavior.swift b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockRekognitionBehavior.swift index 9dcd814c82..ffcb4cdfb7 100644 --- a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockRekognitionBehavior.swift +++ b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockRekognitionBehavior.swift @@ -112,4 +112,13 @@ extension MockRekognitionBehavior { func untagResource(input: AWSRekognition.UntagResourceInput) async throws -> AWSRekognition.UntagResourceOutputResponse { fatalError() } func updateDatasetEntries(input: AWSRekognition.UpdateDatasetEntriesInput) async throws -> AWSRekognition.UpdateDatasetEntriesOutputResponse { fatalError() } func updateStreamProcessor(input: AWSRekognition.UpdateStreamProcessorInput) async throws -> AWSRekognition.UpdateStreamProcessorOutputResponse { fatalError() } + func associateFaces(input: AWSRekognition.AssociateFacesInput) async throws -> AWSRekognition.AssociateFacesOutputResponse { fatalError() } + func createFaceLivenessSession(input: AWSRekognition.CreateFaceLivenessSessionInput) async throws -> AWSRekognition.CreateFaceLivenessSessionOutputResponse { fatalError() } + func createUser(input: AWSRekognition.CreateUserInput) async throws -> AWSRekognition.CreateUserOutputResponse { fatalError() } + func deleteUser(input: AWSRekognition.DeleteUserInput) async throws -> AWSRekognition.DeleteUserOutputResponse { fatalError() } + func disassociateFaces(input: AWSRekognition.DisassociateFacesInput) async throws -> AWSRekognition.DisassociateFacesOutputResponse { fatalError() } + func getFaceLivenessSessionResults(input: AWSRekognition.GetFaceLivenessSessionResultsInput) async throws -> AWSRekognition.GetFaceLivenessSessionResultsOutputResponse { fatalError() } + func listUsers(input: AWSRekognition.ListUsersInput) async throws -> AWSRekognition.ListUsersOutputResponse { fatalError() } + func searchUsers(input: AWSRekognition.SearchUsersInput) async throws -> AWSRekognition.SearchUsersOutputResponse { fatalError() } + func searchUsersByImage(input: AWSRekognition.SearchUsersByImageInput) async throws -> AWSRekognition.SearchUsersByImageOutputResponse { fatalError() } } diff --git a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockTranslateBehavior.swift b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockTranslateBehavior.swift index 5a1b1e86c2..ec91544aaa 100644 --- a/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockTranslateBehavior.swift +++ b/AmplifyPlugins/Predictions/Tests/AWSPredictionsPluginUnitTests/Mocks/Service/MockTranslateBehavior.swift @@ -41,4 +41,5 @@ extension MockTranslateBehavior { func tagResource(input: AWSTranslate.TagResourceInput) async throws -> AWSTranslate.TagResourceOutputResponse { fatalError() } func untagResource(input: AWSTranslate.UntagResourceInput) async throws -> AWSTranslate.UntagResourceOutputResponse { fatalError() } func updateParallelData(input: AWSTranslate.UpdateParallelDataInput) async throws -> AWSTranslate.UpdateParallelDataOutputResponse { fatalError() } + func translateDocument(input: AWSTranslate.TranslateDocumentInput) async throws -> AWSTranslate.TranslateDocumentOutputResponse { fatalError() } } diff --git a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/CoreMLPredictionsPluginIntegrationTests/CoreMLPredictionsPluginIntegrationTest.swift b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/CoreMLPredictionsPluginIntegrationTests/CoreMLPredictionsPluginIntegrationTest.swift index 6a744d5d4e..ff1f8d1a55 100644 --- a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/CoreMLPredictionsPluginIntegrationTests/CoreMLPredictionsPluginIntegrationTest.swift +++ b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/CoreMLPredictionsPluginIntegrationTests/CoreMLPredictionsPluginIntegrationTest.swift @@ -21,7 +21,7 @@ class CoreMLPredictionsPluginIntegrationTest: AWSPredictionsPluginTestBase { in: url ) - XCTAssertEqual(result.labels.count, 0, String(describing: result)) + XCTAssertEqual(result.labels.count, 2, String(describing: result)) XCTAssertNil(result.unsafeContent, String(describing: result)) } diff --git a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp.xcodeproj/project.pbxproj index a2b9f1f0a9..cdd34b7658 100644 --- a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp.xcodeproj/project.pbxproj @@ -18,9 +18,6 @@ 6875F90F2A3CCCB7001C9AAF /* InterpretBasicIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9054235A291425630000D108 /* InterpretBasicIntegrationTests.swift */; }; 6875F9102A3CCCB7001C9AAF /* AWSPredictionsPluginTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90542359291425630000D108 /* AWSPredictionsPluginTestBase.swift */; }; 6875F9112A3CCCB7001C9AAF /* ConvertBasicIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90542358291425630000D108 /* ConvertBasicIntegrationTests.swift */; }; - 6875F9142A3CCCB7001C9AAF /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 6875F9092A3CCCB7001C9AAF /* Amplify */; }; - 6875F9152A3CCCB7001C9AAF /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 6875F90B2A3CCCB7001C9AAF /* AWSPredictionsPlugin */; }; - 6875F9162A3CCCB7001C9AAF /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 6875F90A2A3CCCB7001C9AAF /* AWSCognitoAuthPlugin */; }; 6875F9182A3CCCB7001C9AAF /* testImageText.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9054235E291425630000D108 /* testImageText.jpg */; }; 6875F9192A3CCCB7001C9AAF /* testImageCeleb.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 90542363291425630000D108 /* testImageCeleb.jpg */; }; 6875F91A2A3CCCB7001C9AAF /* testImageTextWithTables.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 90542360291425630000D108 /* testImageTextWithTables.jpg */; }; @@ -38,10 +35,6 @@ 9028304E2914042800897087 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 9028304D2914042800897087 /* AWSCognitoAuthPlugin */; }; 902830502914042800897087 /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 9028304F2914042800897087 /* AWSPredictionsPlugin */; }; 902830522914042800897087 /* CoreMLPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 902830512914042800897087 /* CoreMLPredictionsPlugin */; }; - 904D63AD291439890057D06F /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 904D63AC291439890057D06F /* Amplify */; }; - 904D63AF291439890057D06F /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 904D63AE291439890057D06F /* AWSCognitoAuthPlugin */; }; - 904D63B1291439890057D06F /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 904D63B0291439890057D06F /* AWSPredictionsPlugin */; }; - 904D63B3291439890057D06F /* CoreMLPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 904D63B2291439890057D06F /* CoreMLPredictionsPlugin */; }; 905423522914254B0000D108 /* audio.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9054234E2914254B0000D108 /* audio.wav */; }; 905423532914254B0000D108 /* people.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9054234F2914254B0000D108 /* people.jpg */; }; 905423542914254B0000D108 /* CoreMLPredictionsPluginIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905423502914254B0000D108 /* CoreMLPredictionsPluginIntegrationTest.swift */; }; @@ -59,6 +52,18 @@ 90542370291425630000D108 /* testImageCeleb.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 90542363291425630000D108 /* testImageCeleb.jpg */; }; 90542371291425630000D108 /* testImageTextForms.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 90542364291425630000D108 /* testImageTextForms.jpg */; }; 90542372291425630000D108 /* audio.wav in Resources */ = {isa = PBXBuildFile; fileRef = 90542365291425630000D108 /* audio.wav */; }; + 90CF304A2AD47A71006B6FF3 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30492AD47A71006B6FF3 /* Amplify */; }; + 90CF304C2AD47A74006B6FF3 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF304B2AD47A74006B6FF3 /* Amplify */; }; + 90CF304E2AD47A78006B6FF3 /* Amplify in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF304D2AD47A78006B6FF3 /* Amplify */; }; + 90CF30502AD47B0E006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF304F2AD47B0E006B6FF3 /* AWSCognitoAuthPlugin */; }; + 90CF30522AD47B0E006B6FF3 /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30512AD47B0E006B6FF3 /* AWSPredictionsPlugin */; }; + 90CF30542AD47B0E006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30532AD47B0E006B6FF3 /* CoreMLPredictionsPlugin */; }; + 90CF30562AD47B19006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30552AD47B19006B6FF3 /* AWSCognitoAuthPlugin */; }; + 90CF30582AD47B19006B6FF3 /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30572AD47B19006B6FF3 /* AWSPredictionsPlugin */; }; + 90CF305A2AD47B19006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF30592AD47B19006B6FF3 /* CoreMLPredictionsPlugin */; }; + 90CF305C2AD47B24006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF305B2AD47B24006B6FF3 /* AWSCognitoAuthPlugin */; }; + 90CF305E2AD47B24006B6FF3 /* AWSPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF305D2AD47B24006B6FF3 /* AWSPredictionsPlugin */; }; + 90CF30602AD47B24006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 90CF305F2AD47B24006B6FF3 /* CoreMLPredictionsPlugin */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -132,9 +137,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6875F9142A3CCCB7001C9AAF /* Amplify in Frameworks */, - 6875F9152A3CCCB7001C9AAF /* AWSPredictionsPlugin in Frameworks */, - 6875F9162A3CCCB7001C9AAF /* AWSCognitoAuthPlugin in Frameworks */, + 90CF30602AD47B24006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */, + 90CF304E2AD47A78006B6FF3 /* Amplify in Frameworks */, + 90CF305E2AD47B24006B6FF3 /* AWSPredictionsPlugin in Frameworks */, + 90CF305C2AD47B24006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -153,6 +159,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 90CF30542AD47B0E006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */, + 90CF304A2AD47A71006B6FF3 /* Amplify in Frameworks */, + 90CF30522AD47B0E006B6FF3 /* AWSPredictionsPlugin in Frameworks */, + 90CF30502AD47B0E006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -160,10 +170,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 904D63B3291439890057D06F /* CoreMLPredictionsPlugin in Frameworks */, - 904D63AD291439890057D06F /* Amplify in Frameworks */, - 904D63B1291439890057D06F /* AWSPredictionsPlugin in Frameworks */, - 904D63AF291439890057D06F /* AWSCognitoAuthPlugin in Frameworks */, + 90CF305A2AD47B19006B6FF3 /* CoreMLPredictionsPlugin in Frameworks */, + 90CF304C2AD47A74006B6FF3 /* Amplify in Frameworks */, + 90CF30582AD47B19006B6FF3 /* AWSPredictionsPlugin in Frameworks */, + 90CF30562AD47B19006B6FF3 /* AWSCognitoAuthPlugin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -326,9 +336,10 @@ ); name = AWSPredictionsPluginIntegrationTestsWatch; packageProductDependencies = ( - 6875F9092A3CCCB7001C9AAF /* Amplify */, - 6875F90A2A3CCCB7001C9AAF /* AWSCognitoAuthPlugin */, - 6875F90B2A3CCCB7001C9AAF /* AWSPredictionsPlugin */, + 90CF304D2AD47A78006B6FF3 /* Amplify */, + 90CF305B2AD47B24006B6FF3 /* AWSCognitoAuthPlugin */, + 90CF305D2AD47B24006B6FF3 /* AWSPredictionsPlugin */, + 90CF305F2AD47B24006B6FF3 /* CoreMLPredictionsPlugin */, ); productName = AWSPredictionsPluginIntegrationTests; productReference = 6875F9242A3CCCB7001C9AAF /* AWSPredictionsPluginIntegrationTestsWatch.xctest */; @@ -372,6 +383,12 @@ 90283038291402D500897087 /* PBXTargetDependency */, ); name = CoreMLPredictionsPluginIntegrationTests; + packageProductDependencies = ( + 90CF30492AD47A71006B6FF3 /* Amplify */, + 90CF304F2AD47B0E006B6FF3 /* AWSCognitoAuthPlugin */, + 90CF30512AD47B0E006B6FF3 /* AWSPredictionsPlugin */, + 90CF30532AD47B0E006B6FF3 /* CoreMLPredictionsPlugin */, + ); productName = CoreMLPredictionsPluginIntegrationTests; productReference = 90283033291402D500897087 /* CoreMLPredictionsPluginIntegrationTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -391,10 +408,10 @@ ); name = AWSPredictionsPluginIntegrationTests; packageProductDependencies = ( - 904D63AC291439890057D06F /* Amplify */, - 904D63AE291439890057D06F /* AWSCognitoAuthPlugin */, - 904D63B0291439890057D06F /* AWSPredictionsPlugin */, - 904D63B2291439890057D06F /* CoreMLPredictionsPlugin */, + 90CF304B2AD47A74006B6FF3 /* Amplify */, + 90CF30552AD47B19006B6FF3 /* AWSCognitoAuthPlugin */, + 90CF30572AD47B19006B6FF3 /* AWSPredictionsPlugin */, + 90CF30592AD47B19006B6FF3 /* CoreMLPredictionsPlugin */, ); productName = AWSPredictionsPluginIntegrationTests; productReference = 903555F829141355004B83C2 /* AWSPredictionsPluginIntegrationTests.xctest */; @@ -882,6 +899,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.coffee.PredictionsHostApp; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -916,6 +934,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.coffee.PredictionsHostApp; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -943,6 +962,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.coffee.CoreMLPredictionsPluginIntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -973,6 +993,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.coffee.CoreMLPredictionsPluginIntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -1119,47 +1140,67 @@ isa = XCSwiftPackageProductDependency; productName = AWSPredictionsPlugin; }; - 6875F9092A3CCCB7001C9AAF /* Amplify */ = { + 9028304B2914042800897087 /* Amplify */ = { isa = XCSwiftPackageProductDependency; productName = Amplify; }; - 6875F90A2A3CCCB7001C9AAF /* AWSCognitoAuthPlugin */ = { + 9028304D2914042800897087 /* AWSCognitoAuthPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSCognitoAuthPlugin; }; - 6875F90B2A3CCCB7001C9AAF /* AWSPredictionsPlugin */ = { + 9028304F2914042800897087 /* AWSPredictionsPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSPredictionsPlugin; }; - 9028304B2914042800897087 /* Amplify */ = { + 902830512914042800897087 /* CoreMLPredictionsPlugin */ = { + isa = XCSwiftPackageProductDependency; + productName = CoreMLPredictionsPlugin; + }; + 90CF30492AD47A71006B6FF3 /* Amplify */ = { isa = XCSwiftPackageProductDependency; productName = Amplify; }; - 9028304D2914042800897087 /* AWSCognitoAuthPlugin */ = { + 90CF304B2AD47A74006B6FF3 /* Amplify */ = { + isa = XCSwiftPackageProductDependency; + productName = Amplify; + }; + 90CF304D2AD47A78006B6FF3 /* Amplify */ = { + isa = XCSwiftPackageProductDependency; + productName = Amplify; + }; + 90CF304F2AD47B0E006B6FF3 /* AWSCognitoAuthPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSCognitoAuthPlugin; }; - 9028304F2914042800897087 /* AWSPredictionsPlugin */ = { + 90CF30512AD47B0E006B6FF3 /* AWSPredictionsPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSPredictionsPlugin; }; - 902830512914042800897087 /* CoreMLPredictionsPlugin */ = { + 90CF30532AD47B0E006B6FF3 /* CoreMLPredictionsPlugin */ = { isa = XCSwiftPackageProductDependency; productName = CoreMLPredictionsPlugin; }; - 904D63AC291439890057D06F /* Amplify */ = { + 90CF30552AD47B19006B6FF3 /* AWSCognitoAuthPlugin */ = { isa = XCSwiftPackageProductDependency; - productName = Amplify; + productName = AWSCognitoAuthPlugin; + }; + 90CF30572AD47B19006B6FF3 /* AWSPredictionsPlugin */ = { + isa = XCSwiftPackageProductDependency; + productName = AWSPredictionsPlugin; + }; + 90CF30592AD47B19006B6FF3 /* CoreMLPredictionsPlugin */ = { + isa = XCSwiftPackageProductDependency; + productName = CoreMLPredictionsPlugin; }; - 904D63AE291439890057D06F /* AWSCognitoAuthPlugin */ = { + 90CF305B2AD47B24006B6FF3 /* AWSCognitoAuthPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSCognitoAuthPlugin; }; - 904D63B0291439890057D06F /* AWSPredictionsPlugin */ = { + 90CF305D2AD47B24006B6FF3 /* AWSPredictionsPlugin */ = { isa = XCSwiftPackageProductDependency; productName = AWSPredictionsPlugin; }; - 904D63B2291439890057D06F /* CoreMLPredictionsPlugin */ = { + 90CF305F2AD47B24006B6FF3 /* CoreMLPredictionsPlugin */ = { isa = XCSwiftPackageProductDependency; productName = CoreMLPredictionsPlugin; }; diff --git a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp/ContentView.swift b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp/ContentView.swift index 3af450c8a8..f5b0b64765 100644 --- a/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp/ContentView.swift +++ b/AmplifyPlugins/Predictions/Tests/PredictionsHostApp/PredictionsHostApp/ContentView.swift @@ -9,8 +9,6 @@ import SwiftUI import Amplify struct ContentView: View { - - var body: some View { VStack { Image(systemName: "globe") @@ -22,70 +20,6 @@ struct ContentView: View { .accessibilityLabel("foobar") .padding() } - - func convert() async throws { - let url = URL(string: "")! - let textToSpeech = try await Amplify.Predictions.convert(.textToSpeech("hello, world!")) - _ = textToSpeech - let speechToText = try await Amplify.Predictions.convert(.speechToText(url: url)) - _ = speechToText - } - - func identify() async throws { - let imageURL = URL(string: "")! - let identifyTextOptions = Predictions.Identify.Options( - defaultNetworkPolicy: .auto, - uploadToRemote: false, - pluginOptions: nil - ) - - let text = try await Amplify.Predictions.identify( - .text, - in: imageURL, - options: identifyTextOptions - ) - - _ = text - let celebrities = try await Amplify.Predictions.identify(.celebrities, in: imageURL) - _ = celebrities - let entities = try await Amplify.Predictions.identify(.entities, in: imageURL) - _ = entities - let entitiesFromCollection = try await Amplify.Predictions.identify( - .entitiesFromCollection(withID: ""), - in: imageURL - ) - _ = entitiesFromCollection - let allLabels = try await Amplify.Predictions.identify( - .labels(type: .all), - in: imageURL - ) - _ = allLabels - let labels = try await Amplify.Predictions.identify( - .labels(type: .labels), - in: imageURL - ) - _ = labels - let moderationLabels = try await Amplify.Predictions.identify( - .labels(type: .moderation), - in: imageURL - ) - _ = moderationLabels - let textFromDocAll = try await Amplify.Predictions.identify( - .textInDocument(textFormatType: .all), - in: imageURL - ) - _ = textFromDocAll - let textFromDocForm = try await Amplify.Predictions.identify( - .textInDocument(textFormatType: .form), - in: imageURL - ) - _ = textFromDocForm - let textFromDocTable = try await Amplify.Predictions.identify( - .textInDocument(textFormatType: .table), - in: imageURL - ) - _ = textFromDocTable - } } struct ContentView_Previews: PreviewProvider { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift index 033af1a2e5..0bd830c3d9 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift @@ -35,6 +35,7 @@ extension AWSS3StoragePlugin { let result = try await storageService.getPreSignedURL( serviceKey: serviceKey, signingOperation: .getObject, + metadata: nil, accelerate: accelerate, expires: options.expires) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Adapter.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Adapter.swift index 0e15b170b7..5fd65c6ec0 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Adapter.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Adapter.swift @@ -18,10 +18,10 @@ import AWSClientRuntime /// and allows for mocking in unit tests. The methods contain no other logic other than calling the /// same method using the AWSS3 instance. class AWSS3Adapter: AWSS3Behavior { - let awsS3: S3Client - let config: S3ClientConfigurationProtocol + let awsS3: S3ClientProtocol + let config: S3Client.S3ClientConfiguration - init(_ awsS3: S3Client, config: S3ClientConfigurationProtocol) { + init(_ awsS3: S3ClientProtocol, config: S3Client.S3ClientConfiguration) { self.awsS3 = awsS3 self.config = config } @@ -66,7 +66,7 @@ class AWSS3Adapter: AWSS3Behavior { } let listResult = StorageListResult(items: items) completion(.success(listResult)) - } catch let error as SdkError { + } catch let error as StorageErrorConvertible { completion(.failure(error.storageError)) } catch { completion(.failure(StorageError(error: error))) @@ -161,7 +161,7 @@ class AWSS3Adapter: AWSS3Behavior { /// Instance of S3 service. /// - Returns: S3 service instance. - func getS3() -> S3Client { + func getS3() -> S3ClientProtocol { return awsS3 } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Behavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Behavior.swift index 19a2b4cdd0..400ca3eb6c 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Behavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3Behavior.swift @@ -26,7 +26,7 @@ protocol AWSS3Behavior { func createMultipartUpload(_ request: CreateMultipartUploadRequest, completion: @escaping (Result) -> Void) // Get list of uploaded parts (supports development) - func listParts(bucket: String, key: String, uploadId: UploadID, completion: @escaping (SdkResult) -> Void) + func listParts(bucket: String, key: String, uploadId: UploadID, completion: @escaping (Result) -> Void) // Completes a Multipart Upload. func completeMultipartUpload(_ request: AWSS3CompleteMultipartUploadRequest, completion: @escaping (Result) -> Void) @@ -35,12 +35,12 @@ protocol AWSS3Behavior { func abortMultipartUpload(_ request: AWSS3AbortMultipartUploadRequest, completion: @escaping (Result) -> Void) // Gets a client for AWS S3 Service. - func getS3() -> S3Client + func getS3() -> S3ClientProtocol } extension AWSS3Behavior { - func listParts(bucket: String, key: String, uploadId: UploadID, completion: @escaping (SdkResult) -> Void) { + func listParts(bucket: String, key: String, uploadId: UploadID, completion: @escaping (Result) -> Void) { // do nothing } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift index 0ff4a5db4b..b935df2dd0 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift @@ -19,7 +19,7 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior { let defaultExpiration: Int64 = 50 * 60 // 50 minutes let bucket: String - let config: S3ClientConfigurationProtocol + let config: S3Client.S3ClientConfiguration let logger: Logger /// Creates a pre-signed URL builder. @@ -34,13 +34,12 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior { /// - Returns: Pre-Signed URL func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, + metadata: [String: String]? = nil, accelerate: Bool? = nil, expires: Int64? = nil) async throws -> URL { let expiresDate = Date(timeIntervalSinceNow: Double(expires ?? defaultExpiration)) let expiration = expiresDate.timeIntervalSinceNow - let config = (accelerate == nil) ? self.config : S3ClientConfigurationProxy( - target: self.config, - accelerateOverride: accelerate) + let config = try config.withAccelerate(accelerate) let preSignedUrl: URL? switch signingOperation { case .getObject: @@ -49,7 +48,7 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior { config: config, expiration: expiration) case .putObject: - let input = PutObjectInput(bucket: bucket, key: key) + let input = PutObjectInput(bucket: bucket, key: key, metadata: metadata) preSignedUrl = try await input.presignURL( config: config, expiration: expiration) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift index dddc19ed44..7ca3f989cb 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift @@ -41,6 +41,7 @@ protocol AWSS3PreSignedURLBuilderBehavior { /// - Tag: AWSS3PreSignedURLBuilderBehavior.getPreSignedURL func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, + metadata: [String: String]?, accelerate: Bool?, expires: Int64?) async throws -> URL diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfiguration+withAccelerate.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfiguration+withAccelerate.swift new file mode 100644 index 0000000000..bd3cac4d8b --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfiguration+withAccelerate.swift @@ -0,0 +1,53 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import AWSS3 + +extension S3Client.S3ClientConfiguration { + func withAccelerate(_ shouldAccelerate: Bool?) throws -> S3Client.S3ClientConfiguration { + // if `shouldAccelerate` is `nil`, this is a noop - return self + guard let shouldAccelerate else { + return self + } + + // if `shouldAccelerate` isn't `nil` and + // is equal to the exisiting config's `serviceSpecific.accelerate + // we can avoid allocating a new configuration object. + if shouldAccelerate == serviceSpecific.accelerate { + return self + } + + // This shouldn't happen based on how we're initially + // creating the configuration, but we can't reasonably prove + // it at compile time - so we have to unwrap. + guard let region else { return self } + + // `S3Client.ServiceSpecificConfiguration` is a struct + // so we're copying by value here. + var serviceSpecific = serviceSpecific + serviceSpecific.accelerate = shouldAccelerate + + // `S3Client.S3ClientConfiguration` is a `class` so we need to make + // a deep copy here as not to change the value of the existing base + // configuration. + let copy = try S3Client.S3ClientConfiguration( + region: region, + credentialsProvider: credentialsProvider, + endpoint: endpoint, + serviceSpecific: serviceSpecific, + signingRegion: signingRegion, + useDualStack: useDualStack, + useFIPS: useFIPS, + retryMode: awsRetryMode, + appID: appID, + connectTimeoutMs: connectTimeoutMs + ) + + return copy + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProtocol+Endpoint.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProtocol+Endpoint.swift index 892446c243..5eac989093 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProtocol+Endpoint.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProtocol+Endpoint.swift @@ -6,20 +6,20 @@ import Foundation import AWSS3 -extension S3ClientConfigurationProtocol { - +extension S3Client.S3ClientConfiguration { func endpointParams(withBucket bucket: String?) -> EndpointParams { EndpointParams( - accelerate: accelerate ?? false, + accelerate: serviceSpecific.accelerate ?? false, bucket: bucket, - disableMultiRegionAccessPoints: disableMultiRegionAccessPoints ?? false, + disableMultiRegionAccessPoints: serviceSpecific.disableMultiRegionAccessPoints ?? false, endpoint: endpoint, - forcePathStyle: forcePathStyle, + forcePathStyle: serviceSpecific.forcePathStyle ?? false, region: region, - useArnRegion: useArnRegion, + useArnRegion: serviceSpecific.useArnRegion, useDualStack: useDualStack ?? false, useFIPS: useFIPS ?? false, - useGlobalEndpoint: useGlobalEndpoint ?? false) + useGlobalEndpoint: serviceSpecific.useGlobalEndpoint ?? false + ) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift deleted file mode 100644 index 6a6ebb04d3..0000000000 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 - -import AWSS3 -import AWSClientRuntime -import ClientRuntime -import Foundation - -/// Convenience proxy class around a -/// [S3ClientConfigurationProtocol](x-source-tag://S3ClientConfigurationProtocol) -/// implementaitons that allows Amplify to change configuration values JIT. -/// -/// - Tag: S3ClientConfigurationProxy -struct S3ClientConfigurationProxy { - - /// - Tag: S3ClientConfigurationProxy.target - var target: S3ClientConfigurationProtocol - - /// - Tag: S3ClientConfigurationProxy.accelerateOverride - var accelerateOverride: Bool? -} - -extension S3ClientConfigurationProxy: S3ClientConfigurationProtocol { - - var accelerate: Bool? { - if let accelerateOverride = accelerateOverride { - return accelerateOverride - } - return target.accelerate - } - - var disableMultiRegionAccessPoints: Bool? { - return target.disableMultiRegionAccessPoints - } - - var endpointResolver: EndpointResolver { - return target.endpointResolver - } - - var forcePathStyle: Bool? { - return target.forcePathStyle - } - - var useArnRegion: Bool? { - return target.useArnRegion - } - - var useGlobalEndpoint: Bool? { - return target.useGlobalEndpoint - } - - var credentialsProvider: AWSClientRuntime.CredentialsProvider { - get { - return target.credentialsProvider - } - set(newValue) { - target.credentialsProvider = newValue - } - } - - var region: String? { - get { - return target.region - } - set(newValue) { - target.region = newValue - } - } - - var signingRegion: String? { - get { - return target.signingRegion - } - set(newValue) { - target.signingRegion = newValue - } - } - - var regionResolver: RegionResolver? { - get { - return target.regionResolver - } - set(newValue) { - target.regionResolver = newValue - } - } - - var frameworkMetadata: FrameworkMetadata? { - get { - return target.frameworkMetadata - } - set(newValue) { - target.frameworkMetadata = newValue - } - } - - var useFIPS: Bool? { - get { - return target.useFIPS - } - set(newValue) { - target.useFIPS = newValue - } - } - - var useDualStack: Bool? { - get { - return target.useDualStack - } - set(newValue) { - target.useDualStack = newValue - } - } - - var logger: LogAgent { - return target.logger - } - - var retryer: ClientRuntime.SDKRetryer { - return target.retryer - } - - var endpoint: String? { - get { - return target.endpoint - } - set(newValue) { - target.endpoint = newValue - } - } - - var encoder: ClientRuntime.RequestEncoder? { - return target.encoder - } - - var decoder: ClientRuntime.ResponseDecoder? { - return target.decoder - } - - var httpClientEngine: ClientRuntime.HttpClientEngine { - return target.httpClientEngine - } - - var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { - return target.httpClientConfiguration - } - - var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator { - return target.idempotencyTokenGenerator - } - - var clientLogMode: ClientRuntime.ClientLogMode { - return target.clientLogMode - } - - var partitionID: String? { - return target.partitionID - } -} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/SdkTypealiases.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/SdkTypealiases.swift index 64d76e908f..d0cdf91669 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/SdkTypealiases.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/SdkTypealiases.swift @@ -9,8 +9,5 @@ import Foundation import AWSClientRuntime import ClientRuntime -/// - Tag: SdkResult -public typealias SdkResult = Result> - /// - Tag: NetworkResult public typealias NetworkResult = (Result) -> Void diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift index 83f4958c77..38af518a9c 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift @@ -10,7 +10,7 @@ import ClientRuntime import AWSClientRuntime extension UploadPartInput { - func customPresignURL(config: S3ClientConfigurationProtocol, expiration: TimeInterval) async throws -> ClientRuntime.URL? { + func customPresignURL(config: S3Client.S3ClientConfiguration, expiration: TimeInterval) async throws -> ClientRuntime.URL? { let serviceName = "S3" let input = self let encoder = ClientRuntime.XMLEncoder() @@ -34,10 +34,10 @@ extension UploadPartInput { var operation = ClientRuntime.OperationStack(id: "uploadPart") operation.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware()) operation.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware()) - operation.buildStep.intercept(position: .before, middleware: EndpointResolverMiddleware(endpointResolver: config.endpointResolver, endpointParams: config.endpointParams(withBucket: input.bucket))) + operation.buildStep.intercept(position: .before, middleware: EndpointResolverMiddleware(endpointResolver: config.serviceSpecific.endpointResolver, endpointParams: config.endpointParams(withBucket: input.bucket))) operation.serializeStep.intercept(position: .after, middleware: UploadPartInputBodyMiddleware()) operation.serializeStep.intercept(position: .after, middleware: QueryItemMiddleware()) - operation.finalizeStep.intercept(position: .after, middleware: RetryerMiddleware(retryer: config.retryer)) + operation.finalizeStep.intercept(position: .after, middleware: ClientRuntime.RetryMiddleware(options: config.retryStrategyOptions)) let sigv4Config = AWSClientRuntime.SigV4Config( signatureType: .requestQueryParams, useDoubleURIEncode: false, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/AWSS3+StorageErrorConvertible.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/AWSS3+StorageErrorConvertible.swift new file mode 100644 index 0000000000..7b4a08ab76 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/AWSS3+StorageErrorConvertible.swift @@ -0,0 +1,52 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify +import AWSS3 +import AWSClientRuntime + +extension AWSS3.NoSuchBucket: StorageErrorConvertible { + var storageError: StorageError { + .service( + "The specific bucket does not exist", + "", + self + ) + } +} + +extension AWSClientRuntime.UnknownAWSHTTPServiceError: StorageErrorConvertible { + var storageError: StorageError { + let error: StorageError + switch httpResponse.statusCode { + case .unauthorized, .forbidden: + error = .accessDenied( + StorageErrorConstants.accessDenied.errorDescription, + StorageErrorConstants.accessDenied.recoverySuggestion, + self + ) + case .notFound: + error = .keyNotFound( + StorageError.serviceKey, + "Received HTTP Response status code 404 NotFound", + "Make sure the key exists before trying to download it.", + self + ) + default: + error = .unknown( + """ + Unknown service error occured with: + - status: \(httpResponse.statusCode) + - message: \(message ?? "") + """, + self + ) + } + return error + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/StorageErrorConvertible.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/StorageErrorConvertible.swift new file mode 100644 index 0000000000..9f9e55c5a6 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Error/StorageErrorConvertible.swift @@ -0,0 +1,17 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +protocol StorageErrorConvertible { + var storageError: StorageError { get } +} + +extension StorageError: StorageErrorConvertible { + var storageError: StorageError { self } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift index 48e428c425..cbfd7ee52e 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift @@ -91,22 +91,25 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload(serviceKey: serviceKey, - uploadSource: .data(request.data), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.multiPartUpload( + serviceKey: serviceKey, + uploadSource: .data(request.data), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } else { - storageService.upload(serviceKey: serviceKey, - uploadSource: .data(request.data), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.upload( + serviceKey: serviceKey, + uploadSource: .data(request.data), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift index fce5fdd94d..617602388a 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift @@ -115,22 +115,25 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload(serviceKey: serviceKey, - uploadSource: .local(request.local), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.multiPartUpload( + serviceKey: serviceKey, + uploadSource: .local(request.local), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } else { - storageService.upload(serviceKey: serviceKey, - uploadSource: .local(request.local), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.upload( + serviceKey: serviceKey, + uploadSource: .local(request.local), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..74f8af8564 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,8 @@ + + + + + NSPrivacyAccessedAPITypes + + + diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift index c4893c86c9..ab7c5849de 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift @@ -30,6 +30,7 @@ extension AWSS3StorageService { do { let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .getObject, + metadata: nil, accelerate: accelerate, expires: nil) startDownload(preSignedURL: preSignedURL, transferTask: transferTask) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift index 1716891ab1..fc3eb40699 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift @@ -14,13 +14,16 @@ extension AWSS3StorageService { func getPreSignedURL(serviceKey: String, signingOperation: AWSS3SigningOperation, + metadata: [String: String]?, accelerate: Bool?, expires: Int) async throws -> URL { return try await preSignedURLBuilder.getPreSignedURL( key: serviceKey, signingOperation: signingOperation, - accelerate: accelerate, - expires: Int64(expires)) + metadata: metadata, + accelerate: nil, + expires: Int64(expires) + ) } func validateObjectExistence(serviceKey: String) async throws { @@ -29,49 +32,20 @@ extension AWSS3StorageService { bucket: self.bucket, key: serviceKey )) - } catch let error as HeadObjectOutputError { - // Because the AWS SDK may wrap the HeadObjectOutputError in an - // SdkError, it is necessary to do some more - // complex error pattern matching. - throw Self.validateObjectExistenceMap(headObjectOutputError: error, serviceKey: serviceKey) - } catch let error as SdkError { - throw Self.validateObjectExistenceMap(sdkError: error, serviceKey: serviceKey) - } - } - - private static func validateObjectExistenceMap(sdkError: SdkError, serviceKey: String) -> StorageError { - switch sdkError { - case .service(let serviceError, _): - return validateObjectExistenceMap(headObjectOutputError: serviceError, serviceKey: serviceKey) - case .client(let clientError, _): - switch clientError { - case .retryError(let error as HeadObjectOutputError): - return validateObjectExistenceMap(headObjectOutputError: error, serviceKey: serviceKey) - case .retryError(let error as SdkError): - return validateObjectExistenceMap(sdkError: error, serviceKey: serviceKey) - default: - return validateObjectExistenceMap(unexpectedError: clientError, serviceKey: serviceKey) - } - case .unknown(let error): - return validateObjectExistenceMap(unexpectedError: error, serviceKey: serviceKey) - } - } - - private static func validateObjectExistenceMap(headObjectOutputError: HeadObjectOutputError, serviceKey: String) -> StorageError { - switch headObjectOutputError { - case .notFound: - return StorageError.keyNotFound( + } catch is AWSS3.NotFound { + throw StorageError.keyNotFound( serviceKey, "Unable to generate URL for non-existent key: \(serviceKey)", "Please ensure the key is valid or the object has been uploaded", nil ) - default: - return validateObjectExistenceMap(unexpectedError: headObjectOutputError, serviceKey: serviceKey) + } catch let error as StorageErrorConvertible { + throw error.storageError + } catch { + throw StorageError.unknown( + "Unable to get object information for \(serviceKey)", + error + ) } } - - private static func validateObjectExistenceMap(unexpectedError: Error?, serviceKey: String) -> StorageError { - return StorageError.unknown("Unable to get object information for \(serviceKey)", unexpectedError) - } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+ListBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+ListBehavior.swift index 186f70d330..817822f346 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+ListBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+ListBehavior.swift @@ -42,11 +42,10 @@ extension AWSS3StorageService { try StorageListResult.Item(s3Object: $0, prefix: prefix) } return StorageListResult(items: items, nextToken: response.nextContinuationToken) - } catch let error as SdkError { + } catch let error as StorageErrorConvertible { throw error.storageError } catch { throw StorageError(error: error) } } - } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift index 77d7e4efb7..41ee946407 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift @@ -32,7 +32,8 @@ extension AWSS3StorageService { let client = DefaultStorageMultipartUploadClient(serviceProxy: self, bucket: bucket, key: serviceKey, - uploadFile: uploadFile) + uploadFile: uploadFile, + metadata: metadata) let multipartUploadSession = StorageMultipartUploadSession(client: client, bucket: bucket, key: serviceKey, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift index 7bff9e94ba..5349e4a6be 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift @@ -36,6 +36,7 @@ extension AWSS3StorageService { do { let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .putObject, + metadata: metadata, accelerate: accelerate, expires: nil) startUpload(preSignedURL: preSignedURL, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift index 58329ddb84..d31b9e588e 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift @@ -6,11 +6,10 @@ // import Foundation - import AWSS3 import Amplify import AWSPluginsCore -@_spi(FoundationClientEngine) import AWSPluginsCore +@_spi(PluginHTTPClientEngine) import AWSPluginsCore import ClientRuntime /// - Tag: AWSS3StorageService @@ -55,33 +54,24 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { httpClientEngineProxy: HttpClientEngineProxy? = nil, storageConfiguration: StorageConfiguration = .default, storageTransferDatabase: StorageTransferDatabase = .default, + fileSystem: FileSystem = .default, sessionConfiguration: URLSessionConfiguration? = nil, delegateQueue: OperationQueue? = nil, logger: Logger = storageLogger) throws { let credentialsProvider = authService.getCredentialsProvider() let clientConfig = try S3Client.S3ClientConfiguration( - credentialsProvider: credentialsProvider, region: region, - signingRegion: region) - - if var proxy = httpClientEngineProxy { - let httpClientEngine: HttpClientEngine - #if os(iOS) || os(macOS) - httpClientEngine = clientConfig.httpClientEngine - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - httpClientEngine = FoundationClientEngine() - #endif - proxy.target = httpClientEngine - clientConfig.httpClientEngine = proxy + credentialsProvider: credentialsProvider, + signingRegion: region + ) + + if var httpClientEngineProxy = httpClientEngineProxy { + httpClientEngineProxy.target = baseClientEngine(for: clientConfig) + clientConfig.httpClientEngine = UserAgentSettingClientEngine( + target: httpClientEngineProxy + ) } else { - #if os(iOS) || os(macOS) // no-op - #else - // For any platform except iOS or macOS - // Use Foundation instead of CRT for networking. - clientConfig.httpClientEngine = FoundationClientEngine() - #endif + clientConfig.httpClientEngine = .userAgentEngine(for: clientConfig) } let s3Client = S3Client(config: clientConfig) @@ -108,7 +98,9 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { self.init(authService: authService, storageConfiguration: storageConfiguration, storageTransferDatabase: storageTransferDatabase, + fileSystem: fileSystem, sessionConfiguration: _sessionConfiguration, + logger: logger, s3Client: s3Client, preSignedURLBuilder: preSignedURLBuilder, awsS3: awsS3, @@ -141,7 +133,7 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { self.preSignedURLBuilder = preSignedURLBuilder self.awsS3 = awsS3 self.bucket = bucket - self.userAgent = AmplifyAWSServiceConfiguration.frameworkMetaData(includeOS: true).description + self.userAgent = "\(AmplifyAWSServiceConfiguration.userAgentLib) \(AmplifyAWSServiceConfiguration.userAgentOS)" StorageBackgroundEventsRegistry.register(identifier: identifier) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift index 8226df07ff..21ae5df171 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift @@ -44,6 +44,7 @@ protocol AWSS3StorageServiceBehavior { func getPreSignedURL(serviceKey: String, signingOperation: AWSS3SigningOperation, + metadata: [String: String]?, accelerate: Bool?, expires: Int) async throws -> URL diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift index 7bedf97def..7884e13482 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift @@ -42,14 +42,17 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { let contentType: String? let requestHeaders: RequestHeaders? weak var session: StorageMultipartUploadSession? - + let metadata: [String: String]? + init(serviceProxy: StorageServiceProxy, fileSystem: FileSystem = .default, bucket: String, key: String, uploadFile: UploadFile, contentType: String? = nil, - requestHeaders: RequestHeaders? = nil) { + requestHeaders: RequestHeaders? = nil, + metadata: [String: String]? = nil + ) { self.serviceProxy = serviceProxy self.fileSystem = fileSystem self.bucket = bucket @@ -57,6 +60,7 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { self.uploadFile = uploadFile self.contentType = contentType self.requestHeaders = requestHeaders + self.metadata = metadata } func integrate(session: StorageMultipartUploadSession) { @@ -71,7 +75,19 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { // The AWS S3 SDK handles the request so there will be not taskIdentifier session.handle(multipartUploadEvent: .creating) - let request = CreateMultipartUploadRequest(bucket: bucket, key: key) + // User-defined metadata needs to provided + // when initiating the MPU. + // -- + // https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-process + // > Multipart upload initiation + // "If you want to provide any metadata describing the object + // being uploaded, you must provide it in the request to initiate + // multipart upload." + let request = CreateMultipartUploadRequest( + bucket: bucket, + key: key, + metadata: metadata + ) serviceProxy.awsS3.createMultipartUpload(request) { [weak self] result in guard let self = self else { return } switch result { @@ -135,6 +151,9 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { let preSignedURL = try await serviceProxy.preSignedURLBuilder.getPreSignedURL( key: self.key, signingOperation: operation, + // user-controlled metadata should *not* be provided + // with each upload part. + metadata: nil, accelerate: nil, expires: nil ) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift index d335f31805..fc51016cb9 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift @@ -44,14 +44,6 @@ class StorageMultipartUploadSession { private let transferTask: StorageTransferTask - private var contentType: String? { - transferTask.contentType - } - - private var requestHeaders: RequestHeaders? { - transferTask.requestHeaders - } - init(client: StorageMultipartUploadClient, bucket: String, key: String, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageTransferTask.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageTransferTask.swift index 1cd7e95385..1ecf8894db 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageTransferTask.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageTransferTask.swift @@ -173,10 +173,6 @@ class StorageTransferTask { } } - private var cancelled: Bool { - status == .cancelled - } - var isFailed: Bool { status == .error } @@ -324,7 +320,7 @@ class StorageTransferTask { logger.warn("Unable to complete after cancelled") return } - guard _status == .completed else { + guard _status != .completed else { logger.warn("Task is already completed") return } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/SdkError+Properties.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/SdkError+Properties.swift index 6dbbb395aa..b4b240df18 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/SdkError+Properties.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/SdkError+Properties.swift @@ -6,95 +6,10 @@ // import Foundation - import Amplify -import AWSS3 -import ClientRuntime -import AWSClientRuntime extension StorageError { static var serviceKey: String { "s3" } } - -extension SdkError { - var httpResponse: HttpResponse? { - switch self { - case .service(_, let response): - return response - case .client(_, let response): - return response - default: - return nil - } - } - - var statusCode: HttpStatusCode? { - if let statusCode = httpResponse?.statusCode { - return statusCode - } - - guard case let .retryError(error) = clientError, - let sdkError = error as? Self else { - return nil - } - - return sdkError.statusCode - } - - var clientError: ClientError? { - switch self { - case .client(let clientError, _): - return clientError - default: - return nil - } - } - - var unknownError: Error? { - switch self { - case .unknown(let error): - return error - default: - return nil - } - } - - func isOk(statusCode: Int) -> Bool { - (200..<299).contains(statusCode) - } - - func isAccessDenied(statusCode: Int) -> Bool { - [401, 403].contains(statusCode) - } - - func isNotFound(statusCode: Int) -> Bool { - 404 == statusCode - } - - var storageError: StorageError { - let storageError: StorageError - if let statusCode = statusCode?.rawValue, - !isOk(statusCode: statusCode) { - if isAccessDenied(statusCode: statusCode) { - storageError = StorageError.accessDenied(StorageErrorConstants.accessDenied.errorDescription, - StorageErrorConstants.accessDenied.recoverySuggestion, - self) - } else if isNotFound(statusCode: statusCode) { - storageError = StorageError.keyNotFound(StorageError.serviceKey, - "Received HTTP Response status code 404 NotFound", - "Make sure the key exists before trying to download it.") - } else { - storageError = StorageError.httpStatusError(statusCode, localizedDescription, self) - } - } else if let clientError = clientError { - storageError = StorageError.unknown(clientError.localizedDescription, clientError) - } else { - storageError = StorageError.unknown(localizedDescription, self) - } - - return storageError - } - -} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift index e37a03c4c0..ac610b39fb 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift @@ -25,19 +25,6 @@ extension StorageRequestUtils { return accessLevel.serviceAccessPrefix + "/" } - static func getServiceMetadata(_ metadata: [String: String]?) -> [String: String]? { - guard let metadata = metadata else { - return nil - } - var serviceMetadata: [String: String] = [:] - for (key, value) in metadata { - let serviceKey = metadataKeyPrefix + key - serviceMetadata[serviceKey] = value - } - - return serviceMetadata - } - static func getSize(_ file: URL) throws -> UInt64 { if let error = validateFileExists(file) { throw StorageError.localFileNotFound(error.errorDescription, error.recoverySuggestion) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift index 49a5734754..4dcf9c0108 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift @@ -65,7 +65,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { ]) let expectedServiceKey = "public/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject 18000" + "getPreSignedURL(serviceKey:signingOperation:metadata:accelerate:expires:) \(expectedServiceKey) getObject nil 18000" ]) } @@ -120,7 +120,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:metadata:accelerate:expires:) \(expectedServiceKey) getObject nil \(expectedExpires)" ]) } @@ -152,7 +152,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:metadata:accelerate:expires:) \(expectedServiceKey) getObject nil \(expectedExpires)" ]) } @@ -173,7 +173,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedExpires = 18000 let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:metadata:accelerate:expires:) \(expectedServiceKey) getObject nil \(expectedExpires)" ]) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/AWSS3PluginPrefixResolverTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/AWSS3PluginPrefixResolverTests.swift index 8533307117..5880cf935a 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/AWSS3PluginPrefixResolverTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/AWSS3PluginPrefixResolverTests.swift @@ -58,14 +58,15 @@ class AWSS3PluginPrefixResolverTests: XCTestCase { .init(.private, "identityId", ""), ] - let done = asyncExpectation(description: "done", expectedFulfillmentCount: testData.count) + let done = expectation(description: "done") + done.expectedFulfillmentCount = testData.count Task { try await testData.async.forEach { try await $0.assertEqual(prefixResolver: prefixResolver) - await done.fulfill() + done.fulfill() } } - await waitForExpectations([done]) + await fulfillment(of: [done]) } func testStorageAccessLevelAwarePrefixResolver() async throws { @@ -82,14 +83,16 @@ class AWSS3PluginPrefixResolverTests: XCTestCase { .init(.private, "targetUserId", "private/targetUserId/"), ] - let done = asyncExpectation(description: "done", expectedFulfillmentCount: testData.count) + let done = expectation(description: "done") + done.expectedFulfillmentCount = testData.count + Task { try await testData.async.forEach { try await $0.assertEqual(prefixResolver: prefixResolver) - await done.fulfill() + done.fulfill() } } - await waitForExpectations([done]) + await fulfillment(of: [done]) } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift index 19a38fb062..77d1ec3b3d 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift @@ -13,67 +13,75 @@ import XCTest @testable import AWSS3StoragePlugin -final class S3ClientConfigurationProxyTests: XCTestCase { +final class S3ClientConfigurationAccelerateTestCase: XCTestCase { - /// Given: A client configuration that has a value for a property such as `accelerate`. - /// When: An override is set on its proxy configuration. - /// Then: The proxy returns the value from the override. + /// Given: A base configuration that has a value for a property such as `accelerate`. + /// When: An override is set through `withAccelerate(_:)` + /// Then: The base configuration is not mutated. func testPropertyOverrides() async throws { - let target = try await S3Client.S3ClientConfiguration() - target.accelerate = true - - let sut = S3ClientConfigurationProxy(target: target, accelerateOverride: false) - XCTAssertEqual(sut.accelerate, false) - XCTAssertEqual(target.accelerate, true) + let baseConfiguration = try await configuration(accelerate: true) + let sut = try baseConfiguration.withAccelerate(false) + XCTAssertEqual(sut.serviceSpecific.accelerate, false) + XCTAssertEqual(baseConfiguration.serviceSpecific.accelerate, true) } - /// Given: A client configuration with random values. - /// When: A proxy configuration around it is created **without overrides**. - /// Then: The values returned by the proxy are equal to those from the **client configuration**. - func testPropertyBypass() async throws { - let target = try await S3Client.S3ClientConfiguration( - accelerate: Bool.random(), + /// Given: A client configuration. + /// When: Calling `withAccelerate` with a `nil` value. + /// Then: The existing and new configurations should share a reference. + func test_copySemantics_nilAccelerate() async throws { + let baseAccelerate = Bool.random() + let baseConfiguration = try await configuration(accelerate: baseAccelerate) + + let nilAccelerate = try baseConfiguration.withAccelerate(nil) + XCTAssert(baseConfiguration === nilAccelerate) + } + + /// Given: A client configuration. + /// When: Calling `withAccelerate` with a non-nil value equal to that of the existing config's. + /// Then: The existing and new configurations should share a reference. + func test_copySemantics_equalAccelerate() async throws { + let baseAccelerate = Bool.random() + let baseConfiguration = try await configuration(accelerate: baseAccelerate) + + let equalAccelerate = try baseConfiguration.withAccelerate(baseAccelerate) + XCTAssert(baseConfiguration === equalAccelerate) + } + + /// Given: A client configuration. + /// When: Calling `withAccelerate` with a non-nil value **not** equal to that of the existing config's. + /// Then: The existing and new configurations should not share a reference. + func test_copySemantics_nonEqualAccelerate() async throws { + let baseAccelerate = Bool.random() + let baseConfiguration = try await configuration(accelerate: baseAccelerate) + + let nonEqualAccelerate = try baseConfiguration.withAccelerate(!baseAccelerate) + XCTAssert(baseConfiguration !== nonEqualAccelerate) + } + + + // Helper configuration method + private func configuration(accelerate: Bool) async throws -> S3Client.S3ClientConfiguration { + var serviceSpecific = try S3Client.ServiceSpecificConfiguration() + serviceSpecific.accelerate = accelerate + serviceSpecific.useArnRegion = .random() + serviceSpecific.useGlobalEndpoint = .random() + serviceSpecific.disableMultiRegionAccessPoints = .random() + serviceSpecific.forcePathStyle = .random() + + let baseConfiguration = try await S3Client.S3ClientConfiguration( credentialsProvider: nil, - disableMultiRegionAccessPoints: Bool.random(), endpoint: UUID().uuidString, - endpointResolver: nil, - forcePathStyle: Bool.random(), - frameworkMetadata: nil, + serviceSpecific: serviceSpecific, + region: "us-east-1", regionResolver: nil, signingRegion: UUID().uuidString, - useArnRegion: Bool.random(), - useDualStack: Bool.random(), - useFIPS: Bool.random(), - useGlobalEndpoint: Bool.random() + useDualStack: .random(), + useFIPS: .random(), + retryMode: .adaptive, + appID: UUID().uuidString, + connectTimeoutMs: .random(in: UInt32.min...UInt32.max) ) - - var sut = S3ClientConfigurationProxy(target: target, accelerateOverride: nil) - XCTAssertEqual(sut.accelerate, target.accelerate) - XCTAssertEqual(sut.disableMultiRegionAccessPoints, target.disableMultiRegionAccessPoints) - XCTAssertEqual(sut.forcePathStyle, target.forcePathStyle) - XCTAssertEqual(sut.useArnRegion, target.useArnRegion) - XCTAssertEqual(sut.useDualStack, target.useDualStack) - XCTAssertEqual(sut.region, target.region) - XCTAssertEqual(sut.signingRegion, target.signingRegion) - XCTAssertEqual(sut.useFIPS, target.useFIPS) - XCTAssertEqual(sut.useGlobalEndpoint, target.useGlobalEndpoint) - XCTAssertEqual(sut.endpoint, target.endpoint) - - sut.region = UUID().uuidString - sut.signingRegion = UUID().uuidString - sut.useFIPS = !(sut.useFIPS ?? false) - sut.useDualStack = !(sut.useDualStack ?? false) - sut.endpoint = UUID().uuidString - XCTAssertEqual(sut.accelerate, target.accelerate) - XCTAssertEqual(sut.disableMultiRegionAccessPoints, target.disableMultiRegionAccessPoints) - XCTAssertEqual(sut.forcePathStyle, target.forcePathStyle) - XCTAssertEqual(sut.useArnRegion, target.useArnRegion) - XCTAssertEqual(sut.useDualStack, target.useDualStack) - XCTAssertEqual(sut.region, target.region) - XCTAssertEqual(sut.signingRegion, target.signingRegion) - XCTAssertEqual(sut.useFIPS, target.useFIPS) - XCTAssertEqual(sut.useGlobalEndpoint, target.useGlobalEndpoint) - XCTAssertEqual(sut.endpoint, target.endpoint) + return baseConfiguration } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Dependency/AWSS3AdapterTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Dependency/AWSS3AdapterTests.swift new file mode 100644 index 0000000000..4cf455e494 --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Dependency/AWSS3AdapterTests.swift @@ -0,0 +1,754 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import AWSS3StoragePlugin +import AWSS3 +import XCTest + +class AWSS3AdapterTests: XCTestCase { + private var adapter: AWSS3Adapter! + private var awsS3: S3ClientMock! + + override func setUp() { + awsS3 = S3ClientMock() + adapter = AWSS3Adapter( + awsS3, + config: try! S3Client.S3ClientConfiguration( + region: "us-east-1" + ) + ) + } + + override func tearDown() { + adapter = nil + awsS3 = nil + } + + /// Given: An AWSS3Adapter + /// When: deleteObject is invoked and the s3 client returns success + /// Then: A .success result is returned + func testDeleteObject_withSuccess_shouldSucceed() { + let deleteExpectation = expectation(description: "Delete Object") + adapter.deleteObject(.init(bucket: "bucket", key: "key")) { result in + XCTAssertEqual(self.awsS3.deleteObjectCount, 1) + guard case .success = result else { + XCTFail("Expected success") + return + } + deleteExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: deleteObject is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testDeleteObject_withError_shouldFail() { + let deleteExpectation = expectation(description: "Delete Object") + awsS3.deleteObjectResult = .failure(StorageError.keyNotFound("InvalidKey", "", "", nil)) + adapter.deleteObject(.init(bucket: "bucket", key: "key")) { result in + XCTAssertEqual(self.awsS3.deleteObjectCount, 1) + guard case .failure(let error) = result, + case .keyNotFound(let key, _, _, _) = error else { + XCTFail("Expected StorageError.keyNotFound") + return + } + XCTAssertEqual(key, "InvalidKey") + deleteExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: listObjectsV2 is invoked and the s3 client returns a list of objects + /// Then: A .success result is returned containing the corresponding list items + func testListObjectsV2_withSuccess_shouldSucceed() { + let listExpectation = expectation(description: "List Objects") + awsS3.listObjectsV2Result = .success(ListObjectsV2OutputResponse( + contents: [ + .init(eTag: "one", key: "prefix/key1", lastModified: .init()), + .init(eTag: "two", key: "prefix/key2", lastModified: .init()) + ] + )) + adapter.listObjectsV2(.init( + bucket: "bucket", + prefix: "prefix/" + )) { result in + XCTAssertEqual(self.awsS3.listObjectsV2Count, 1) + guard case .success(let response) = result else { + XCTFail("Expected success") + return + } + XCTAssertEqual(response.items.count, 2) + XCTAssertTrue(response.items.contains(where: { $0.key == "key1" && $0.eTag == "one" })) + XCTAssertTrue(response.items.contains(where: { $0.key == "key2" && $0.eTag == "two" })) + listExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: listObjectsV2 is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testListObjectsV2_withError_shouldFail() { + let listExpectation = expectation(description: "List Objects") + awsS3.listObjectsV2Result = .failure(StorageError.accessDenied("AccessDenied", "", nil)) + adapter.listObjectsV2(.init( + bucket: "bucket", + prefix: "prefix" + )) { result in + XCTAssertEqual(self.awsS3.listObjectsV2Count, 1) + guard case .failure(let error) = result, + case .accessDenied(let description, _, _) = error else { + XCTFail("Expected StorageError.accessDenied") + return + } + XCTAssertEqual(description, "AccessDenied") + listExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: createMultipartUpload is invoked and the s3 client returns a valid response + /// Then: A .success result is returned containing the corresponding parsed response + func testCreateMultipartUpload_withSuccess_shouldSucceed() { + let createMultipartUploadExpectation = expectation(description: "Create Multipart Upload") + awsS3.createMultipartUploadResult = .success(.init( + bucket: "bucket", + key: "key", + uploadId: "uploadId" + )) + adapter.createMultipartUpload(.init(bucket: "bucket", key: "key")) { result in + XCTAssertEqual(self.awsS3.createMultipartUploadCount, 1) + guard case .success(let response) = result else { + XCTFail("Expected success") + return + } + XCTAssertEqual(response.bucket, "bucket") + XCTAssertEqual(response.key, "key") + XCTAssertEqual(response.uploadId, "uploadId") + createMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: createMultipartUpload is invoked and the s3 client returns an invalid response + /// Then: A .failure result is returned with an .uknown error + func testCreateMultipartUpload_withWrongResponse_shouldFail() { + let createMultipartUploadExpectation = expectation(description: "Create Multipart Upload") + adapter.createMultipartUpload(.init(bucket: "bucket", key: "key")) { result in + XCTAssertEqual(self.awsS3.createMultipartUploadCount, 1) + guard case .failure(let error) = result, + case .unknown(let description, _) = error else { + XCTFail("Expected StorageError.unknown") + return + } + XCTAssertEqual(description, "Invalid response for creating multipart upload") + createMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: createMultipartUpload is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testCreateMultipartUpload_withError_shouldFail() { + let createMultipartUploadExpectation = expectation(description: "Create Multipart Upload") + awsS3.createMultipartUploadResult = .failure(StorageError.accessDenied("AccessDenied", "", nil)) + adapter.createMultipartUpload(.init(bucket: "bucket", key: "key")) { result in + XCTAssertEqual(self.awsS3.createMultipartUploadCount, 1) + guard case .failure(let error) = result, + case .accessDenied(let description, _, _) = error else { + XCTFail("Expected StorageError.accessDenied") + return + } + XCTAssertEqual(description, "AccessDenied") + createMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: listParts is invoked and the s3 client returns a valid response + /// Then: A .success result is returned containing the corresponding parsed response + func testListParts_withSuccess_shouldSucceed() { + let listPartsExpectation = expectation(description: "List Parts") + awsS3.listPartsResult = .success(.init( + bucket: "bucket", + key: "key", + parts: [ + .init(eTag: "eTag1", partNumber: 1), + .init(eTag: "eTag2", partNumber: 2) + ], + uploadId: "uploadId" + )) + adapter.listParts(bucket: "bucket", key: "key", uploadId: "uploadId") { result in + XCTAssertEqual(self.awsS3.listPartsCount, 1) + guard case .success(let response) = result else { + XCTFail("Expected success") + return + } + XCTAssertEqual(response.bucket, "bucket") + XCTAssertEqual(response.key, "key") + XCTAssertEqual(response.uploadId, "uploadId") + XCTAssertEqual(response.parts.count, 2) + listPartsExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: listParts is invoked and the s3 client returns an invalid response + /// Then: A .failure result is returned with an .unknown error + func testListParts_withWrongResponse_shouldFail() { + let listPartsExpectation = expectation(description: "List Parts") + adapter.listParts(bucket: "bucket", key: "key", uploadId: "uploadId") { result in + XCTAssertEqual(self.awsS3.listPartsCount, 1) + guard case .failure(let error) = result, + case .unknown(let description, _) = error else { + XCTFail("Expected StorageError.unknown") + return + } + XCTAssertEqual(description, "ListParts response is invalid") + listPartsExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: listParts is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testListParts_withError_shouldFail() { + let listPartsExpectation = expectation(description: "List Parts") + awsS3.listPartsResult = .failure(StorageError.authError("AuthError", "", nil)) + adapter.listParts(bucket: "bucket", key: "key", uploadId: "uploadId") { result in + XCTAssertEqual(self.awsS3.listPartsCount, 1) + guard case .failure(let error) = result, + case .authError(let description, _, _) = error else { + XCTFail("Expected StorageError.authError") + return + } + XCTAssertEqual(description, "AuthError") + listPartsExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: completeMultipartUpload is invoked and the s3 client returns a valid response + /// Then: A .success result is returned containing the corresponding parsed response + func testCompleteMultipartUpload_withSuccess_shouldSucceed() { + let completeMultipartUploadExpectation = expectation(description: "Complete Multipart Upload") + awsS3.completeMultipartUploadResult = .success(.init( + eTag: "eTag" + )) + adapter.completeMultipartUpload(.init( + bucket: "bucket", + key: "key", + uploadId: "uploadId", + parts: [.init(partNumber: 1, eTag: "eTag1"), .init(partNumber: 2, eTag: "eTag2")] + )) { result in + XCTAssertEqual(self.awsS3.completeMultipartUploadCount, 1) + guard case .success(let response) = result else { + XCTFail("Expected success") + return + } + XCTAssertEqual(response.bucket, "bucket") + XCTAssertEqual(response.key, "key") + XCTAssertEqual(response.eTag, "eTag") + completeMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: completeMultipartUpload is invoked and the s3 client returns an invalid response + /// Then: A .failure result is returned with .unknown error + func testCompleteMultipartUpload_withWrongResponse_shouldFail() { + let completeMultipartUploadExpectation = expectation(description: "Complete Multipart Upload") + adapter.completeMultipartUpload(.init(bucket: "bucket", key: "key", uploadId: "uploadId", parts: [])) { result in + XCTAssertEqual(self.awsS3.completeMultipartUploadCount, 1) + guard case .failure(let error) = result, + case .unknown(let description, _) = error else { + XCTFail("Expected StorageError.unknown") + return + } + XCTAssertEqual(description, "Invalid response for completing multipart upload") + completeMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: completeMultipartUpload is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testCompleteMultipartUpload_withError_shouldFail() { + let completeMultipartUploadExpectation = expectation(description: "Complete Multipart Upload") + awsS3.completeMultipartUploadResult = .failure(StorageError.authError("AuthError", "", nil)) + adapter.completeMultipartUpload(.init(bucket: "bucket", key: "key", uploadId: "uploadId", parts: [])) { result in + XCTAssertEqual(self.awsS3.completeMultipartUploadCount, 1) + guard case .failure(let error) = result, + case .authError(let description, _, _) = error else { + XCTFail("Expected StorageError.authError") + return + } + XCTAssertEqual(description, "AuthError") + completeMultipartUploadExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: abortMultipartUpload is invoked and the s3 client returns a valid response + /// Then: A .success result is returned + func testAbortMultipartUpload_withSuccess_shouldSucceed() { + let abortExpectation = expectation(description: "Abort Multipart Upload") + adapter.abortMultipartUpload(.init(bucket: "bucket", key: "key", uploadId: "uploadId")) { result in + XCTAssertEqual(self.awsS3.abortMultipartUploadCount, 1) + guard case .success = result else { + XCTFail("Expected success") + return + } + abortExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: abortMultipartUpload is invoked and the s3 client returns an error + /// Then: A .failure result is returned + func testAbortMultipartUpload_withError_shouldFail() { + let abortExpectation = expectation(description: "Abort Multipart Upload") + awsS3.abortMultipartUploadResult = .failure(StorageError.keyNotFound("InvalidKey", "", "", nil)) + adapter.abortMultipartUpload(.init(bucket: "bucket", key: "key", uploadId: "uploadId")) { result in + XCTAssertEqual(self.awsS3.abortMultipartUploadCount, 1) + guard case .failure(let error) = result, + case .keyNotFound(let key, _, _, _) = error else { + XCTFail("Expected StorageError.keyNotFound") + return + } + XCTAssertEqual(key, "InvalidKey") + abortExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3Adapter + /// When: getS3 is invoked + /// Then: The underlying S3ClientProtocol instance is returned + func testGetS3() { + XCTAssertTrue(adapter.getS3() is S3ClientMock) + } +} + +private class S3ClientMock: S3ClientProtocol { + var deleteObjectCount = 0 + var deleteObjectResult: Result = .success(.init()) + func deleteObject(input: AWSS3.DeleteObjectInput) async throws -> AWSS3.DeleteObjectOutputResponse { + deleteObjectCount += 1 + return try deleteObjectResult.get() + } + + var listObjectsV2Count = 0 + var listObjectsV2Result: Result = .success(.init()) + func listObjectsV2(input: AWSS3.ListObjectsV2Input) async throws -> AWSS3.ListObjectsV2OutputResponse { + listObjectsV2Count += 1 + return try listObjectsV2Result.get() + } + + var createMultipartUploadCount = 0 + var createMultipartUploadResult: Result = .success(.init()) + func createMultipartUpload(input: AWSS3.CreateMultipartUploadInput) async throws -> AWSS3.CreateMultipartUploadOutputResponse { + createMultipartUploadCount += 1 + return try createMultipartUploadResult.get() + } + + var listPartsCount = 0 + var listPartsResult: Result = .success(.init()) + func listParts(input: AWSS3.ListPartsInput) async throws -> AWSS3.ListPartsOutputResponse { + listPartsCount += 1 + return try listPartsResult.get() + } + + var completeMultipartUploadCount = 0 + var completeMultipartUploadResult: Result = .success(.init()) + func completeMultipartUpload(input: AWSS3.CompleteMultipartUploadInput) async throws -> AWSS3.CompleteMultipartUploadOutputResponse { + completeMultipartUploadCount += 1 + return try completeMultipartUploadResult.get() + } + + var abortMultipartUploadCount = 0 + var abortMultipartUploadResult: Result = .success(.init()) + func abortMultipartUpload(input: AWSS3.AbortMultipartUploadInput) async throws -> AWSS3.AbortMultipartUploadOutputResponse { + abortMultipartUploadCount += 1 + return try abortMultipartUploadResult.get() + } + + func copyObject(input: AWSS3.CopyObjectInput) async throws -> AWSS3.CopyObjectOutputResponse { + fatalError("Not Implemented") + } + + func createBucket(input: AWSS3.CreateBucketInput) async throws -> AWSS3.CreateBucketOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucket(input: AWSS3.DeleteBucketInput) async throws -> AWSS3.DeleteBucketOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketAnalyticsConfiguration(input: AWSS3.DeleteBucketAnalyticsConfigurationInput) async throws -> AWSS3.DeleteBucketAnalyticsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketCors(input: AWSS3.DeleteBucketCorsInput) async throws -> AWSS3.DeleteBucketCorsOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketEncryption(input: AWSS3.DeleteBucketEncryptionInput) async throws -> AWSS3.DeleteBucketEncryptionOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketIntelligentTieringConfiguration(input: AWSS3.DeleteBucketIntelligentTieringConfigurationInput) async throws -> AWSS3.DeleteBucketIntelligentTieringConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketInventoryConfiguration(input: AWSS3.DeleteBucketInventoryConfigurationInput) async throws -> AWSS3.DeleteBucketInventoryConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketLifecycle(input: AWSS3.DeleteBucketLifecycleInput) async throws -> AWSS3.DeleteBucketLifecycleOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketMetricsConfiguration(input: AWSS3.DeleteBucketMetricsConfigurationInput) async throws -> AWSS3.DeleteBucketMetricsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketOwnershipControls(input: AWSS3.DeleteBucketOwnershipControlsInput) async throws -> AWSS3.DeleteBucketOwnershipControlsOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketPolicy(input: AWSS3.DeleteBucketPolicyInput) async throws -> AWSS3.DeleteBucketPolicyOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketReplication(input: AWSS3.DeleteBucketReplicationInput) async throws -> AWSS3.DeleteBucketReplicationOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketTagging(input: AWSS3.DeleteBucketTaggingInput) async throws -> AWSS3.DeleteBucketTaggingOutputResponse { + fatalError("Not Implemented") + } + + func deleteBucketWebsite(input: AWSS3.DeleteBucketWebsiteInput) async throws -> AWSS3.DeleteBucketWebsiteOutputResponse { + fatalError("Not Implemented") + } + + func deleteObjects(input: AWSS3.DeleteObjectsInput) async throws -> AWSS3.DeleteObjectsOutputResponse { + fatalError("Not Implemented") + } + + func deleteObjectTagging(input: AWSS3.DeleteObjectTaggingInput) async throws -> AWSS3.DeleteObjectTaggingOutputResponse { + fatalError("Not Implemented") + } + + func deletePublicAccessBlock(input: AWSS3.DeletePublicAccessBlockInput) async throws -> AWSS3.DeletePublicAccessBlockOutputResponse { + fatalError("Not Implemented") + } + + func getBucketAccelerateConfiguration(input: AWSS3.GetBucketAccelerateConfigurationInput) async throws -> AWSS3.GetBucketAccelerateConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketAcl(input: AWSS3.GetBucketAclInput) async throws -> AWSS3.GetBucketAclOutputResponse { + fatalError("Not Implemented") + } + + func getBucketAnalyticsConfiguration(input: AWSS3.GetBucketAnalyticsConfigurationInput) async throws -> AWSS3.GetBucketAnalyticsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketCors(input: AWSS3.GetBucketCorsInput) async throws -> AWSS3.GetBucketCorsOutputResponse { + fatalError("Not Implemented") + } + + func getBucketEncryption(input: AWSS3.GetBucketEncryptionInput) async throws -> AWSS3.GetBucketEncryptionOutputResponse { + fatalError("Not Implemented") + } + + func getBucketIntelligentTieringConfiguration(input: AWSS3.GetBucketIntelligentTieringConfigurationInput) async throws -> AWSS3.GetBucketIntelligentTieringConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketInventoryConfiguration(input: AWSS3.GetBucketInventoryConfigurationInput) async throws -> AWSS3.GetBucketInventoryConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketLifecycleConfiguration(input: AWSS3.GetBucketLifecycleConfigurationInput) async throws -> AWSS3.GetBucketLifecycleConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketLocation(input: AWSS3.GetBucketLocationInput) async throws -> AWSS3.GetBucketLocationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketLogging(input: AWSS3.GetBucketLoggingInput) async throws -> AWSS3.GetBucketLoggingOutputResponse { + fatalError("Not Implemented") + } + + func getBucketMetricsConfiguration(input: AWSS3.GetBucketMetricsConfigurationInput) async throws -> AWSS3.GetBucketMetricsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketNotificationConfiguration(input: AWSS3.GetBucketNotificationConfigurationInput) async throws -> AWSS3.GetBucketNotificationConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketOwnershipControls(input: AWSS3.GetBucketOwnershipControlsInput) async throws -> AWSS3.GetBucketOwnershipControlsOutputResponse { + fatalError("Not Implemented") + } + + func getBucketPolicy(input: AWSS3.GetBucketPolicyInput) async throws -> AWSS3.GetBucketPolicyOutputResponse { + fatalError("Not Implemented") + } + + func getBucketPolicyStatus(input: AWSS3.GetBucketPolicyStatusInput) async throws -> AWSS3.GetBucketPolicyStatusOutputResponse { + fatalError("Not Implemented") + } + + func getBucketReplication(input: AWSS3.GetBucketReplicationInput) async throws -> AWSS3.GetBucketReplicationOutputResponse { + fatalError("Not Implemented") + } + + func getBucketRequestPayment(input: AWSS3.GetBucketRequestPaymentInput) async throws -> AWSS3.GetBucketRequestPaymentOutputResponse { + fatalError("Not Implemented") + } + + func getBucketTagging(input: AWSS3.GetBucketTaggingInput) async throws -> AWSS3.GetBucketTaggingOutputResponse { + fatalError("Not Implemented") + } + + func getBucketVersioning(input: AWSS3.GetBucketVersioningInput) async throws -> AWSS3.GetBucketVersioningOutputResponse { + fatalError("Not Implemented") + } + + func getBucketWebsite(input: AWSS3.GetBucketWebsiteInput) async throws -> AWSS3.GetBucketWebsiteOutputResponse { + fatalError("Not Implemented") + } + + func getObject(input: AWSS3.GetObjectInput) async throws -> AWSS3.GetObjectOutputResponse { + fatalError("Not Implemented") + } + + func getObjectAcl(input: AWSS3.GetObjectAclInput) async throws -> AWSS3.GetObjectAclOutputResponse { + fatalError("Not Implemented") + } + + func getObjectAttributes(input: AWSS3.GetObjectAttributesInput) async throws -> AWSS3.GetObjectAttributesOutputResponse { + fatalError("Not Implemented") + } + + func getObjectLegalHold(input: AWSS3.GetObjectLegalHoldInput) async throws -> AWSS3.GetObjectLegalHoldOutputResponse { + fatalError("Not Implemented") + } + + func getObjectLockConfiguration(input: AWSS3.GetObjectLockConfigurationInput) async throws -> AWSS3.GetObjectLockConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func getObjectRetention(input: AWSS3.GetObjectRetentionInput) async throws -> AWSS3.GetObjectRetentionOutputResponse { + fatalError("Not Implemented") + } + + func getObjectTagging(input: AWSS3.GetObjectTaggingInput) async throws -> AWSS3.GetObjectTaggingOutputResponse { + fatalError("Not Implemented") + } + + func getObjectTorrent(input: AWSS3.GetObjectTorrentInput) async throws -> AWSS3.GetObjectTorrentOutputResponse { + fatalError("Not Implemented") + } + + func getPublicAccessBlock(input: AWSS3.GetPublicAccessBlockInput) async throws -> AWSS3.GetPublicAccessBlockOutputResponse { + fatalError("Not Implemented") + } + + func headBucket(input: AWSS3.HeadBucketInput) async throws -> AWSS3.HeadBucketOutputResponse { + fatalError("Not Implemented") + } + + func headObject(input: AWSS3.HeadObjectInput) async throws -> AWSS3.HeadObjectOutputResponse { + fatalError("Not Implemented") + } + + func listBucketAnalyticsConfigurations(input: AWSS3.ListBucketAnalyticsConfigurationsInput) async throws -> AWSS3.ListBucketAnalyticsConfigurationsOutputResponse { + fatalError("Not Implemented") + } + + func listBucketIntelligentTieringConfigurations(input: AWSS3.ListBucketIntelligentTieringConfigurationsInput) async throws -> AWSS3.ListBucketIntelligentTieringConfigurationsOutputResponse { + fatalError("Not Implemented") + } + + func listBucketInventoryConfigurations(input: AWSS3.ListBucketInventoryConfigurationsInput) async throws -> AWSS3.ListBucketInventoryConfigurationsOutputResponse { + fatalError("Not Implemented") + } + + func listBucketMetricsConfigurations(input: AWSS3.ListBucketMetricsConfigurationsInput) async throws -> AWSS3.ListBucketMetricsConfigurationsOutputResponse { + fatalError("Not Implemented") + } + + func listBuckets(input: AWSS3.ListBucketsInput) async throws -> AWSS3.ListBucketsOutputResponse { + fatalError("Not Implemented") + } + + func listMultipartUploads(input: AWSS3.ListMultipartUploadsInput) async throws -> AWSS3.ListMultipartUploadsOutputResponse { + fatalError("Not Implemented") + } + + func listObjects(input: AWSS3.ListObjectsInput) async throws -> AWSS3.ListObjectsOutputResponse { + fatalError("Not Implemented") + } + + func listObjectVersions(input: AWSS3.ListObjectVersionsInput) async throws -> AWSS3.ListObjectVersionsOutputResponse { + fatalError("Not Implemented") + } + + func putBucketAccelerateConfiguration(input: AWSS3.PutBucketAccelerateConfigurationInput) async throws -> AWSS3.PutBucketAccelerateConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketAcl(input: AWSS3.PutBucketAclInput) async throws -> AWSS3.PutBucketAclOutputResponse { + fatalError("Not Implemented") + } + + func putBucketAnalyticsConfiguration(input: AWSS3.PutBucketAnalyticsConfigurationInput) async throws -> AWSS3.PutBucketAnalyticsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketCors(input: AWSS3.PutBucketCorsInput) async throws -> AWSS3.PutBucketCorsOutputResponse { + fatalError("Not Implemented") + } + + func putBucketEncryption(input: AWSS3.PutBucketEncryptionInput) async throws -> AWSS3.PutBucketEncryptionOutputResponse { + fatalError("Not Implemented") + } + + func putBucketIntelligentTieringConfiguration(input: AWSS3.PutBucketIntelligentTieringConfigurationInput) async throws -> AWSS3.PutBucketIntelligentTieringConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketInventoryConfiguration(input: AWSS3.PutBucketInventoryConfigurationInput) async throws -> AWSS3.PutBucketInventoryConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketLifecycleConfiguration(input: AWSS3.PutBucketLifecycleConfigurationInput) async throws -> AWSS3.PutBucketLifecycleConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketLogging(input: AWSS3.PutBucketLoggingInput) async throws -> AWSS3.PutBucketLoggingOutputResponse { + fatalError("Not Implemented") + } + + func putBucketMetricsConfiguration(input: AWSS3.PutBucketMetricsConfigurationInput) async throws -> AWSS3.PutBucketMetricsConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketNotificationConfiguration(input: AWSS3.PutBucketNotificationConfigurationInput) async throws -> AWSS3.PutBucketNotificationConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketOwnershipControls(input: AWSS3.PutBucketOwnershipControlsInput) async throws -> AWSS3.PutBucketOwnershipControlsOutputResponse { + fatalError("Not Implemented") + } + + func putBucketPolicy(input: AWSS3.PutBucketPolicyInput) async throws -> AWSS3.PutBucketPolicyOutputResponse { + fatalError("Not Implemented") + } + + func putBucketReplication(input: AWSS3.PutBucketReplicationInput) async throws -> AWSS3.PutBucketReplicationOutputResponse { + fatalError("Not Implemented") + } + + func putBucketRequestPayment(input: AWSS3.PutBucketRequestPaymentInput) async throws -> AWSS3.PutBucketRequestPaymentOutputResponse { + fatalError("Not Implemented") + } + + func putBucketTagging(input: AWSS3.PutBucketTaggingInput) async throws -> AWSS3.PutBucketTaggingOutputResponse { + fatalError("Not Implemented") + } + + func putBucketVersioning(input: AWSS3.PutBucketVersioningInput) async throws -> AWSS3.PutBucketVersioningOutputResponse { + fatalError("Not Implemented") + } + + func putBucketWebsite(input: AWSS3.PutBucketWebsiteInput) async throws -> AWSS3.PutBucketWebsiteOutputResponse { + fatalError("Not Implemented") + } + + func putObject(input: AWSS3.PutObjectInput) async throws -> AWSS3.PutObjectOutputResponse { + fatalError("Not Implemented") + } + + func putObjectAcl(input: AWSS3.PutObjectAclInput) async throws -> AWSS3.PutObjectAclOutputResponse { + fatalError("Not Implemented") + } + + func putObjectLegalHold(input: AWSS3.PutObjectLegalHoldInput) async throws -> AWSS3.PutObjectLegalHoldOutputResponse { + fatalError("Not Implemented") + } + + func putObjectLockConfiguration(input: AWSS3.PutObjectLockConfigurationInput) async throws -> AWSS3.PutObjectLockConfigurationOutputResponse { + fatalError("Not Implemented") + } + + func putObjectRetention(input: AWSS3.PutObjectRetentionInput) async throws -> AWSS3.PutObjectRetentionOutputResponse { + fatalError("Not Implemented") + } + + func putObjectTagging(input: AWSS3.PutObjectTaggingInput) async throws -> AWSS3.PutObjectTaggingOutputResponse { + fatalError("Not Implemented") + } + + func putPublicAccessBlock(input: AWSS3.PutPublicAccessBlockInput) async throws -> AWSS3.PutPublicAccessBlockOutputResponse { + fatalError("Not Implemented") + } + + func restoreObject(input: AWSS3.RestoreObjectInput) async throws -> AWSS3.RestoreObjectOutputResponse { + fatalError("Not Implemented") + } + + func selectObjectContent(input: AWSS3.SelectObjectContentInput) async throws -> AWSS3.SelectObjectContentOutputResponse { + fatalError("Not Implemented") + } + + func uploadPart(input: AWSS3.UploadPartInput) async throws -> AWSS3.UploadPartOutputResponse { + fatalError("Not Implemented") + } + + func uploadPartCopy(input: AWSS3.UploadPartCopyInput) async throws -> AWSS3.UploadPartCopyOutputResponse { + fatalError("Not Implemented") + } + + func writeGetObjectResponse(input: AWSS3.WriteGetObjectResponseInput) async throws -> AWSS3.WriteGetObjectResponseOutputResponse { + fatalError("Not Implemented") + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift index 0664ef453b..510e77bae0 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift @@ -22,9 +22,10 @@ extension MockAWSS3PreSignedURLBuilder: AWSS3PreSignedURLBuilderBehavior { func getPreSignedURL( key: String, signingOperation: AWSS3SigningOperation, + metadata: [String : String]?, accelerate: Bool?, expires: Int64?) async throws -> URL { - interactions.append("\(#function) \(key) \(signingOperation) \(String(describing: expires))") + interactions.append("\(#function) \(key) \(signingOperation) \(String(describing: metadata)) \(String(describing: expires))") return try await getPreSignedURLHandler(key, signingOperation, expires) } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift index d11d5bd499..2b70e3bc90 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift @@ -80,9 +80,10 @@ public class MockAWSS3StorageService: AWSS3StorageServiceBehavior { public func getPreSignedURL( serviceKey: String, signingOperation: AWSS3SigningOperation, + metadata: [String: String]?, accelerate: Bool?, expires: Int) async throws -> URL { - interactions.append("\(#function) \(serviceKey) \(signingOperation) \(expires)") + interactions.append("\(#function) \(serviceKey) \(signingOperation) \(String(describing: metadata)) \(expires)") return try await getPreSignedURLHandler(serviceKey, signingOperation, expires) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageDownloadFileOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageDownloadFileOperationTests.swift index faecad2df9..b87d1cd939 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageDownloadFileOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageDownloadFileOperationTests.swift @@ -68,7 +68,7 @@ class AWSS3StorageDownloadFileOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [failedInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) } @@ -177,7 +177,7 @@ class AWSS3StorageDownloadFileOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [inProcessInvoked, completeInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDownload(serviceKey: expectedServiceKey, fileURL: url) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageGetDataOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageGetDataOperationTests.swift index 89c4183eb7..5acea18c20 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageGetDataOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageGetDataOperationTests.swift @@ -45,26 +45,28 @@ class AWSS3StorageDownloadDataOperationTests: AWSS3StorageOperationTestBase { mockAuthService.getIdentityIdError = AuthError.service("", "", "") let request = StorageDownloadDataRequest(key: testKey, options: StorageDownloadDataRequest.Options()) let failedInvoked = expectation(description: "failed was invoked on operation") - let operation = AWSS3StorageDownloadDataOperation(request, - storageConfiguration: testStorageConfiguration, - storageService: mockStorageService, - authService: mockAuthService, - progressListener: nil) { event in - switch event { - case .failure(let error): - guard case .authError = error else { - XCTFail("Should have failed with authError") - return - } - failedInvoked.fulfill() - default: - XCTFail("Should have received failed event") - } + let operation = AWSS3StorageDownloadDataOperation( + request, + storageConfiguration: testStorageConfiguration, + storageService: mockStorageService, + authService: mockAuthService, + progressListener: nil + ) { event in + switch event { + case .failure(let error): + guard case .authError = error else { + XCTFail("Should have failed with authError") + return + } + failedInvoked.fulfill() + default: + XCTFail("Should have received failed event") + } } operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [failedInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) } @@ -96,7 +98,7 @@ class AWSS3StorageDownloadDataOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [inProcessInvoked, completeInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDownload(serviceKey: expectedServiceKey, fileURL: nil) } @@ -129,7 +131,7 @@ class AWSS3StorageDownloadDataOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [inProcessInvoked, failInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDownload(serviceKey: expectedServiceKey, fileURL: nil) } @@ -164,7 +166,10 @@ class AWSS3StorageDownloadDataOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment( + of: [inProcessInvoked, completeInvoked], + timeout: 1 + ) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDownload(serviceKey: expectedServiceKey, fileURL: nil) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift index 208f97fafa..2abd07f580 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift @@ -84,7 +84,6 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { let expectedUploadSource = UploadSource.data(testData) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadDataRequest.Options(accessLevel: .protected, metadata: metadata, @@ -119,7 +118,7 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } func testUploadDataOperationUploadFail() { @@ -183,7 +182,6 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { "Could not create data object greater than MultiPartUploadSizeThreshold") let expectedUploadSource = UploadSource.data(testLargeData) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadDataRequest.Options(accessLevel: .protected, metadata: metadata, @@ -218,7 +216,7 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } // TODO: test pause, resume, canel, etc. diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageRemoveOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageRemoveOperationTests.swift index aea952020b..5369bc68d4 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageRemoveOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageRemoveOperationTests.swift @@ -64,7 +64,7 @@ class AWSS3StorageRemoveOperationTests: AWSS3StorageOperationTestBase { } operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [failedInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) } @@ -89,7 +89,7 @@ class AWSS3StorageRemoveOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [completeInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDelete(serviceKey: expectedServiceKey) } @@ -115,7 +115,7 @@ class AWSS3StorageRemoveOperationTests: AWSS3StorageOperationTestBase { operation.start() - await waitForExpectations(timeout: 1) + await fulfillment(of: [failedInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDelete(serviceKey: expectedServiceKey) } @@ -142,7 +142,7 @@ class AWSS3StorageRemoveOperationTests: AWSS3StorageOperationTestBase { operation.start() - waitForExpectations(timeout: 1) + wait(for: [completeInvoked], timeout: 1) XCTAssertTrue(operation.isFinished) mockStorageService.verifyDelete(serviceKey: expectedServiceKey) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift index 1c1b904312..52800e958e 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift @@ -118,7 +118,6 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { FileManager.default.createFile(atPath: filePath, contents: testData, attributes: nil) let expectedUploadSource = UploadSource.local(fileURL) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadFileRequest.Options(accessLevel: .protected, metadata: metadata, @@ -153,7 +152,7 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } func testUploadFileOperationUploadFail() { @@ -219,7 +218,6 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { "Could not create data object greater than MultiPartUploadSizeThreshold") let expectedUploadSource = UploadSource.local(testURL) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadFileRequest.Options(accessLevel: .protected, metadata: metadata, @@ -254,7 +252,7 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } // TODO: test pause, resume, canel, etc. diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift index 4f725a5780..5e438e735f 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift @@ -68,11 +68,12 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { func testForGetObject() async throws { let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: .getObject, + metadata: nil, accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.getObject) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:metadata:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.getObject) nil \(String(describing: expires))" ]) } @@ -82,11 +83,28 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { func testForPutObject() async throws { let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: .putObject, + metadata: nil, accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.putObject) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:metadata:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.putObject) nil \(String(describing: expires))" + ]) + } + + /// - Given: A storage service configured to use a AWSS3PreSignedURLBuilder + /// - When: A presigned URL is requested for a **AWSS3SigningOperation.putObject** operation with metadata + /// - Then: A valid URL is returned + func testForPutObjectWithMetadata() async throws { + let metadata: [String: String]? = ["test": "value"] + let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, + signingOperation: .putObject, + metadata: metadata, + accelerate: nil, + expires: expires) + XCTAssertEqual(url, presignedURL) + XCTAssertEqual(builder.interactions, [ + "getPreSignedURL(key:signingOperation:metadata:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.putObject) \(String(describing: metadata)) \(String(describing: expires))" ]) } @@ -97,11 +115,12 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { let operation = AWSS3SigningOperation.uploadPart(partNumber: 0, uploadId: UUID().uuidString) let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: operation, + metadata: nil, accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(operation) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:metadata:accelerate:expires:) \(serviceKey ?? "") \(operation) nil \(String(describing: expires))" ]) } @@ -110,7 +129,7 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { /// - Then: A StorageError.keyNotFound is thrown func testvalidateObjectExistenceForNonExistentKey() async throws { client.headObjectHandler = { _ in - throw HeadObjectOutputError.notFound(.init()) + throw AWSS3.NotFound() } let nonExistentKey = UUID().uuidString do { @@ -126,8 +145,9 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { /// - Then: An SdkError.service is thrown func testvalidateObjectExistenceForNonExistentKeyWithSdkServiceError() async throws { client.headObjectHandler = { _ in - let headObjectError = HeadObjectOutputError.notFound(.init()) - throw SdkError.service(headObjectError, HttpResponse(body: .none, statusCode: .notFound)) + throw try await AWSS3.NotFound( + httpResponse: HttpResponse(body: .none, statusCode: .notFound) + ) } let nonExistentKey = UUID().uuidString do { @@ -143,8 +163,7 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { /// - Then: An SdkError.client is thrown func testvalidateObjectExistenceForNonExistentKeyWithSdkClientError() async throws { client.headObjectHandler = { _ in - let headObjectError = HeadObjectOutputError.notFound(.init()) - throw SdkError.client(ClientError.retryError(headObjectError)) + throw AWSS3.NotFound() } let nonExistentKey = UUID().uuidString do { diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceListTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceListTests.swift index 10ad1df2e3..5a4f5dc878 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceListTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceListTests.swift @@ -10,7 +10,7 @@ import AWSS3 import Amplify import ClientRuntime import XCTest - +import AWSClientRuntime @testable import AWSPluginsTestCommon @testable import AWSS3StoragePlugin @@ -98,8 +98,12 @@ final class AWSS3StorageServiceListTests: XCTestCase { /// Then: The service throws a `StorageError` error func testSdkError() async throws { client.listObjectsV2Handler = { _ in - let response = HttpResponse(body: .empty, statusCode: .forbidden) - throw try SdkError.service(ListObjectsV2OutputError(httpResponse: response), response) + throw AWSClientRuntime.UnknownAWSHTTPServiceError( + httpResponse: HttpResponse(body: .empty, statusCode: .forbidden), + message: nil, + requestID: nil, + typeName: nil + ) } let options = StorageListRequest.Options(accessLevel: .protected, targetIdentityId: targetIdentityId, path: path) do { diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceTests.swift new file mode 100644 index 0000000000..25f5410d4a --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceTests.swift @@ -0,0 +1,455 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import AWSPluginsTestCommon +@testable import AWSS3StoragePlugin +import ClientRuntime +import AWSS3 +import XCTest + +class AWSS3StorageServiceTests: XCTestCase { + private var service: AWSS3StorageService! + private var authService: MockAWSAuthService! + private var database: StorageTransferDatabaseMock! + private var task: StorageTransferTask! + private var fileSystem: MockFileSystem! + + override func setUp() async throws { + authService = MockAWSAuthService() + database = StorageTransferDatabaseMock() + fileSystem = MockFileSystem() + task = StorageTransferTask( + transferType: .download(onEvent: { _ in}), + bucket: "bucket", + key: "key" + ) + task.uploadId = "uploadId" + task.sessionTask = MockStorageSessionTask(taskIdentifier: 1) + database.recoverResult = .success([ + .init(transferTask: task, + multipartUploads: [ + .created( + uploadId: "uploadId", + uploadFile:UploadFile( + fileURL: FileSystem.default.createTemporaryFileURL(), + temporaryFileCreated: true, + size: UInt64(Bytes.megabytes(12).bytes) + ) + ) + ] + ) + ]) + service = try AWSS3StorageService( + authService: authService, + region: "region", + bucket: "bucket", + httpClientEngineProxy: MockHttpClientEngineProxy(), + storageTransferDatabase: database, + fileSystem: fileSystem, + logger: MockLogger() + ) + } + + override func tearDown() { + authService = nil + service = nil + database = nil + task = nil + fileSystem = nil + } + + /// Given: An AWSS3StorageService + /// When: it's deallocated + /// Then: StorageBackgroundEventsRegistry.identifier should be set to nil + func testDeinit_shouldUnregisterIdentifier() { + XCTAssertNotNil(StorageBackgroundEventsRegistry.identifier) + service = nil + XCTAssertNil(StorageBackgroundEventsRegistry.identifier) + } + + /// Given: An AWSS3StorageService + /// When: reset is invoked + /// Then: Its members should be set to nil + func testReset_shouldSetValuesToNil() { + service.reset() + XCTAssertNil(service.preSignedURLBuilder) + XCTAssertNil(service.awsS3) + XCTAssertNil(service.region) + XCTAssertNil(service.bucket) + XCTAssertTrue(service.tasks.isEmpty) + XCTAssertTrue(service.multipartUploadSessions.isEmpty) + } + + /// Given: An AWSS3StorageService + /// When: attachEventHandlers is invoked and a .completed event is sent + /// Then: A .completed event is dispatched to the event handler + func testAttachEventHandlers() { + let expectation = self.expectation(description: "Attach Event Handlers") + service.attachEventHandlers( + onUpload: { event in + guard case .completed(_) = event else { + XCTFail("Expected completed") + return + } + expectation.fulfill() + } + ) + XCTAssertNotNil(database.onUploadHandler) + database.onUploadHandler?(.completed(())) + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3StorageService + /// When: register is invoked with a task + /// Then: The task should be added to its map of tasks + func testRegisterTask_shouldAddItToTasksDictionary() { + service.register(task: task) + XCTAssertEqual(service.tasks.count, 1) + XCTAssertNotNil(service.tasks[1]) + } + + /// Given: An AWSS3StorageService with a task in its map of tasks + /// When: unregister is invoked with said task + /// Then: The task should be removed from the map of tasks + func testUnregisterTask_shouldRemoveItToTasksDictionary() { + service.tasks = [ + 1: task + ] + service.unregister(task: task) + XCTAssertTrue(service.tasks.isEmpty) + XCTAssertNil(service.tasks[1]) + } + + /// Given: An AWSS3StorageService with some tasks in its map of tasks + /// When: unregister is invoked with an identifier that is known to be mapped to a task. + /// Then: The task corresponding to the given identifier should be removed from the map of tasks + func testUnregisterTaskIdentifiers_shouldRemoveItToTasksDictionary() { + service.tasks = [ + 1: task, + 2: task + ] + service.unregister(taskIdentifiers: [1]) + XCTAssertEqual(service.tasks.count, 1) + XCTAssertNotNil(service.tasks[2]) + XCTAssertNil(service.tasks[1]) + } + + /// Given: An AWSS3StorageService with a task in its map of tasks + /// When: findTask is invoked with the identifier known to be mapped to a task + /// Then: The task corresponding to the given identifier is returned + func testFindTask_shouldReturnTask() { + service.tasks = [ + 1: task + ] + XCTAssertNotNil(service.findTask(taskIdentifier: 1)) + } + + /// Given: An AWSS3StorageService + /// When: validateParameters is invoked with an empty bucket parameter + /// Then: A .validation error is thrown + func testValidateParameters_withEmptyBucket_shouldThrowError() { + do { + try service.validateParameters(bucket: "", key: "key", accelerationModeEnabled: true) + XCTFail("Expected error") + } catch { + guard case .validation(let field, let description, let recovery, _) = error as? StorageError else { + XCTFail("Expected StorageError.validation") + return + } + XCTAssertEqual(field, "bucket") + XCTAssertEqual(description, "Invalid bucket specified.") + XCTAssertEqual(recovery, "Please specify a bucket name or configure the bucket property.") + } + } + + /// Given: An AWSS3StorageService + /// When: validateParameters is invoked with an empty key parameter + /// Then: A .validation error is thrown + func testValidateParameters_withEmptyKey_shouldThrowError() { + do { + try service.validateParameters(bucket: "bucket", key: "", accelerationModeEnabled: true) + XCTFail("Expected error") + } catch { + guard case .validation(let field, let description, let recovery, _) = error as? StorageError else { + XCTFail("Expected StorageError.validation") + return + } + XCTAssertEqual(field, "key") + XCTAssertEqual(description, "Invalid key specified.") + XCTAssertEqual(recovery, "Please specify a key.") + } + } + + /// Given: An AWSS3StorageService + /// When: validateParameters is invoked with valid bucket and key parameters + /// Then: No error is thrown + func testValidateParameters_withValidParams_shouldNotThrowError() { + do { + try service.validateParameters(bucket: "bucket", key: "key", accelerationModeEnabled: true) + } catch { + XCTFail("Expected success, got \(error)") + } + } + + /// Given: An AWSS3StorageService + /// When: createTransferTask is invoked with valid parameters + /// Then: A task is returned with attributes matching the ones provided + func testCreateTransferTask_shouldReturnTask() { + let task = service.createTransferTask( + transferType: .upload(onEvent: { event in }), + bucket: "bucket", + key: "key", + requestHeaders: [ + "header": "value" + ] + ) + XCTAssertEqual(task.bucket, "bucket") + XCTAssertEqual(task.key, "key") + XCTAssertEqual(task.requestHeaders?.count, 1) + XCTAssertEqual(task.requestHeaders?["header"], "value") + guard case .upload(_) = task.transferType else { + XCTFail("Expected .upload transferType") + return + } + } + + /// Given: An AWSS3StorageService with a non-completed download task + /// When: completeDownload is invoked for the identifier matching the task + /// Then: The task is marked as completed and a .completed event is dispatched + func testCompleteDownload_shouldReturnData() { + let expectation = self.expectation(description: "Complete Download") + + let downloadTask = StorageTransferTask( + transferType: .download(onEvent: { event in + guard case .completed(let data) = event, + let data = data else { + XCTFail("Expected .completed event with data") + return + } + XCTAssertEqual(String(decoding: data, as: UTF8.self), "someFile") + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + + let sourceUrl = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt") + try! "someFile".write(to: sourceUrl, atomically: true, encoding: .utf8) + + service.tasks = [ + 1: downloadTask + ] + + service.completeDownload(taskIdentifier: 1, sourceURL: sourceUrl) + XCTAssertEqual(downloadTask.status, .completed) + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3StorageService with a non-completed download task that sets a location + /// When: completeDownload is invoked for the identifier matching the task + /// Then: The task is marked as completed and the file is moved to the expected location + func testCompleteDownload_withLocation_shouldMoveFileToLocation() { + let temporaryDirectory = FileManager.default.temporaryDirectory + let location = temporaryDirectory.appendingPathComponent("\(UUID().uuidString)-newFile.txt") + + let downloadTask = StorageTransferTask( + transferType: .download(onEvent: { _ in }), + bucket: "bucket", + key: "key", + location: location + ) + + let sourceUrl = temporaryDirectory.appendingPathComponent("\(UUID().uuidString)-oldFile.txt") + try! "someFile".write(to: sourceUrl, atomically: true, encoding: .utf8) + + service.tasks = [ + 1: downloadTask + ] + + service.completeDownload(taskIdentifier: 1, sourceURL: sourceUrl) + XCTAssertTrue(FileManager.default.fileExists(atPath: location.path)) + XCTAssertFalse(FileManager.default.fileExists(atPath: sourceUrl.path)) + XCTAssertEqual(downloadTask.status, .completed) + } + + /// Given: An AWSS3StorageService with a non-completed download task that sets a location + /// When: completeDownload is invoked for the identifier matching the task, but the file system fails to move the file + /// Then: The task is marked as error and the file is not moved to the expected location + func testCompleteDownload_withLocation_andError_shouldFailTask() { + let temporaryDirectory = FileManager.default.temporaryDirectory + let location = temporaryDirectory.appendingPathComponent("\(UUID().uuidString)-newFile.txt") + + let downloadTask = StorageTransferTask( + transferType: .download(onEvent: { _ in }), + bucket: "bucket", + key: "key", + location: location + ) + + let sourceUrl = temporaryDirectory.appendingPathComponent("\(UUID().uuidString)-oldFile.txt") + try! "someFile".write(to: sourceUrl, atomically: true, encoding: .utf8) + + service.tasks = [ + 1: downloadTask + ] + + fileSystem.moveFileError = StorageError.unknown("Unable to move file", nil) + service.completeDownload(taskIdentifier: 1, sourceURL: sourceUrl) + XCTAssertFalse(FileManager.default.fileExists(atPath: location.path)) + XCTAssertTrue(FileManager.default.fileExists(atPath: sourceUrl.path)) + XCTAssertEqual(downloadTask.status, .error) + } + + /// Given: An AWSS3StorageService with a non-completed upload task that sets a location + /// When: completeDownload is invoked for the identifier matching the task + /// Then: The task status is not updated and an .upload event is not dispatched + func testCompleteDownload_withNoDownload_shouldDoNothing() { + let expectation = self.expectation(description: "Complete Download") + expectation.isInverted = true + + let uploadTask = StorageTransferTask( + transferType: .upload(onEvent: { event in + XCTFail("Should not report event") + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + + let sourceUrl = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt") + try! "someFile".write(to: sourceUrl, atomically: true, encoding: .utf8) + + service.tasks = [ + 1: uploadTask + ] + + service.completeDownload(taskIdentifier: 1, sourceURL: sourceUrl) + XCTAssertNotEqual(uploadTask.status, .completed) + XCTAssertNotEqual(uploadTask.status, .error) + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3StorageService that cannot create a pre signed url + /// When: upload is invoked + /// Then: A .failed event is dispatched with an .unknown error + func testUpload_withoutPreSignedURL_shouldSendFailEvent() { + let data = "someData".data(using: .utf8)! + let expectation = self.expectation(description: "Upload") + service.upload( + serviceKey: "key", + uploadSource: .data(data), + contentType: "application/json", + metadata: [:], + accelerate: true, + onEvent: { event in + guard case .failed(let error) = event, + case .unknown(let description, _) = error else { + XCTFail("Expected .failed event with .unknown error, got \(event)") + return + } + XCTAssertEqual(description, "Failed to get pre-signed URL") + expectation.fulfill() + } + ) + + waitForExpectations(timeout: 1) + } + + /// Given: An AWSS3StorageService that can create a pre signed url + /// When: upload is invoked + /// Then: An .initiated event is dispatched + func testUpload_withPreSignedURL_shouldSendInitiatedEvent() { + let data = "someData".data(using: .utf8)! + let expectation = self.expectation(description: "Upload") + service.preSignedURLBuilder = MockAWSS3PreSignedURLBuilder() + service.upload( + serviceKey: "key", + uploadSource: .data(data), + contentType: "application/json", + metadata: [:], + accelerate: true, + onEvent: { event in + guard case .initiated(_) = event else { + XCTFail("Expected .initiated event, got \(event)") + return + } + expectation.fulfill() + } + ) + + waitForExpectations(timeout: 1) + } +} + +private class MockHttpClientEngineProxy: HttpClientEngineProxy { + var target: HttpClientEngine? = nil + + var executeCount = 0 + var executeRequest: SdkHttpRequest? + func execute(request: SdkHttpRequest) async throws -> HttpResponse { + executeCount += 1 + executeRequest = request + return .init(body: .empty, statusCode: .accepted) + } +} + +private class StorageTransferDatabaseMock: StorageTransferDatabase { + + func prepareForBackground(completion: (() -> Void)?) { + completion?() + } + + func insertTransferRequest(task: StorageTransferTask) { + + } + + func updateTransferRequest(task: StorageTransferTask) { + + } + + func removeTransferRequest(task: StorageTransferTask) { + + } + + func defaultTransferType(persistableTransferTask: StoragePersistableTransferTask) -> StorageTransferType? { + return nil + } + + var recoverCount = 0 + var recoverResult: Result = .failure(StorageError.unknown("Result not set", nil)) + func recover(urlSession: StorageURLSession, + completionHandler: @escaping (Result) -> Void) { + recoverCount += 1 + completionHandler(recoverResult) + } + + var attachEventHandlersCount = 0 + var onUploadHandler: AWSS3StorageServiceBehavior.StorageServiceUploadEventHandler? = nil + var onDownloadHandler: AWSS3StorageServiceBehavior.StorageServiceDownloadEventHandler? = nil + var onMultipartUploadHandler: AWSS3StorageServiceBehavior.StorageServiceMultiPartUploadEventHandler? = nil + func attachEventHandlers( + onUpload: AWSS3StorageServiceBehavior.StorageServiceUploadEventHandler?, + onDownload: AWSS3StorageServiceBehavior.StorageServiceDownloadEventHandler?, + onMultipartUpload: AWSS3StorageServiceBehavior.StorageServiceMultiPartUploadEventHandler? + ) { + attachEventHandlersCount += 1 + onUploadHandler = onUpload + onDownloadHandler = onDownload + onMultipartUploadHandler = onMultipartUpload + } +} + +private class MockFileSystem: FileSystem { + var moveFileError: Error? = nil + override func moveFile(from sourceFileURL: URL, to destinationURL: URL) throws { + if let moveFileError = moveFileError { + throw moveFileError + } + try super.moveFile(from: sourceFileURL, to: destinationURL) + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageMultipartUploadClientTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageMultipartUploadClientTests.swift new file mode 100644 index 0000000000..561543f57d --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageMultipartUploadClientTests.swift @@ -0,0 +1,458 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import func AmplifyTestCommon.XCTAssertThrowFatalError +@testable import AWSS3StoragePlugin +import AWSS3 +import XCTest + +class DefaultStorageMultipartUploadClientTests: XCTestCase { + private var defaultClient: DefaultStorageMultipartUploadClient! + private var serviceProxy: MockStorageServiceProxy! + private var session: MockStorageMultipartUploadSession! + private var awss3Behavior: MockAWSS3Behavior! + private var uploadFile: UploadFile! + + override func setUp() async throws { + awss3Behavior = MockAWSS3Behavior() + serviceProxy = MockStorageServiceProxy( + awsS3: awss3Behavior + ) + let tempFileURL = FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension("txt") + try "Hello World".write(to: tempFileURL, atomically: true, encoding: .utf8) + uploadFile = UploadFile( + fileURL: tempFileURL, + temporaryFileCreated: false, + size: 88 + ) + defaultClient = DefaultStorageMultipartUploadClient( + serviceProxy: serviceProxy, + bucket: "bucket", + key: "key", + uploadFile: uploadFile + ) + session = MockStorageMultipartUploadSession( + client: client, + bucket: "bucket", + key: "key", + onEvent: { event in } + ) + client.integrate(session: session) + } + + private var client: StorageMultipartUploadClient! { + defaultClient + } + + override func tearDown() { + defaultClient = nil + serviceProxy = nil + session = nil + awss3Behavior = nil + uploadFile = nil + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: createMultipartUpload is invoked and AWSS3Behavior returns .success + /// Then: A .created event is reported to the session and the session is registered + func testCreateMultipartUpload_withSuccess_shouldSucceed() throws { + awss3Behavior.createMultipartUploadExpectation = expectation(description: "Create Multipart Upload") + awss3Behavior.createMultipartUploadResult = .success(.init( + bucket: "bucket", + key: "key", + uploadId: "uploadId" + )) + try client.createMultipartUpload() + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.createMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 2) + XCTAssertEqual(session.failCount, 0) + if case .created(let uploadFile, let uploadId) = try XCTUnwrap(session.lastMultipartUploadEvent) { + XCTAssertEqual(uploadFile.fileURL, uploadFile.fileURL) + XCTAssertEqual(uploadId, "uploadId") + } + XCTAssertEqual(serviceProxy.registerMultipartUploadSessionCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: createMultipartUpload is invoked and AWSS3Behavior returns .failure + /// Then: An .unknown error is reported to the session and the session is not registered + func testCreateMultipartUpload_withError_shouldFail() throws { + awss3Behavior.createMultipartUploadExpectation = expectation(description: "Create Multipart Upload") + awss3Behavior.createMultipartUploadResult = .failure(.unknown("Unknown Error", nil)) + try client.createMultipartUpload() + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.createMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 1) + XCTAssertEqual(session.failCount, 1) + if case .unknown(let description, _) = try XCTUnwrap(session.lastError) as? StorageError { + XCTAssertEqual(description, "Unknown Error") + } + XCTAssertEqual(serviceProxy.registerMultipartUploadSessionCount, 0) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: serviceProxy is set to nil and createMultipartUpload is invoked + /// Then: A fatal error is thrown + func testCreateMultipartUpload_withoutServiceProxy_shouldThrowFatalError() throws { + serviceProxy = nil + try XCTAssertThrowFatalError { + try? self.client.createMultipartUpload() + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: uploadPart is invoked with valid parts + /// Then: A .started event is reported to the session + func testUploadPart_withParts_shouldSucceed() throws { + session.handleUploadPartExpectation = expectation(description: "Upload Part with parts") + + try client.uploadPart( + partNumber: 1, + multipartUpload: .parts( + uploadId: "uploadId", + uploadFile: uploadFile, + partSize: .default, + parts: [ + .pending(bytes: 10), + .pending(bytes: 20) + ] + ), + subTask: .init( + transferType: .upload(onEvent: { event in }), + bucket: "bucket", + key: "key" + ) + ) + + waitForExpectations(timeout: 1) + XCTAssertEqual(session.handleUploadPartCount, 1) + XCTAssertEqual(session.failCount, 0) + if case .started(let partNumber, _) = try XCTUnwrap(session.lastUploadEvent) { + XCTAssertEqual(partNumber, 1) + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: uploadPart is invoked with a non-existing file + /// Then: An error is reported to the session + func testUploadPart_withInvalidFile_shouldFail() throws { + session.failExpectation = expectation(description: "Upload Part with invalid file") + + try client.uploadPart( + partNumber: 1, + multipartUpload: .parts( + uploadId: "uploadId", + uploadFile: .init( + fileURL: FileManager.default.temporaryDirectory.appendingPathComponent("noFile.txt"), + temporaryFileCreated: false, + size: 1024), + partSize: .default, + parts: [ + .pending(bytes: 10), + .pending(bytes: 20) + ] + ), + subTask: .init( + transferType: .upload(onEvent: { event in }), + bucket: "bucket", + key: "key" + ) + ) + + waitForExpectations(timeout: 1) + XCTAssertEqual(session.handleUploadPartCount, 0) + XCTAssertEqual(session.failCount, 1) + XCTAssertNil(session.lastUploadEvent) + XCTAssertNotNil(session.lastError) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: serviceProxy is set to nil and uploadPart is invoked + /// Then: A fatal error is thrown + func testUploadPart_withoutServiceProxy_shouldThrowFatalError() throws { + self.serviceProxy = nil + try XCTAssertThrowFatalError { + try? self.client.uploadPart( + partNumber: 1, + multipartUpload: .parts( + uploadId: "uploadId", + uploadFile: self.uploadFile, + partSize: .default, + parts: [ + .pending(bytes: 10), + .pending(bytes: 20) + ] + ), + subTask: .init( + transferType: .upload(onEvent: { event in }), + bucket: "bucket", + key: "key" + ) + ) + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: uploadPart is invoked without parts + /// Then: A fatal error is thrown + func testUploadPart_withoutParts_shouldThrowFatalError() throws { + try XCTAssertThrowFatalError { + try? self.client.uploadPart( + partNumber: 1, + multipartUpload: .created( + uploadId: "uploadId", + uploadFile: self.uploadFile + ), + subTask: .init( + transferType: .upload(onEvent: { event in }), + bucket: "bucket", + key: "key" + ) + ) + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: completeMultipartUpload is invoked and AWSS3Behaviour returns succees + /// Then: A .completed event is reported to the session and the session is unregistered + func testCompleteMultipartUpload_withSuccess_shouldSucceed() throws { + awss3Behavior.completeMultipartUploadExpectation = expectation(description: "Complete Multipart Upload") + awss3Behavior.completeMultipartUploadResult = .success(.init( + bucket: "bucket", + key: "key", + eTag: "eTag" + )) + try client.completeMultipartUpload(uploadId: "uploadId") + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.completeMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 1) + XCTAssertEqual(session.failCount, 0) + if case .completed(let uploadId) = try XCTUnwrap(session.lastMultipartUploadEvent) { + XCTAssertEqual(uploadId, "uploadId") + } + XCTAssertEqual(serviceProxy.unregisterMultipartUploadSessionCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: completeMultipartUpload is invoked and AWSS3Behaviour returns failure + /// Then: A .unknown error is reported to the session and the session is not unregistered + func testCompleteMultipartUpload_withError_shouldFail() throws { + awss3Behavior.completeMultipartUploadExpectation = expectation(description: "Complete Multipart Upload") + awss3Behavior.completeMultipartUploadResult = .failure(.unknown("Unknown Error", nil)) + try client.completeMultipartUpload(uploadId: "uploadId") + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.completeMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 0) + XCTAssertEqual(session.failCount, 1) + if case .unknown(let description, _) = try XCTUnwrap(session.lastError) as? StorageError { + XCTAssertEqual(description, "Unknown Error") + } + XCTAssertEqual(serviceProxy.unregisterMultipartUploadSessionCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: serviceProxy is set to nil and completeMultipartUpload is invoked + /// Then: A fatal error is thrown + func testCompleteMultipartUpload_withoutServiceProxy_shouldThrowFatalError() throws { + serviceProxy = nil + try XCTAssertThrowFatalError { + try? self.client.completeMultipartUpload(uploadId: "uploadId") + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: abortMultipartUpload is invoked and AWSS3Behaviour returns success + /// Then: An .aborted event is reported to the session and the session is unregistered + func testAbortMultipartUpload_withSuccess_shouldSucceed() throws { + awss3Behavior.abortMultipartUploadExpectation = expectation(description: "Abort Multipart Upload") + awss3Behavior.abortMultipartUploadResult = .success(()) + try client.abortMultipartUpload(uploadId: "uploadId", error: CancellationError()) + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.abortMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 1) + XCTAssertEqual(session.failCount, 0) + if case .aborted(let uploadId, let error) = try XCTUnwrap(session.lastMultipartUploadEvent) { + XCTAssertEqual(uploadId, "uploadId") + XCTAssertTrue(error is CancellationError) + } + XCTAssertEqual(serviceProxy.unregisterMultipartUploadSessionCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: abortMultipartUpload is invoked and AWSS3Behaviour returns failure + /// Then: A .unknown error is reported to the session and the session is not unregistered + func testAbortMultipartUpload_withError_shouldFail() throws { + awss3Behavior.abortMultipartUploadExpectation = expectation(description: "Abort Multipart Upload") + awss3Behavior.abortMultipartUploadResult = .failure(.unknown("Unknown Error", nil)) + try client.abortMultipartUpload(uploadId: "uploadId") + + waitForExpectations(timeout: 1) + XCTAssertEqual(awss3Behavior.abortMultipartUploadCount, 1) + XCTAssertEqual(session.handleMultipartUploadCount, 0) + XCTAssertEqual(session.failCount, 1) + if case .unknown(let description, _) = try XCTUnwrap(session.lastError) as? StorageError { + XCTAssertEqual(description, "Unknown Error") + } + XCTAssertEqual(serviceProxy.unregisterMultipartUploadSessionCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: serviceProxy is set to nil and abortMultipartUpload is invoked + /// Then: A fatal error is thrown + func testAbortMultipartUpload_withoutServiceProxy_shouldThrowFatalError() throws { + serviceProxy = nil + try XCTAssertThrowFatalError { + try? self.client.abortMultipartUpload(uploadId: "uploadId") + } + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: cancelUploadTasks is invoked with identifiers + /// Then: The tasks are unregistered + func testCancelUploadTasks_shouldSucceed() throws { + let cancelExpectation = expectation(description: "Cancel Upload Tasks") + client.cancelUploadTasks(taskIdentifiers: [0, 1,2], done: { + cancelExpectation.fulfill() + }) + + waitForExpectations(timeout: 1) + XCTAssertEqual(serviceProxy.unregisterTaskIdentifiersCount, 1) + } + + /// Given: a DefaultStorageMultipartUploadClient + /// When: filter is invoked with some disallowed values + /// Then: a dictionary is returned with the disallowed values removed + func testFilterRequestHeaders_shouldResultFilteredHeaders() { + let filteredHeaders = defaultClient.filter( + requestHeaders: [ + "validHeader": "validValue", + "x-amz-acl": "invalidValue", + "x-amz-tagging": "invalidValue", + "x-amz-storage-class": "invalidValue", + "x-amz-server-side-encryption": "invalidValue", + "x-amz-meta-invalid_one": "invalidValue", + "x-amz-meta-invalid_two": "invalidValue", + "x-amz-grant-invalid_one": "invalidvalue", + "x-amz-grant-invalid_two": "invalidvalue" + ] + ) + + XCTAssertEqual(filteredHeaders.count, 1) + XCTAssertEqual(filteredHeaders["validHeader"], "validValue") + } +} + +private class MockStorageServiceProxy: StorageServiceProxy { + var preSignedURLBuilder: AWSS3PreSignedURLBuilderBehavior! = MockAWSS3PreSignedURLBuilder() + var awsS3: AWSS3Behavior! + var urlSession = URLSession.shared + var userAgent: String = "" + var urlRequestDelegate: URLRequestDelegate? = nil + + init(awsS3: AWSS3Behavior) { + self.awsS3 = awsS3 + } + + func register(task: StorageTransferTask) {} + + func unregister(task: StorageTransferTask) {} + + var unregisterTaskIdentifiersCount = 0 + func unregister(taskIdentifiers: [TaskIdentifier]) { + unregisterTaskIdentifiersCount += 1 + } + + var registerMultipartUploadSessionCount = 0 + func register(multipartUploadSession: StorageMultipartUploadSession) { + registerMultipartUploadSessionCount += 1 + } + + var unregisterMultipartUploadSessionCount = 0 + func unregister(multipartUploadSession: StorageMultipartUploadSession) { + unregisterMultipartUploadSessionCount += 1 + } +} + +private class MockAWSS3Behavior: AWSS3Behavior { + func deleteObject(_ request: AWSS3DeleteObjectRequest, completion: @escaping (Result) -> Void) {} + + func listObjectsV2(_ request: AWSS3ListObjectsV2Request, completion: @escaping (Result) -> Void) {} + + var createMultipartUploadCount = 0 + var createMultipartUploadResult: Result? = nil + var createMultipartUploadExpectation: XCTestExpectation? = nil + func createMultipartUpload(_ request: CreateMultipartUploadRequest, completion: @escaping (Result) -> Void) { + createMultipartUploadCount += 1 + if let result = createMultipartUploadResult { + completion(result) + } + createMultipartUploadExpectation?.fulfill() + } + + var completeMultipartUploadCount = 0 + var completeMultipartUploadResult: Result? = nil + var completeMultipartUploadExpectation: XCTestExpectation? = nil + func completeMultipartUpload(_ request: AWSS3CompleteMultipartUploadRequest, completion: @escaping (Result) -> Void) { + completeMultipartUploadCount += 1 + if let result = completeMultipartUploadResult { + completion(result) + } + completeMultipartUploadExpectation?.fulfill() + } + + var abortMultipartUploadCount = 0 + var abortMultipartUploadResult: Result? = nil + var abortMultipartUploadExpectation: XCTestExpectation? = nil + func abortMultipartUpload(_ request: AWSS3AbortMultipartUploadRequest, completion: @escaping (Result) -> Void) { + abortMultipartUploadCount += 1 + if let result = abortMultipartUploadResult { + completion(result) + } + abortMultipartUploadExpectation?.fulfill() + } + + func getS3() -> S3ClientProtocol { + return MockS3Client() + } +} + +class MockStorageMultipartUploadSession: StorageMultipartUploadSession { + var handleMultipartUploadCount = 0 + var lastMultipartUploadEvent: StorageMultipartUploadEvent? = nil + override func handle(multipartUploadEvent: StorageMultipartUploadEvent) { + handleMultipartUploadCount += 1 + lastMultipartUploadEvent = multipartUploadEvent + } + + var handleUploadPartCount = 0 + var lastUploadEvent: StorageUploadPartEvent? = nil + var handleUploadPartExpectation: XCTestExpectation? = nil + + override func handle(uploadPartEvent: StorageUploadPartEvent) { + handleUploadPartCount += 1 + lastUploadEvent = uploadPartEvent + handleUploadPartExpectation?.fulfill() + } + + var failCount = 0 + var lastError: Error? = nil + var failExpectation: XCTestExpectation? = nil + override func fail(error: Error) { + failCount += 1 + lastError = error + failExpectation?.fulfill() + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift new file mode 100644 index 0000000000..127c151229 --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift @@ -0,0 +1,241 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import AWSS3StoragePlugin +import XCTest + +class DefaultStorageTransferDatabaseTests: XCTestCase { + private var database: DefaultStorageTransferDatabase! + private var uploadFile: UploadFile! + private var session: MockStorageSessionTask! + + override func setUp() { + database = DefaultStorageTransferDatabase( + databaseDirectoryURL: FileManager.default.temporaryDirectory, + logger: MockLogger() + ) + uploadFile = UploadFile( + fileURL: FileSystem.default.createTemporaryFileURL(), + temporaryFileCreated: true, + size: UInt64(Bytes.megabytes(12).bytes) + ) + session = MockStorageSessionTask(taskIdentifier: 1) + } + + override func tearDown() { + database = nil + uploadFile = nil + session = nil + } + + /// Given: A DefaultStorageTransferDatabase + /// When: linkTasksWithSessions is invoked with tasks containing multipart uploads and a sessionTask, and a session + /// Then: A StorageTransferTaskPairs linking the tasks with the session is returned + func testLinkTasksWithSessions_withMultipartUpload_shouldReturnPairs() { + let transferTask1 = StorageTransferTask( + transferType: .multiPartUpload(onEvent: { _ in }), + bucket: "bucket", + key: "key1" + ) + transferTask1.sessionTask = session + transferTask1.multipartUpload = .created( + uploadId: "uploadId", + uploadFile: uploadFile + ) + + let transferTask2 = StorageTransferTask( + transferType: .multiPartUpload(onEvent: { _ in }), + bucket: "bucket", + key: "key2" + ) + transferTask2.sessionTask = session + transferTask2.multipartUpload = .created( + uploadId: "uploadId", + uploadFile: uploadFile + ) + + let pairs = database.linkTasksWithSessions( + persistableTransferTasks: [ + "taskId1": .init(task: transferTask1), + "taskId2": .init(task: transferTask2) + ], + sessionTasks: [ + session + ] + ) + + XCTAssertEqual(pairs.count, 2) + XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key1" })) + XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key2" })) + } + + /// Given: A DefaultStorageTransferDatabase + /// When: linkTasksWithSessions is invoked with tasks containing multipart uploads but without a sessionTask, and a session + /// Then: A StorageTransferTaskPairs linking the tasks with the session is returned + func testLinkTasksWithSessions_withMultipartUpload_andNoSession_shouldReturnPairs() { + let transferTask1 = StorageTransferTask( + transferType: .multiPartUpload(onEvent: { _ in }), + bucket: "bucket", + key: "key1" + ) + transferTask1.multipartUpload = .created( + uploadId: "uploadId", + uploadFile: uploadFile + ) + + let transferTask2 = StorageTransferTask( + transferType: .multiPartUpload(onEvent: { _ in }), + bucket: "bucket", + key: "key2" + ) + transferTask2.multipartUpload = .created( + uploadId: "uploadId", + uploadFile: uploadFile + ) + + let pairs = database.linkTasksWithSessions( + persistableTransferTasks: [ + "taskId1": .init(task: transferTask1), + "taskId2": .init(task: transferTask2) + ], + sessionTasks: [ + session + ] + ) + + XCTAssertEqual(pairs.count, 2) + XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key1" })) + XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key2" })) + } + + /// Given: A DefaultStorageTransferDatabase + /// When: linkTasksWithSessions is invoked with tasks containing multipart upload parts, and a session + /// Then: A StorageTransferTaskPairs linking the tasks with the session is returned + func testLinkTasksWithSessions_withMultipartUploadPart_shouldReturnPairs() { + let transferTask0 = StorageTransferTask( + transferType: .multiPartUpload(onEvent: { _ in }), + bucket: "bucket", + key: "key1" + ) + transferTask0.sessionTask = session + transferTask0.multipartUpload = .created( + uploadId: "uploadId", + uploadFile: uploadFile + ) + + let transferTask1 = StorageTransferTask( + transferType: .multiPartUploadPart( + uploadId: "uploadId", + partNumber: 1 + ), + bucket: "bucket", + key: "key1" + ) + transferTask1.sessionTask = session + transferTask1.uploadId = "uploadId" + transferTask1.multipartUpload = .parts( + uploadId: "uploadId", + uploadFile: uploadFile, + partSize: try! .init(fileSize: UInt64(Bytes.megabytes(6).bytes)), + parts: [ + .inProgress( + bytes: Bytes.megabytes(6).bytes, + bytesTransferred: Bytes.megabytes(3).bytes, + taskIdentifier: 1 + ), + .completed( + bytes: Bytes.megabytes(6).bytes, + eTag: "eTag") + , + .pending(bytes: Bytes.megabytes(6).bytes) + ] + ) + transferTask1.uploadPart = .completed( + bytes: Bytes.megabytes(6).bytes, + eTag: "eTag" + ) + + let transferTask2 = StorageTransferTask( + transferType: .multiPartUploadPart( + uploadId: "uploadId", + partNumber: 2 + ), + bucket: "bucket", + key: "key1" + ) + transferTask2.sessionTask = session + transferTask2.uploadId = "uploadId" + transferTask2.multipartUpload = .parts( + uploadId: "uploadId", + uploadFile: uploadFile, + partSize: try! .init(fileSize: UInt64(Bytes.megabytes(6).bytes)), + parts: [ + .pending(bytes: Bytes.megabytes(6).bytes), + .pending(bytes: Bytes.megabytes(6).bytes) + ] + ) + transferTask2.uploadPart = .inProgress( + bytes: Bytes.megabytes(6).bytes, + bytesTransferred: Bytes.megabytes(3).bytes, + taskIdentifier: 1 + ) + + let pairs = database.linkTasksWithSessions( + persistableTransferTasks: [ + "taskId0": .init(task: transferTask0), + "taskId1": .init(task: transferTask1), + "taskId2": .init(task: transferTask2) + ], + sessionTasks: [ + session + ] + ) + + XCTAssertEqual(pairs.count, 3) + XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key1" })) + XCTAssertFalse(pairs.contains(where: { $0.transferTask.key == "key2" })) + } + + /// Given: A DefaultStorageTransferDatabase + /// When: recover is invoked with a StorageURLSession that returns a session + /// Then: A .success is returned + func testLoadPersistableTasks() { + let urlSession = MockStorageURLSession( + sessionTasks: [ + session + ]) + let expectation = self.expectation(description: "Recover") + database.recover(urlSession: urlSession) { result in + guard case .success(_) = result else { + XCTFail("Expected success") + return + } + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: A DefaultStorageTransferDatabase + /// When: prepareForBackground is invoked + /// Then: A callback is invoked + func testPrepareForBackground() { + let expectation = self.expectation(description: "Prepare for Background") + database.prepareForBackground() { + expectation.fulfill() + } + waitForExpectations(timeout: 1) + } + + /// Given: The StorageTransferDatabase Type + /// When: default is invoked + /// Then: An instance of DefaultStorageTransferDatabase is returned + func testDefault_shouldReturnDefaultInstance() { + let defaultProtocol: StorageTransferDatabase = .default + XCTAssertTrue(defaultProtocol is DefaultStorageTransferDatabase) + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageBackgroundEventsRegistryTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageBackgroundEventsRegistryTests.swift index 4d1cb67a88..6b1f696261 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageBackgroundEventsRegistryTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageBackgroundEventsRegistryTests.swift @@ -18,14 +18,13 @@ class StorageBackgroundEventsRegistryTests: XCTestCase { let otherIdentifier = UUID().uuidString StorageBackgroundEventsRegistry.register(identifier: identifier) - let done = asyncExpectation(description: "done", expectedFulfillmentCount: 2) + let done = expectation(description: "done") + done.expectedFulfillmentCount = 2 Task { let handled = await withCheckedContinuation { (continuation: CheckedContinuation) in StorageBackgroundEventsRegistry.handleBackgroundEvents(identifier: identifier, continuation: continuation) - Task { - await done.fulfill() - } + done.fulfill() } XCTAssertTrue(handled) } @@ -33,14 +32,12 @@ class StorageBackgroundEventsRegistryTests: XCTestCase { Task { let otherHandled = await withCheckedContinuation { (continuation: CheckedContinuation) in StorageBackgroundEventsRegistry.handleBackgroundEvents(identifier: otherIdentifier, continuation: continuation) - Task { - await done.fulfill() - } + done.fulfill() } XCTAssertFalse(otherHandled) } - await waitForExpectations([done]) + await fulfillment(of: [done]) handleEvents(for: identifier) handleEvents(for: otherIdentifier) @@ -51,19 +48,17 @@ class StorageBackgroundEventsRegistryTests: XCTestCase { let otherIdentifier = UUID().uuidString StorageBackgroundEventsRegistry.register(identifier: otherIdentifier) - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") Task { let handled = await withCheckedContinuation { (continuation: CheckedContinuation) in StorageBackgroundEventsRegistry.handleBackgroundEvents(identifier: identifier, continuation: continuation) - Task { - await done.fulfill() - } + done.fulfill() } XCTAssertFalse(handled) } - await waitForExpectations([done]) + await fulfillment(of: [done]) } // Simulates URLSessionDelegate behavior diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift index fa808b4ccc..dad5a0a531 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift @@ -41,6 +41,44 @@ class StorageMultipartUploadSessionTests: XCTestCase { XCTAssertFalse(session.partsFailed) } + /// Given: A StorageTransferTask with a valid StorageTransferType + /// When: A StorageMultipartUploadSession is created from the task + /// Then: Its values are set correctly + func testSessionCreation_withTransferTask() throws { + let client = MockMultipartUploadClient() + let transferType: StorageTransferType = .multiPartUpload(onEvent: {_ in }) + let transferTask = StorageTransferTask( + transferType: transferType, + bucket: "bucket", + key: "key" + ) + + let session = try XCTUnwrap(StorageMultipartUploadSession(client: client, transferTask: transferTask, multipartUpload: .none, logger: MockLogger())) + XCTAssertEqual(session.partsCount, 0) + XCTAssertEqual(session.inProgressCount, 0) + XCTAssertFalse(session.partsCompleted) + XCTAssertFalse(session.partsFailed) + } + + /// Given: A StorageTransferTask with an invalid StorageTransferType + /// When: A StorageMultipartUploadSession is created from the task + /// Then: Its values are set correctly + func testSessionCreation_withTransferTask_andInvalidTransferType_shouldReturnNil() throws { + let client = MockMultipartUploadClient() + let transferType: StorageTransferType = .list(onEvent: {_ in }) + let transferTask = StorageTransferTask( + transferType: transferType, + bucket: "bucket", + key: "key" + ) + + XCTAssertNil(StorageMultipartUploadSession( + client: client, + transferTask: transferTask, + multipartUpload: .none + )) + } + func testCompletedMultipartUploadSession() throws { let initiatedExp = expectation(description: "Initiated") let completedExp = expectation(description: "Completed") @@ -105,7 +143,7 @@ class StorageMultipartUploadSessionTests: XCTestCase { let client = MockMultipartUploadClient() // creates an UploadFile for the mock process client.didCompletePartUpload = { (_, partNumber, _, _) in if partNumber == 5 { - closureSession?.handle(multipartUploadEvent: .aborting(error: nil)) + closureSession?.cancel() XCTAssertTrue(closureSession?.isAborted ?? false) } @@ -156,10 +194,10 @@ class StorageMultipartUploadSessionTests: XCTestCase { if pauseCount == 0, partNumber > 5, bytesTransferred > 0 { print("pausing on \(partNumber)") pauseCount += 1 - closureSession?.handle(multipartUploadEvent: .pausing) + closureSession?.pause() XCTAssertTrue(closureSession?.isPaused ?? false) print("resuming on \(partNumber)") - closureSession?.handle(multipartUploadEvent: .resuming) + closureSession?.resume() XCTAssertFalse(closureSession?.isPaused ?? true) } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageServiceSessionDelegateTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageServiceSessionDelegateTests.swift new file mode 100644 index 0000000000..b8498a0387 --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageServiceSessionDelegateTests.swift @@ -0,0 +1,363 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import AWSPluginsTestCommon +@testable import AWSS3StoragePlugin +import ClientRuntime +import AWSS3 +import XCTest + +class StorageServiceSessionDelegateTests: XCTestCase { + private var delegate: StorageServiceSessionDelegate! + private var service: AWSS3StorageServiceMock! + private var logger: MockLogger! + + override func setUp() { + service = try! AWSS3StorageServiceMock() + logger = MockLogger() + delegate = StorageServiceSessionDelegate( + identifier: "delegateTest", + logger: logger + ) + delegate.storageService = service + } + + override func tearDown() { + logger = nil + service = nil + delegate = nil + } + + /// Given: A StorageServiceSessionDelegate + /// When: logURLSessionActivity is invoked with warning set to true + /// Then: A warn message is logged + func testLogURLSession_withWarningTrue_shouldLogWarning() { + delegate.logURLSessionActivity("message", warning: true) + XCTAssertEqual(logger.warnCount, 1) + XCTAssertEqual(logger.infoCount, 0) + } + + /// Given: A StorageServiceSessionDelegate + /// When: logURLSessionActivity is invoked without setting warning + /// Then: An info message is logged + func testLogURLSession_shouldLogInfo() { + delegate.logURLSessionActivity("message") + XCTAssertEqual(logger.warnCount, 0) + XCTAssertEqual(logger.infoCount, 1) + } + + /// Given: A StorageServiceSessionDelegate and an identifier registered in the registry + /// When: the registry's handleBackgroundEvents is invoked with a matching identifier and then urlSessionDidFinishEvents is invoked + /// Then: The registry's continuation is triggered with true + func testDidFinishEvents_withMatchingIdentifiers_shouldTriggerContinuationWithTrue() async { + let handleEventsExpectation = self.expectation(description: "Handle Background Events") + let finishEventsExpectation = self.expectation(description: "Did Finish Events") + StorageBackgroundEventsRegistry.register(identifier: "identifier") + Task { + let result = await withCheckedContinuation { continuation in + StorageBackgroundEventsRegistry.handleBackgroundEvents( + identifier: "identifier", + continuation: continuation + ) + handleEventsExpectation.fulfill() + } + XCTAssertTrue(result) + finishEventsExpectation.fulfill() + } + + await fulfillment(of: [handleEventsExpectation], timeout: 1) + XCTAssertNotNil(StorageBackgroundEventsRegistry.continuation) + delegate.urlSessionDidFinishEvents(forBackgroundURLSession: .shared) + await fulfillment(of: [finishEventsExpectation], timeout: 1) + XCTAssertNil(StorageBackgroundEventsRegistry.continuation) + } + + /// Given: A StorageServiceSessionDelegate and an identifier registered in the registry + /// When: the registry's handleBackgroundEvents is invoked first with a matching identifier and then with a non-matching one, and after that urlSessionDidFinishEvents is invoked + /// Then: The registry's continuation for the non-matching identifier is triggered immediately with false, while the one for the matching identifier is triggered with true only after urlSessionDidFinishEvents is invoked + func testDidFinishEvents_withNonMatchingIdentifiers_shouldTriggerContinuationWithFalse() async { + let handleEventsMatchingExpectation = self.expectation(description: "Handle Background Events with Matching Identifiers") + let finishEventsExpectation = self.expectation(description: "Did Finish Events") + StorageBackgroundEventsRegistry.register(identifier: "identifier") + Task { + let result = await withCheckedContinuation { continuation in + StorageBackgroundEventsRegistry.handleBackgroundEvents( + identifier: "identifier", + continuation: continuation + ) + handleEventsMatchingExpectation.fulfill() + } + XCTAssertTrue(result) + finishEventsExpectation.fulfill() + } + + await fulfillment(of: [handleEventsMatchingExpectation], timeout: 1) + XCTAssertNotNil(StorageBackgroundEventsRegistry.continuation) + + let handleEventsNonMatchingExpectation = self.expectation(description: "Handle Background Events with Matching Identifiers") + Task { + let result = await withCheckedContinuation { continuation in + StorageBackgroundEventsRegistry.handleBackgroundEvents( + identifier: "identifier2", + continuation: continuation + ) + } + XCTAssertFalse(result) + handleEventsNonMatchingExpectation.fulfill() + } + await fulfillment(of: [handleEventsNonMatchingExpectation], timeout: 1) + delegate.urlSessionDidFinishEvents(forBackgroundURLSession: .shared) + await fulfillment(of: [finishEventsExpectation], timeout: 1) + XCTAssertNil(StorageBackgroundEventsRegistry.continuation) + } + + /// Given: A StorageServiceSessionDelegate + /// When: didBecomeInvalidWithError is invoked with a StorageError + /// Then: The service's resetURLSession is invoked + func testDidBecomeInvalid_withError_shouldResetURLSession() { + delegate.urlSession(.shared, didBecomeInvalidWithError: StorageError.accessDenied("", "", nil)) + XCTAssertEqual(service.resetURLSessionCount, 1) + } + + /// Given: A StorageServiceSessionDelegate + /// When: didBecomeInvalidWithError is invoked with a nil error + /// Then: The service's resetURLSession is invoked + func testDidBecomeInvalid_withNilError_shouldResetURLSession() { + delegate.urlSession(.shared, didBecomeInvalidWithError: nil) + XCTAssertEqual(service.resetURLSessionCount, 1) + } + + /// Given: A StorageServiceSessionDelegate and a StorageTransferTask with a NSError with a NSURLErrorCancelled reason + /// When: didComplete is invoked + /// Then: The task is not unregistered + func testDidComplete_withNSURLErrorCancelled_shouldNotCompleteTask() { + let task = URLSession.shared.dataTask(with: FileManager.default.temporaryDirectory) + let reasons = [ + NSURLErrorCancelledReasonBackgroundUpdatesDisabled, + NSURLErrorCancelledReasonInsufficientSystemResources, + NSURLErrorCancelledReasonUserForceQuitApplication, + NSURLErrorCancelled + ] + + for reason in reasons { + let expectation = self.expectation(description: "Did Complete With Error Reason \(reason)") + expectation.isInverted = true + let storageTask = StorageTransferTask( + transferType: .upload(onEvent: { _ in + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + let error: Error = NSError( + domain: NSURLErrorDomain, + code: NSURLErrorCancelled, + userInfo: [ + NSURLErrorBackgroundTaskCancelledReasonKey: reason + ] + ) + + delegate.urlSession(.shared, task: task, didCompleteWithError: error) + waitForExpectations(timeout: 1) + XCTAssertEqual(storageTask.status, .unknown) + XCTAssertEqual(service.unregisterCount, 0) + } + } + + /// Given: A StorageServiceSessionDelegate and a StorageTransferTask with a StorageError + /// When: didComplete is invoked + /// Then: The task status is set to error and it's unregistered + func testDidComplete_withError_shouldFailTask() { + let task = URLSession.shared.dataTask(with: FileManager.default.temporaryDirectory) + let expectation = self.expectation(description: "Did Complete With Error") + let storageTask = StorageTransferTask( + transferType: .upload(onEvent: { _ in + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + + delegate.urlSession(.shared, task: task, didCompleteWithError: StorageError.accessDenied("", "", nil)) + waitForExpectations(timeout: 1) + XCTAssertEqual(storageTask.status, .error) + XCTAssertEqual(service.unregisterCount, 1) + } + + /// Given: A StorageServiceSessionDelegate and a StorageTransferTask of type .upload + /// When: didSendBodyData is invoked + /// Then: An .inProcess event is reported, with the corresponding values + func testDidSendBodyData_upload_shouldSendInProcessEvent() { + let task = URLSession.shared.dataTask(with: FileManager.default.temporaryDirectory) + let expectation = self.expectation(description: "Did Send Body Data") + let storageTask = StorageTransferTask( + transferType: .upload(onEvent: { event in + guard case .inProcess(let progress) = event else { + XCTFail("Expected .inProcess event, got \(event)") + return + } + XCTAssertEqual(progress.totalUnitCount, 120) + XCTAssertEqual(progress.completedUnitCount, 100) + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + + delegate.urlSession( + .shared, + task: task, + didSendBodyData: 10, + totalBytesSent: 100, + totalBytesExpectedToSend: 120 + ) + + waitForExpectations(timeout: 1) + } + + /// Given: A StorageServiceSessionDelegate and a StorageTransferTask of type .multiPartUploadPart + /// When: didSendBodyData is invoked + /// Then: A .progressUpdated event is reported to the session + func testDidSendBodyData_multiPartUploadPart_shouldSendInProcessEvent() { + let task = URLSession.shared.dataTask(with: FileManager.default.temporaryDirectory) + let storageTask = StorageTransferTask( + transferType: .multiPartUploadPart( + uploadId: "uploadId", + partNumber: 3 + ), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + let multipartSession = MockStorageMultipartUploadSession( + client: MockMultipartUploadClient(), + bucket: "bucket", + key: "key", + onEvent: { event in } + ) + service.mockedMultipartUploadSession = multipartSession + + delegate.urlSession( + .shared, + task: task, + didSendBodyData: 10, + totalBytesSent: 100, + totalBytesExpectedToSend: 120 + ) + XCTAssertEqual(multipartSession.handleUploadPartCount, 1) + guard case .progressUpdated(let partNumber, let bytesTransferred, let taskIdentifier) = multipartSession.lastUploadEvent else { + XCTFail("Expected .progressUpdated event") + return + } + + XCTAssertEqual(partNumber, 3) + XCTAssertEqual(bytesTransferred, 10) + XCTAssertEqual(taskIdentifier, task.taskIdentifier) + } + + /// Given: A StorageServiceSessionDelegate and a StorageTransferTask of type .download + /// When: didWriteData is invoked + /// Then: An .inProcess event is reported, with the corresponding values + func testDidWriteData_shouldNotifyProgress() { + let task = URLSession.shared.downloadTask(with: FileManager.default.temporaryDirectory) + let expectation = self.expectation(description: "Did Write Data") + let storageTask = StorageTransferTask( + transferType: .download(onEvent: { event in + guard case .inProcess(let progress) = event else { + XCTFail("Expected .inProcess event, got \(event)") + return + } + XCTAssertEqual(progress.totalUnitCount, 300) + XCTAssertEqual(progress.completedUnitCount, 200) + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + + delegate.urlSession( + .shared, + downloadTask: task, + didWriteData: 15, + totalBytesWritten: 200, + totalBytesExpectedToWrite: 300 + ) + + waitForExpectations(timeout: 1) + } + + /// Given: A StorageServiceSessionDelegate and a URLSessionDownloadTask without a httpResponse + /// When: didFinishDownloadingTo is invoked + /// Then: No event is reported and the task is not completed + func testDiFinishDownloading_withError_shouldNotCompleteDownload() { + let task = URLSession.shared.downloadTask(with: FileManager.default.temporaryDirectory) + let expectation = self.expectation(description: "Did Finish Downloading") + expectation.isInverted = true + let storageTask = StorageTransferTask( + transferType: .download(onEvent: { _ in + expectation.fulfill() + }), + bucket: "bucket", + key: "key" + ) + service.mockedTask = storageTask + + delegate.urlSession( + .shared, + downloadTask: task, + didFinishDownloadingTo: FileManager.default.temporaryDirectory + ) + + waitForExpectations(timeout: 1) + XCTAssertEqual(service.completeDownloadCount, 0) + } +} + +private class AWSS3StorageServiceMock: AWSS3StorageService { + convenience init() throws { + try self.init( + authService: MockAWSAuthService(), + region: "region", + bucket: "bucket", + storageTransferDatabase: MockStorageTransferDatabase() + ) + } + + override var identifier: String { + return "identifier" + } + + var mockedTask: StorageTransferTask? = nil + override func findTask(taskIdentifier: TaskIdentifier) -> StorageTransferTask? { + return mockedTask + } + + var resetURLSessionCount = 0 + override func resetURLSession() { + resetURLSessionCount += 1 + } + + var unregisterCount = 0 + override func unregister(task: StorageTransferTask) { + unregisterCount += 1 + } + + var mockedMultipartUploadSession: StorageMultipartUploadSession? = nil + override func findMultipartUploadSession(uploadId: UploadID) -> StorageMultipartUploadSession? { + return mockedMultipartUploadSession + } + + var completeDownloadCount = 0 + override func completeDownload(taskIdentifier: TaskIdentifier, sourceURL: URL) { + completeDownloadCount += 1 + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift index bd599e718a..15719cde1c 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift @@ -219,7 +219,7 @@ class StorageTransferDatabaseTests: XCTestCase { XCTAssertEqual(database.tasksCount, 3) XCTAssertNotNil(originalTask.multipartUpload) - let exp = asyncExpectation(description: #function) + let exp = expectation(description: #function) var transferTaskPairs: StorageTransferTaskPairs? let urlSession = MockStorageURLSession(sessionTasks: sessionTasks) @@ -234,13 +234,11 @@ class StorageTransferDatabaseTests: XCTestCase { } catch { XCTFail("Error: \(error)") } - Task { - await exp.fulfill() - } + exp.fulfill() } } - await waitForExpectations([exp], timeout: 10.0) + await fulfillment(of: [exp], timeout: 10.0) XCTAssertNotNil(transferTaskPairs) XCTAssertEqual(transferTaskPairs?.count, 3) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferTaskTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferTaskTests.swift new file mode 100644 index 0000000000..c9b96aea8d --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferTaskTests.swift @@ -0,0 +1,658 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +@testable import AWSS3StoragePlugin +import XCTest + +class StorageTransferTaskTests: XCTestCase { + + // MARK: - Resume tests + /// Given: A StorageTransferTask with a sessionTask + /// When: resume is invoked + /// Then: an .initiated event is reported and the task set to .inProgress + func testResume_withSessionTask_shouldCallResume_andReportInitiatedEvent() { + let expectation = expectation(description: ".initiated event received on resume with only sessionTask") + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { event in + guard case .initiated(_) = event else { + XCTFail("Expected .initiated, got \(event)") + return + } + expectation.fulfill() + }), + sessionTask: sessionTask, + proxyStorageTask: nil + ) + XCTAssertEqual(task.status, .paused) + + task.resume() + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(sessionTask.resumeCount, 1) + XCTAssertEqual(task.status, .inProgress) + } + + /// Given: A StorageTransferTask with a proxyStorageTask + /// When: resume is invoked + /// Then: an .initiated event is reported and the task set to .inProgress + func testResume_withProxyStorageTask_shouldCallResume_andReportInitiatedEvent() { + let expectation = expectation(description: ".initiated event received on resume with only proxyStorageTask") + let sessionTask = MockSessionTask() + let storageTask = MockStorageTask() + let task = createTask( + transferType: .download(onEvent: { event in + guard case .initiated(_) = event else { + XCTFail("Expected .initiated, got \(event)") + return + } + expectation.fulfill() + }), + sessionTask: sessionTask, // Set the sessionTask to set task.status = .paused + proxyStorageTask: storageTask + ) + task.sessionTask = nil // Remove the session task + XCTAssertEqual(task.status, .paused) + + task.resume() + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(sessionTask.resumeCount, 0) + XCTAssertEqual(storageTask.resumeCount, 1) + XCTAssertEqual(task.status, .inProgress) + } + + /// Given: A StorageTransferTask with a sessionTask and a proxyStorageTask + /// When: resume is invoked + /// Then: an .initiated event is reported and the task set to .inProgress + func testResume_withSessionTask_andProxyStorageTask_shouldCallResume_andReportInitiatedEvent() { + let expectation = expectation(description: ".initiated event received on resume with sessionTask and proxyStorageTask") + let sessionTask = MockSessionTask() + let storageTask = MockStorageTask() + let task = createTask( + transferType: .multiPartUpload(onEvent: { event in + guard case .initiated(_) = event else { + XCTFail("Expected .initiated, got \(event)") + return + } + expectation.fulfill() + }), + sessionTask: sessionTask, + proxyStorageTask: storageTask + ) + XCTAssertEqual(task.status, .paused) + + task.resume() + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(sessionTask.resumeCount, 1) + XCTAssertEqual(storageTask.resumeCount, 0) + XCTAssertEqual(task.status, .inProgress) + } + + /// Given: A StorageTransferTask without a sessionTask and without a proxyStorageTask + /// When: resume is invoked + /// Then: No event is reported and the task is not to .inProgress + func testResume_withoutSessionTask_withoutProxyStorateTask_shouldNotCallResume_andNotReportEvent() { + let expectation = expectation(description: "no event is received on resume when no sessionTask nor proxyStorageTask") + expectation.isInverted = true + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .multiPartUpload(onEvent: { event in + XCTFail("No event expected, got \(event)") + expectation.fulfill() + }), + sessionTask: sessionTask, // Set the sessionTask to set task.status = .paused + proxyStorageTask: nil + ) + task.sessionTask = nil // Remove the sessionTask + XCTAssertEqual(task.status, .paused) + + task.resume() + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(sessionTask.resumeCount, 0) + XCTAssertEqual(task.status, .paused) + } + + /// Given: A StorageTransferTask with status not being paused + /// When: resume is invoked + /// Then: No event is reported and the task is not set to .inProgress + func testResume_withTaskNotPaused_shouldNotCallResume_andNotReportEvent() { + let expectation = expectation(description: "no event is received on resume when the session is not paused") + expectation.isInverted = true + let task = createTask( + transferType: .multiPartUpload(onEvent: { event in + XCTFail("No event expected, got \(event)") + expectation.fulfill() + }), + sessionTask: nil, // Do not set session task so task.status = .unknown + proxyStorageTask: nil + ) + XCTAssertEqual(task.status, .unknown) + + task.resume() + waitForExpectations(timeout: 0.5) + + XCTAssertEqual(task.status, .unknown) + } + + // MARK: - Suspend Tests + /// Given: A StorageTransferTask with a sessionTask + /// When: suspend is invoked + /// Then: The task is set to .paused + func testSuspend_withSessionTask_shouldCallSuspend() { + let sessionTask = MockSessionTask(state: .running) + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: nil + ) + // Set the task to inProgress by setting a multiPartUpload.creating + task.multipartUpload = .creating + XCTAssertEqual(task.status, .inProgress) + + task.suspend() + + XCTAssertEqual(sessionTask.suspendCount, 1) + XCTAssertEqual(task.status, .paused) + } + + /// Given: A StorageTransferTask with a proxyStorageTask + /// When: suspend is invoked + /// Then: The task is set to .paused + func testSuspend_withProxyStorageTask_shouldCallPause() { + let storageTask = MockStorageTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: nil, + proxyStorageTask: storageTask + ) + // Set the task to inProgress by setting a multiPartUpload.creating + task.multipartUpload = .creating + XCTAssertEqual(task.status, .inProgress) + + task.suspend() + + XCTAssertEqual(storageTask.pauseCount, 1) + XCTAssertEqual(task.status, .paused) + } + + /// Given: A StorageTransferTask with a sessionTask and a proxyStorageTask + /// When: suspend is invoked + /// Then: The task is set to .paused + func testSuspend_withSessionTask_andProxyStorageTask_shouldCallSuspend() { + let sessionTask = MockSessionTask(state: .running) + let storageTask = MockStorageTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: storageTask + ) + // Set the task to inProgress by setting a multiPartUpload.creating + task.multipartUpload = .creating + XCTAssertEqual(task.status, .inProgress) + + task.suspend() + + XCTAssertEqual(sessionTask.suspendCount, 1) + XCTAssertEqual(storageTask.pauseCount, 0) + XCTAssertEqual(task.status, .paused) + } + + /// Given: A StorageTransferTask without a sessionTask and without a proxyStorageTask + /// When: suspend is invoked + /// Then: The task remains .inProgress + func testSuspend_withoutSessionTask_andWithoutProxyStorageTask_shouldDoNothing() { + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: nil, + proxyStorageTask: nil + ) + // Set the task to inProgress by setting a multiPartUpload.creating + task.multipartUpload = .creating + XCTAssertEqual(task.status, .inProgress) + + task.suspend() + + XCTAssertEqual(task.status, .inProgress) + } + + /// Given: A StorageTransferTask with status completed + /// When: suspend is invoked + /// Then: The task remains completed + func testSuspend_withTaskNotInProgress_shouldDoNothing() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: nil + ) + // Set the task to completed by setting a multiPartUpload.completed + task.multipartUpload = .completed(uploadId: "") + XCTAssertEqual(task.status, .completed) + + task.suspend() + + XCTAssertEqual(sessionTask.suspendCount, 0) + XCTAssertEqual(task.status, .completed) + } + + /// Given: A StorageTransferTask + /// When: pause is invoked + /// Then: The task is set to .paused + func testPause_shouldCallSuspend() { + let sessionTask = MockSessionTask(state: .running) + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: nil + ) + // Set the task to inProgress by setting a multiPartUpload.creating + task.multipartUpload = .creating + XCTAssertEqual(task.status, .inProgress) + + task.pause() + + XCTAssertEqual(sessionTask.suspendCount, 1) + XCTAssertEqual(task.status, .paused) + } + + // MARK: - Cancel Tests + /// Given: A StorageTransferTask with a sessionTask + /// When: cancel is invoked + /// Then: The task is set to .cancelled + func testCancel_withSessionTask_shouldCancel() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: MockStorageTask() + ) + + // Set the task to completed by setting a multiPartUpload.completed + XCTAssertNotEqual(task.status, .completed) + + task.cancel() + + XCTAssertEqual(task.status, .cancelled) + XCTAssertEqual(sessionTask.cancelCount, 1) + XCTAssertNil(task.proxyStorageTask) + } + + /// Given: A StorageTransferTask with a proxyStorageTask + /// When: cancel is invoked + /// Then: The task is set to .cancelled + func testCancel_withProxyStorageTask_shouldCancel() { + let storageTask = MockStorageTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: nil, + proxyStorageTask: storageTask + ) + + task.cancel() + XCTAssertEqual(task.status, .cancelled) + XCTAssertEqual(storageTask.cancelCount, 1) + XCTAssertNil(task.proxyStorageTask) + } + + /// Given: A StorageTransferTask without a sessionTask and without a proxyStorageTask + /// When: cancel is invoked + /// Then: The task is not set to .cancelled + func testCancel_withoutSessionTask_withoutProxyStorageTask_shouldDoNothing() { + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: nil, + proxyStorageTask: nil + ) + + task.cancel() + XCTAssertNotEqual(task.status, .cancelled) + } + + /// Given: A StorageTransferTask with status completed + /// When: cancel is invoked + /// Then: The task is not set to .cancelled + func testCancel_withTaskCompleted_shouldDoNothing() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: MockStorageTask() + ) + // Set the task to completed by setting a multiPartUpload.completed + task.multipartUpload = .completed(uploadId: "") + XCTAssertEqual(task.status, .completed) + + task.cancel() + XCTAssertNotEqual(task.status, .cancelled) + XCTAssertEqual(sessionTask.cancelCount, 0) + XCTAssertNotNil(task.proxyStorageTask) + } + + // MARK: - Complete Tests + /// Given: A StorageTransferTask with sessionTask + /// When: complete is invoked + /// Then: The task is set to .completed + func testComplete_withSessionTask_shouldComplete() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: MockStorageTask() + ) + + task.complete() + XCTAssertEqual(task.status, .completed) + XCTAssertNil(task.proxyStorageTask) + } + + /// Given: A StorageTransferTask with status cancelled + /// When: complete is invoked + /// Then: The task is remains .cancelled + func testComplete_withTaskCancelled_shouldDoNothing() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: nil + ) + task.cancel() + XCTAssertEqual(task.status, .cancelled) + + task.complete() + XCTAssertEqual(task.status, .cancelled) + } + + /// Given: A StorageTransferTask with status completed + /// When: complete is invoked + /// Then: The task is remains .completed + func testComplete_withTaskCompleted_shouldDoNothing() { + let sessionTask = MockSessionTask() + let task = createTask( + transferType: .upload(onEvent: { _ in }), + sessionTask: sessionTask, + proxyStorageTask: MockStorageTask() + ) + // Set the task to completed by setting a multiPartUpload.completed + task.multipartUpload = .completed(uploadId: "") + XCTAssertEqual(task.status, .completed) + + task.complete() + + XCTAssertNotNil(task.proxyStorageTask) + } + + // MARK: - Fail Tests + /// Given: A StorageTransferTask + /// When: fail is invoked + /// Then: A .failed event is reported + func testFail_shouldReportFailEvent() { + let expectation = expectation(description: ".failed event received on fail") + let task = createTask( + transferType: .upload(onEvent: { event in + guard case .failed(_) = event else { + XCTFail("Expected .failed, got \(event)") + return + } + expectation.fulfill() + }), + sessionTask: MockSessionTask(), + proxyStorageTask: MockStorageTask() + ) + task.fail(error: CancellationError()) + + waitForExpectations(timeout: 0.5) + XCTAssertEqual(task.status, .error) + XCTAssertTrue(task.isFailed) + XCTAssertNil(task.proxyStorageTask) + } + + /// Given: A StorageTransferTask with status .failed + /// When: fail is invoked + /// Then: No event is reported + func testFail_withFailedTask_shouldNotReportEvent() { + let expectation = expectation(description: "event received on fail for failed task") + expectation.isInverted = true + let task = createTask( + transferType: .upload(onEvent: { event in + XCTFail("No event expected, got \(event)") + expectation.fulfill() + }), + sessionTask: MockSessionTask(), + proxyStorageTask: MockStorageTask() + ) + + // Set the task to error by setting a multiPartUpload.failed + task.multipartUpload = .failed(uploadId: "", parts: nil, error: CancellationError()) + XCTAssertEqual(task.status, .error) + task.fail(error: CancellationError()) + + waitForExpectations(timeout: 0.5) + XCTAssertNotNil(task.proxyStorageTask) + } + + // MARK: - Response Tests + /// Given: A StorageTransferTask with a valid responseData + /// When: responseText is invoked + /// Then: A string representing the data is returned + func testResponseText_withValidData_shouldReturnText() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil + ) + task.responseData = "Test".data(using: .utf8) + + XCTAssertEqual(task.responseText, "Test") + } + + /// Given: A StorageTransferTask with an invalid responseData + /// When: responseText is invoked + /// Then: nil is returned + func testResponseText_withInvalidData_shouldReturnNil() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil + ) + task.responseData = Data(count: 9999) + + XCTAssertNil(task.responseText) + } + + /// Given: A StorageTransferTask with a nil responseData + /// When: responseText is invoked + /// Then: nil is returned + func testResponseText_withoutData_shouldReturnNil() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil + ) + task.responseData = nil + + XCTAssertNil(task.responseText) + } + + // MARK: - PartNumber Tests + /// Given: A StorageTransferTask of type .multiPartUploadPart + /// When: partNumber is invoked + /// Then: The corresponding part number is returned + func testPartNumber_withMultipartUpload_shouldReturnPartNumber() { + let partNumber: PartNumber = 5 + let task = createTask( + transferType: .multiPartUploadPart(uploadId: "", partNumber: partNumber), + sessionTask: nil, + proxyStorageTask: nil + ) + + XCTAssertEqual(task.partNumber, partNumber) + } + + /// Given: A StorageTransferTask of type .upload + /// When: partNumber is invoked + /// Then: nil is returned + func testPartNumber_withOtherTransferType_shouldReturnNil() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil + ) + + XCTAssertNil(task.partNumber) + } + + // MARK: - HTTPRequestHeaders Tests + /// Given: A StorageTransferTask with requestHeaders + /// When: URLRequest.setHTTPRequestHeaders is invoked with said task + /// Then: The request includes the corresponding headers + func testHTTPRequestHeaders_shouldSetValues() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil, + requestHeaders: [ + "header1": "value1", + "header2": "value2" + ] + ) + + var request = URLRequest(url: FileManager.default.temporaryDirectory) + XCTAssertNil(request.allHTTPHeaderFields) + + request.setHTTPRequestHeaders(transferTask: task) + XCTAssertEqual(request.allHTTPHeaderFields?.count, 2) + XCTAssertEqual(request.allHTTPHeaderFields?["header1"], "value1") + XCTAssertEqual(request.allHTTPHeaderFields?["header2"], "value2") + } + + /// Given: A StorageTransferTask with nil requestHeaders + /// When: URLRequest.setHTTPRequestHeaders is invoked with said task + /// Then: The request does not adds headers + func testHTTPRequestHeaders_withoutHeaders_shouldDoNothing() { + let task = createTask( + transferType: .upload(onEvent: { _ in}), + sessionTask: nil, + proxyStorageTask: nil, + requestHeaders: nil + ) + + var request = URLRequest(url: FileManager.default.temporaryDirectory) + XCTAssertNil(request.allHTTPHeaderFields) + + request.setHTTPRequestHeaders(transferTask: task) + XCTAssertNil(request.allHTTPHeaderFields) + } +} + +extension StorageTransferTaskTests { + private func createTask( + transferType: StorageTransferType, + sessionTask: StorageSessionTask?, + proxyStorageTask: StorageTask?, + requestHeaders: [String: String]? = nil + ) -> StorageTransferTask { + let transferID = UUID().uuidString + let bucket = "BUCKET" + let key = UUID().uuidString + let task = StorageTransferTask( + transferID: transferID, + transferType: transferType, + bucket: bucket, + key: key, + location: nil, + contentType: nil, + requestHeaders: requestHeaders, + storageTransferDatabase: MockStorageTransferDatabase(), + logger: MockLogger() + ) + task.sessionTask = sessionTask + task.proxyStorageTask = proxyStorageTask + return task + } +} + + +private class MockStorageTask: StorageTask { + var pauseCount = 0 + func pause() { + pauseCount += 1 + } + + var resumeCount = 0 + func resume() { + resumeCount += 1 + } + + var cancelCount = 0 + func cancel() { + cancelCount += 1 + } +} + +private class MockSessionTask: StorageSessionTask { + let taskIdentifier: TaskIdentifier + let state: URLSessionTask.State + + init( + taskIdentifier: TaskIdentifier = 1, + state: URLSessionTask.State = .suspended + ) { + self.taskIdentifier = taskIdentifier + self.state = state + } + + var resumeCount = 0 + func resume() { + resumeCount += 1 + } + + var suspendCount = 0 + func suspend() { + suspendCount += 1 + } + + var cancelCount = 0 + func cancel() { + cancelCount += 1 + } +} + +class MockLogger: Logger { + var logLevel: LogLevel = .verbose + + func error(_ message: @autoclosure () -> String) { + print(message()) + } + + func error(error: Error) { + print(error) + } + + var warnCount = 0 + func warn(_ message: @autoclosure () -> String) { + print(message()) + warnCount += 1 + } + + var infoCount = 0 + func info(_ message: @autoclosure () -> String) { + print(message()) + infoCount += 1 + } + + func debug(_ message: @autoclosure () -> String) { + print(message()) + } + + func verbose(_ message: @autoclosure () -> String) { + print(message()) + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift index c70f56e7b4..295ffbd3f0 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift @@ -67,20 +67,6 @@ class StorageRequestUtilsGetterTests: XCTestCase { XCTAssertEqual(result, expected) } - // MARK: GetServiceMetadata tests - - func testGetServiceMetadataConstructsMetadataKeysWithS3Prefix() { - let metadata = ["key1": "value1", "key2": "value2"] - let results = StorageRequestUtils.getServiceMetadata(metadata) - XCTAssertNotNil(results) - - for (key, value) in results! { - XCTAssertNotNil(key) - XCTAssertNotNil(value) - XCTAssertTrue(key.contains(StorageRequestUtils.metadataKeyPrefix)) - } - } - // MARK: GetSize tests func testGetSizeForFileUploadSourceReturnsSize() throws { diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginBasicIntegrationTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginBasicIntegrationTests.swift index 52af5bfcf8..6a2a1ad007 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginBasicIntegrationTests.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginBasicIntegrationTests.swift @@ -67,7 +67,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { _ = try await Amplify.Storage.remove(key: key) // Only the remove operation results in an SDK request - XCTAssertEqual(requestRecorder.sdkRequests.map { $0.method} , [.delete]) + XCTAssertEqual(requestRecorder.sdkRequests.map { $0.method } , [.delete]) try assertUserAgentComponents(sdkRequests: requestRecorder.sdkRequests) XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, ["PUT"]) @@ -173,7 +173,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { /// Then: The operation completes successfully with the data retrieved func testDownloadDataToMemory() async throws { let key = UUID().uuidString - await uploadData(key: key, data: key.data(using: .utf8)!) + try await uploadData(key: key, data: key.data(using: .utf8)!) _ = try await Amplify.Storage.downloadData(key: key, options: .init()).value _ = try await Amplify.Storage.remove(key: key) } @@ -185,7 +185,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { let key = UUID().uuidString let timestamp = String(Date().timeIntervalSince1970) let timestampData = timestamp.data(using: .utf8)! - await uploadData(key: key, data: timestampData) + try await uploadData(key: key, data: timestampData) let filePath = NSTemporaryDirectory() + key + ".tmp" let fileURL = URL(fileURLWithPath: filePath) removeIfExists(fileURL) @@ -209,7 +209,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { /// Then: The operation completes successfully with the URL retrieved func testGetRemoteURL() async throws { let key = UUID().uuidString - await uploadData(key: key, dataString: key) + try await uploadData(key: key, dataString: key) let remoteURL = try await Amplify.Storage.getURL(key: key) @@ -244,7 +244,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { } // A S3 HeadObject call is expected - XCTAssertEqual(requestRecorder.sdkRequests.map { $0.method} , [.head]) + XCTAssert(requestRecorder.sdkRequests.map(\.method).allSatisfy { $0 == .head }) try assertUserAgentComponents(sdkRequests: requestRecorder.sdkRequests) XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, []) @@ -274,7 +274,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { func testListFromPublic() async throws { let key = UUID().uuidString let expectedMD5Hex = "\"\(key.md5())\"" - await uploadData(key: key, dataString: key) + try await uploadData(key: key, dataString: key) let options = StorageListRequest.Options(accessLevel: .guest, targetIdentityId: nil, path: key) @@ -311,7 +311,7 @@ class AWSS3StoragePluginBasicIntegrationTests: AWSS3StoragePluginTestBase { for i in 0.. StorageUploadDataTask? { - return await wait(name: "Upload Task created") { - return Amplify.Storage.uploadData(key: key, data: data) - } + Amplify.Storage.uploadData(key: key, data: data) } func downloadTask(key: String) async -> StorageDownloadDataTask? { - return await wait(name: "Upload Task created") { - return Amplify.Storage.downloadData(key: key) - } + Amplify.Storage.downloadData(key: key) } - func uploadData(key: String, data: Data) async { - let completeInvoked = asyncExpectation(description: "Completed is invoked") - let result = await wait(with: completeInvoked, timeout: 60) { - return try await Amplify.Storage.uploadData(key: key, data: data, options: nil).value + func uploadData(key: String, data: Data) async throws { + let completeInvoked = expectation(description: "Completed is invoked") + Task { + let result = try await Amplify.Storage.uploadData( + key: key, + data: data, + options: nil + ).value + + XCTAssertNotNil(result) + completeInvoked.fulfill() } - XCTAssertNotNil(result) + + await fulfillment(of: [completeInvoked], timeout: 60) } func remove(key: String, accessLevel: StorageAccessLevel? = nil) async { @@ -121,35 +126,35 @@ class AWSS3StoragePluginTestBase: XCTestCase { return } - let registerFirstUserComplete = asyncExpectation(description: "register firt user completed") + let registerFirstUserComplete = expectation(description: "register firt user completed") Task { do { try await AuthSignInHelper.signUpUser(username: AWSS3StoragePluginTestBase.user1, password: AWSS3StoragePluginTestBase.password, email: AWSS3StoragePluginTestBase.email1) Self.isFirstUserSignedUp = true - await registerFirstUserComplete.fulfill() + registerFirstUserComplete.fulfill() } catch { XCTFail("Failed to Sign up user: \(error)") - await registerFirstUserComplete.fulfill() + registerFirstUserComplete.fulfill() } } - let registerSecondUserComplete = asyncExpectation(description: "register second user completed") + let registerSecondUserComplete = expectation(description: "register second user completed") Task { do { try await AuthSignInHelper.signUpUser(username: AWSS3StoragePluginTestBase.user2, password: AWSS3StoragePluginTestBase.password, email: AWSS3StoragePluginTestBase.email2) Self.isSecondUserSignedUp = true - await registerSecondUserComplete.fulfill() + registerSecondUserComplete.fulfill() } catch { XCTFail("Failed to Sign up user: \(error)") - await registerSecondUserComplete.fulfill() + registerSecondUserComplete.fulfill() } } - await waitForExpectations([registerFirstUserComplete, registerSecondUserComplete], + await fulfillment(of: [registerFirstUserComplete, registerSecondUserComplete], timeout: TestCommonConstants.networkTimeout) } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift new file mode 100644 index 0000000000..9d729d496c --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift @@ -0,0 +1,253 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Amplify +import AWSPluginsCore +import AWSS3StoragePlugin +import AWSS3 + +class AWSS3StoragePluginUploadMetadataTestCase: AWSS3StoragePluginTestBase { + // MARK: - Tests + + /// Given: `StorageUploadFileRequest.Options` with `metadata` + /// When: Uploading a file below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadSmallFileWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-small-file-with-metadata", UUID().uuidString) + let options = StorageUploadFileRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + let fileURL = temporaryFile(named: key, data: data(mb: 1)) + _ = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadFileRequest.Options` with `metadata` + /// When: Uploading a file above the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadLargeFileWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-large-file-with-metadata", UUID().uuidString) + let options = StorageUploadFileRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + let fileURL = temporaryFile(named: key, data: data(mb: 7)) + _ = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with `metadata` + /// When: Uploading data with a size below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadSmallDataWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-small-data-with-metadata", UUID().uuidString) + let options = StorageUploadDataRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 1), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with `metadata` + /// When: Uploading data with a size below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadLargeDataWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-large-data-with-metadata", UUID().uuidString) + let options = StorageUploadDataRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 7), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with multiple + /// `metadata` key-value pairs. + /// When: Calling uploading an object via `uploadData` + /// Then: That object's headers (retrieved via `HeadObject`) should contain + /// all key-value pairs`metadata` + func test_uploadWithMultipleMetadataPairs() async throws { + // Include metadata in upload file request + let range = (1...11) + let metadata = zip(range, range.dropFirst()) + .map { tuple -> (String, String) in + (.init(tuple.0), .init(tuple.0)) + } + .reduce(into: [String: String]()) { dict, pair in + let (key, value) = pair + dict[key] = value + } + + let options = StorageUploadDataRequest.Options( + metadata: metadata + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 1), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata, + metadata, + """ + Expected `headObject().metadata` to equal + user-defined metadata \(metadata). + Instead, received metadata: \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + // MARK: - Helper Functions + private func data(mb: Int) -> Data { + Data( + repeating: 0xff, + count: 1_024 * 1_024 * mb + ) + } + + private func temporaryFile(named key: String, data: Data) -> URL { + let filePath = "\(NSTemporaryDirectory() + key).tmp" + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile( + atPath: filePath, + contents: data, + attributes: nil + ) + return fileURL + } + + private func headObject(key: String) async throws -> HeadObjectOutputResponse { + let plugin = try Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") + let storagePlugin = try XCTUnwrap( + plugin as? AWSS3StoragePlugin, + "Cast to `AWSS3StoragePlugin` failed" + ) + let s3Client = storagePlugin.getEscapeHatch() + let bucket = try AWSS3StoragePluginTestBase.getBucketFromConfig( + forResource: "amplifyconfiguration" + ) + let input = HeadObjectInput( + bucket: bucket, + key: key + ) + + return try await s3Client.headObject(input: input) + } +} + + diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift index b471c1cd5b..5131743d06 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift @@ -29,7 +29,7 @@ class TestConfigHelper { } static func retrieve(forResource: String) throws -> Data { - guard let path = Bundle(for: self).path(forResource: forResource, ofType: "json") else { + guard let path = Bundle.main.path(forResource: forResource, ofType: "json") else { throw "Could not retrieve configuration file: \(forResource)" } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginGetDataResumabilityTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginGetDataResumabilityTests.swift index 06811cfe36..40f9e863b9 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginGetDataResumabilityTests.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginGetDataResumabilityTests.swift @@ -18,160 +18,158 @@ class AWSS3StoragePluginDownloadDataResumabilityTests: AWSS3StoragePluginTestBas /// When: Call the get API then pause /// Then: The operation is stalled (no progress, completed, or failed event) func testDownloadDataAndPause() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - let data = AWSS3StoragePluginTestBase.smallDataObject - let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value - XCTAssertEqual(uploadKey, key) - - Self.logger.debug("Downloading data") - let task = Amplify.Storage.downloadData(key: key) - - let didPause = asyncExpectation(description: "did pause") - let didContinue = asyncExpectation(description: "did continue", isInverted: true) - Task { - var paused = false - var progressAfterPause = 0 - for await progress in await task.progress { - Self.logger.debug("progress: \(progress)") - if !paused { - paused = true - task.pause() - await didPause.fulfill() - } else { - progressAfterPause += 1 - if progressAfterPause > 1 { - await didContinue.fulfill() - } + let key = UUID().uuidString + let data = AWSS3StoragePluginTestBase.smallDataObject + let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value + XCTAssertEqual(uploadKey, key) + + Self.logger.debug("Downloading data") + let task = Amplify.Storage.downloadData(key: key) + + let didPause = expectation(description: "did pause") + let didContinue = expectation(description: "did continue") + didContinue.isInverted = true + Task { + var paused = false + var progressAfterPause = 0 + for await progress in await task.progress { + Self.logger.debug("progress: \(progress)") + if !paused { + paused = true + task.pause() + didPause.fulfill() + } else { + progressAfterPause += 1 + if progressAfterPause > 1 { + didContinue.fulfill() } } } - await waitForExpectations([didPause], timeout: TestCommonConstants.networkTimeout) - await waitForExpectations([didContinue], timeout: 5) - - let completeInvoked = asyncExpectation(description: "Download is completed", isInverted: true) - let downloadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } + } + await fulfillment(of: [didPause], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [didContinue], timeout: 5) + + let completeInvoked = expectation(description: "Download is completed") + completeInvoked.isInverted = true + let downloadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result + } - Self.logger.debug("Cancelling download task") - task.cancel() - await waitForExpectations([completeInvoked]) + Self.logger.debug("Cancelling download task") + task.cancel() + await fulfillment(of: [completeInvoked], timeout: 1) - let downloadData = try? await downloadTask.value - XCTAssertNil(downloadData) + let downloadData = try? await downloadTask.value + XCTAssertNil(downloadData) - // clean up - Self.logger.debug("Cleaning up after download task") - try await Amplify.Storage.remove(key: key) - } + // clean up + Self.logger.debug("Cleaning up after download task") + try await Amplify.Storage.remove(key: key) } /// Given: A data object in storage /// When: Call the downloadData API, pause, and then resume the operation /// Then: The operation should complete successfully func testDownloadDataAndPauseThenResume() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - let data = AWSS3StoragePluginTestBase.smallDataObject - let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value - XCTAssertEqual(uploadKey, key) - - let task = Amplify.Storage.downloadData(key: key) - - let progressInvoked = asyncExpectation(description: "Progress invoked") - Task { - var progressInvokedCalled = false - for await progress in await task.progress { - Self.logger.debug("Download progress: \(progress.fractionCompleted)") - if !progressInvokedCalled, progress.fractionCompleted > 0.1 { - progressInvokedCalled = true - await progressInvoked.fulfill() - } + let key = UUID().uuidString + let data = AWSS3StoragePluginTestBase.smallDataObject + let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value + XCTAssertEqual(uploadKey, key) + + let task = Amplify.Storage.downloadData(key: key) + + let progressInvoked = expectation(description: "Progress invoked") + Task { + var progressInvokedCalled = false + for await progress in await task.progress { + Self.logger.debug("Download progress: \(progress.fractionCompleted)") + if !progressInvokedCalled, progress.fractionCompleted > 0.1 { + progressInvokedCalled = true + progressInvoked.fulfill() } } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + } + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) - Self.logger.debug("Pausing download task") - task.pause() + Self.logger.debug("Pausing download task") + task.pause() - Self.logger.debug("Sleeping") - try await Task.sleep(seconds: 0.25) + Self.logger.debug("Sleeping") + try await Task.sleep(seconds: 0.25) - let completeInvoked = asyncExpectation(description: "Download is completed") - let downloadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } + let completeInvoked = expectation(description: "Download is completed") + let downloadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result + } - Self.logger.debug("Resuming download task") - task.resume() + Self.logger.debug("Resuming download task") + task.resume() - await waitForExpectations([completeInvoked], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) - Self.logger.debug("Waiting to finish download task") - let downloadData = try await downloadTask.value - XCTAssertEqual(downloadData, data) + Self.logger.debug("Waiting to finish download task") + let downloadData = try await downloadTask.value + XCTAssertEqual(downloadData, data) - // clean up - Self.logger.debug("Cleaning up after download task") - try await Amplify.Storage.remove(key: key) - } + // clean up + Self.logger.debug("Cleaning up after download task") + try await Amplify.Storage.remove(key: key) } /// Given: A data object in storage /// When: Call the get API then cancel the operation, /// Then: The operation should not complete or fail. func testDownloadDataAndCancel() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - let data = AWSS3StoragePluginTestBase.smallDataObject - let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value - XCTAssertEqual(uploadKey, key) - - Self.logger.debug("Downloading data") - let task = Amplify.Storage.downloadData(key: key) - - let didCancel = asyncExpectation(description: "did cancel") - let didContinue = asyncExpectation(description: "did continue", isInverted: true) - Task { - var cancelled = false - var continued = false - for await progress in await task.progress { - if !cancelled, progress.fractionCompleted > 0.1 { - cancelled = true - task.cancel() - await didCancel.fulfill() - } else if cancelled, !continued, progress.fractionCompleted > 0.5 { - continued = true - await didContinue.fulfill() - } + let key = UUID().uuidString + let data = AWSS3StoragePluginTestBase.smallDataObject + let uploadKey = try await Amplify.Storage.uploadData(key: key, data: data).value + XCTAssertEqual(uploadKey, key) + + Self.logger.debug("Downloading data") + let task = Amplify.Storage.downloadData(key: key) + + let didCancel = expectation(description: "did cancel") + let didContinue = expectation(description: "did continue") + didContinue.isInverted = true + Task { + var cancelled = false + var continued = false + for await progress in await task.progress { + if !cancelled, progress.fractionCompleted > 0.1 { + cancelled = true + task.cancel() + didCancel.fulfill() + } else if cancelled, !continued, progress.fractionCompleted > 0.5 { + continued = true + didContinue.fulfill() } } - await waitForExpectations([didCancel], timeout: TestCommonConstants.networkTimeout) - await waitForExpectations([didContinue], timeout: 5) - - let completeInvoked = asyncExpectation(description: "Download is completed", isInverted: true) - let downloadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } + } + await fulfillment(of: [didCancel], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [didContinue], timeout: 5) + + let completeInvoked = expectation(description: "Download is completed") + completeInvoked.isInverted = true + let downloadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result + } - await waitForExpectations([completeInvoked]) + await fulfillment(of: [completeInvoked], timeout: 1) - Self.logger.debug("Waiting for download to complete") - let downloadData = try? await downloadTask.value - XCTAssertNil(downloadData) + Self.logger.debug("Waiting for download to complete") + let downloadData = try? await downloadTask.value + XCTAssertNil(downloadData) - // clean up - Self.logger.debug("Cleaning up after download task") - try await Amplify.Storage.remove(key: key) + // clean up + Self.logger.debug("Cleaning up after download task") + try await Amplify.Storage.remove(key: key) - Self.logger.debug("Done") - } + Self.logger.debug("Done") } } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginPutDataResumabilityTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginPutDataResumabilityTests.swift index 78a6625792..8d76b67bef 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginPutDataResumabilityTests.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/ResumabilityTests/AWSS3StoragePluginPutDataResumabilityTests.swift @@ -17,143 +17,142 @@ class AWSS3StoragePluginUploadDataResumabilityTests: AWSS3StoragePluginTestBase /// When: Call the put API and pause the operation /// Then: The operation is stalled (no progress, completed, or failed event) func testUploadLargeDataThenPause() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - Self.logger.debug("Uploading data") - let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) - - let didPause = asyncExpectation(description: "did pause") - let didContinue = asyncExpectation(description: "did continue", isInverted: true) - Task { - var paused = false - var progressAfterPause = 0 - for await progress in await task.progress { - Self.logger.debug("progress: \(progress)") - if !paused { - paused = true - task.pause() - await didPause.fulfill() - } else { - progressAfterPause += 1 - if progressAfterPause > 1 { - await didContinue.fulfill() - } + let key = UUID().uuidString + Self.logger.debug("Uploading data") + let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) + + let didPause = expectation(description: "did pause") + let didContinue = expectation(description: "did continue") + didContinue.isInverted = true + Task { + var paused = false + var progressAfterPause = 0 + for await progress in await task.progress { + Self.logger.debug("progress: \(progress)") + if !paused { + paused = true + task.pause() + didPause.fulfill() + } else { + progressAfterPause += 1 + if progressAfterPause > 1 { + didContinue.fulfill() } } } - await waitForExpectations([didPause], timeout: TestCommonConstants.networkTimeout) - await waitForExpectations([didContinue], timeout: 5) - - let completeInvoked = asyncExpectation(description: "Upload is completed", isInverted: true) - let uploadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } + } + await fulfillment(of: [didPause], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [didContinue], timeout: 5) + + let completeInvoked = expectation(description: "Upload is completed") + completeInvoked.isInverted = true + let uploadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result + } - Self.logger.debug("Cancelling upload task") - task.cancel() - await waitForExpectations([completeInvoked]) + Self.logger.debug("Cancelling upload task") + task.cancel() + await fulfillment(of: [completeInvoked], timeout: 1) - let uploadKey = try? await uploadTask.value - XCTAssertNil(uploadKey) + let uploadKey = try? await uploadTask.value + XCTAssertNil(uploadKey) - // clean up - Self.logger.debug("Cleaning up after upload task") - try await Amplify.Storage.remove(key: key) - } + // clean up + Self.logger.debug("Cleaning up after upload task") + try await Amplify.Storage.remove(key: key) } /// Given: A large data object to upload /// When: Call the put API, pause, and then resume the operation, /// Then: The operation should complete successfully func testUploadLargeDataAndPauseThenResume() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - Self.logger.debug("Uploading data") - let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) - - let progressInvoked = asyncExpectation(description: "Progress invoked") - Task { - for await progress in await task.progress { - if progress.fractionCompleted > 0.1 { - await progressInvoked.fulfill() - break - } + let key = UUID().uuidString + Self.logger.debug("Uploading data") + let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) + + let progressInvoked = expectation(description: "Progress invoked") + Task { + for await progress in await task.progress { + if progress.fractionCompleted > 0.1 { + progressInvoked.fulfill() + break } } - await waitForExpectations([progressInvoked], timeout: TestCommonConstants.networkTimeout) + } + await fulfillment(of: [progressInvoked], timeout: TestCommonConstants.networkTimeout) - Self.logger.debug("Pausing upload task") - task.pause() + Self.logger.debug("Pausing upload task") + task.pause() - Self.logger.debug("Sleeping") - try await Task.sleep(seconds: 0.25) + Self.logger.debug("Sleeping") + try await Task.sleep(seconds: 0.25) - let completeInvoked = asyncExpectation(description: "Upload is completed") - let uploadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } + let completeInvoked = expectation(description: "Upload is completed") + let uploadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result + } - Self.logger.debug("Resuming upload task") - task.resume() - await waitForExpectations([completeInvoked], timeout: TestCommonConstants.networkTimeout) + Self.logger.debug("Resuming upload task") + task.resume() + await fulfillment(of: [completeInvoked], timeout: TestCommonConstants.networkTimeout) - Self.logger.debug("Waiting to finish upload task") - let uploadKey = try await uploadTask.value - XCTAssertEqual(uploadKey, key) + Self.logger.debug("Waiting to finish upload task") + let uploadKey = try await uploadTask.value + XCTAssertEqual(uploadKey, key) - // clean up - Self.logger.debug("Cleaning up after upload task") - try await Amplify.Storage.remove(key: key) - } + // clean up + Self.logger.debug("Cleaning up after upload task") + try await Amplify.Storage.remove(key: key) } /// Given: A large data object to upload /// When: Call the put API, pause, and then resume tthe operation, /// Then: The operation should complete successfully func testUploadLargeDataAndCancel() async throws { - try await testTask(timeout: 600) { - let key = UUID().uuidString - Self.logger.debug("Uploading data") - let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) - - let didCancel = asyncExpectation(description: "did cancel") - let didContinue = asyncExpectation(description: "did continue", isInverted: true) - Task { - var cancelled = false - var continued = false - for await progress in await task.progress { - if !cancelled, progress.fractionCompleted > 0.1 { - cancelled = true - task.cancel() - await didCancel.fulfill() - } else if cancelled, !continued, progress.fractionCompleted > 0.5 { - continued = true - await didContinue.fulfill() - } + let key = UUID().uuidString + Self.logger.debug("Uploading data") + let task = Amplify.Storage.uploadData(key: key, data: AWSS3StoragePluginTestBase.largeDataObject) + + let didCancel = expectation(description: "did cancel") + let didContinue = expectation(description: "did continue") + didContinue.isInverted = true + Task { + var cancelled = false + var continued = false + for await progress in await task.progress { + if !cancelled, progress.fractionCompleted > 0.1 { + cancelled = true + task.cancel() + didCancel.fulfill() + } else if cancelled, !continued, progress.fractionCompleted > 0.5 { + continued = true + didContinue.fulfill() } } - await waitForExpectations([didCancel], timeout: TestCommonConstants.networkTimeout) - await waitForExpectations([didContinue], timeout: 5) - - let completeInvoked = asyncExpectation(description: "Upload is completed", isInverted: true) - let uploadTask = Task { - let result = try await task.value - await completeInvoked.fulfill() - return result - } - - await waitForExpectations([completeInvoked]) + } + await fulfillment(of: [didCancel], timeout: TestCommonConstants.networkTimeout) + await fulfillment(of: [didContinue], timeout: 5) - let uploadKey = try? await uploadTask.value - XCTAssertNil(uploadKey) + let completeInvoked = expectation(description: "Upload is completed") + completeInvoked.isInverted = true - // clean up - Self.logger.debug("Cleaning up after upload task") - try await Amplify.Storage.remove(key: key) + let uploadTask = Task { + let result = try await task.value + completeInvoked.fulfill() + return result } + + await fulfillment(of: [completeInvoked], timeout: 1) + + let uploadKey = try? await uploadTask.value + XCTAssertNil(uploadKey) + + // clean up + Self.logger.debug("Cleaning up after upload task") + try await Amplify.Storage.remove(key: key) } } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj index 944c6db9d6..d6c2a2c236 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 56043E9329FC4D33003E3424 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C0382101A0E23943FDF4CB /* amplifyconfiguration.json */; }; 562B9AA42A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */; }; 562B9AA52A0D734E00A96FC6 /* AWSS3StoragePluginRequestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */; }; + 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */; }; 681D7D4E2A4263C200F7C310 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB06F28BEAF1500C8A6EB /* ContentView.swift */; }; 681D7D4F2A4263C200F7C310 /* StorageHostAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB06D28BEAF1500C8A6EB /* StorageHostAppApp.swift */; }; 681D7D552A4263E500F7C310 /* AuthSignInHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */; }; @@ -39,7 +40,6 @@ 681D7D792A4264D200F7C310 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D782A4264D200F7C310 /* AWSCognitoAuthPlugin */; }; 681D7D7B2A4264D200F7C310 /* AWSS3StoragePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D7A2A4264D200F7C310 /* AWSS3StoragePlugin */; }; 681D7D852A426FF500F7C310 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C0382101A0E23943FDF4CB /* amplifyconfiguration.json */; }; - 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */; }; 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; }; 681DFEB328E748270000C36A /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; }; 681DFEB428E748270000C36A /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */; }; @@ -59,6 +59,7 @@ 68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */; }; 68828E4728C27745006E7C0A /* AWSS3StoragePluginPutDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08828BEAF8E00C8A6EB /* AWSS3StoragePluginPutDataResumabilityTests.swift */; }; 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08B28BEAF8E00C8A6EB /* AWSS3StoragePluginGetDataResumabilityTests.swift */; }; + 901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */; }; 97914BA32955798D002000EA /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; }; 97914BA52955798D002000EA /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; }; 97914BB02955798D002000EA /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */; }; @@ -101,10 +102,10 @@ 0311113828EBEEA700D58441 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; 031BC3F228EC9B2C0047B2E8 /* AppIcon.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = AppIcon.xcassets; sourceTree = ""; }; 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginRequestRecorder.swift; sourceTree = ""; }; + 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginAccelerateIntegrationTests.swift; sourceTree = ""; }; 681D7D392A42637700F7C310 /* StorageWatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StorageWatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 681D7D502A4263CA00F7C310 /* StorageWatchApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageWatchApp.entitlements; sourceTree = ""; }; 681D7D6C2A4263E500F7C310 /* AWSS3StoragePluginIntegrationTestsWatch.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTestsWatch.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginAccelerateIntegrationTests.swift; sourceTree = ""; }; 681DFEAF28E748270000C36A /* AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncTesting.swift; sourceTree = ""; }; 681DFEB028E748270000C36A /* AsyncExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncExpectation.swift; sourceTree = ""; }; 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+AsyncTesting.swift"; sourceTree = ""; }; @@ -127,6 +128,7 @@ 684FB0A928BEB07200C8A6EB /* AWSS3StoragePluginIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSignInHelper.swift; sourceTree = ""; }; 684FB0C528BEB84800C8A6EB /* StorageHostApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageHostApp.entitlements; sourceTree = ""; }; + 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadMetadataTestCase.swift; sourceTree = ""; }; 97914B972955797E002000EA /* StorageStressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageStressTests.swift; sourceTree = ""; }; 97914BB92955798D002000EA /* StorageStressTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StorageStressTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 97914BBA29557A52002000EA /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -265,6 +267,7 @@ 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */, 684FB07E28BEAF8E00C8A6EB /* AWSS3StoragePluginTestBase.swift */, 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */, + 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */, 684FB08728BEAF8E00C8A6EB /* ResumabilityTests */, ); path = AWSS3StoragePluginIntegrationTests; @@ -608,6 +611,7 @@ 684FB0C328BEB45600C8A6EB /* AuthSignInHelper.swift in Sources */, 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */, 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */, + 901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */, 681DFEB328E748270000C36A /* AsyncExpectation.swift in Sources */, 68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */, 684FB0B528BEB08900C8A6EB /* AWSS3StoragePluginAccessLevelTests.swift in Sources */, diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f284cdc5c9..603f949096 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift.git", "state" : { - "revision" : "6feec6c3787877807aa9a00fad09591b96752376", - "version" : "0.6.1" + "revision" : "997904873945e074aaf5c51ea968d9a84684525a", + "version" : "0.13.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift.git", "state" : { - "revision" : "24bae88a2391fe75da8a940a544d1ef6441f5321", - "version" : "0.13.0" + "revision" : "ace826dbfe96e7e3103fe7f45f815b8a590bcf21", + "version" : "0.26.0" } }, { @@ -57,10 +57,10 @@ { "identity" : "smithy-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/smithy-swift.git", + "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "7b28da158d92cd06a3549140d43b8fbcf64a94a6", - "version" : "0.15.0" + "revision" : "eed3f3d8e5aa704fcd60bb227b0fc89bf3328c42", + "version" : "0.30.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MaxDesiatov/XMLCoder.git", "state" : { - "revision" : "b1e944cbd0ef33787b13f639a5418d55b3bed501", - "version" : "0.17.1" + "revision" : "80b4a1646399b8e4e0ce80711653476a85bd5e37", + "version" : "0.17.0" } } ], diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/xcshareddata/xcschemes/StorageHostApp.xcscheme b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/xcshareddata/xcschemes/StorageHostApp.xcscheme index 432789d9ad..dbaee112f9 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/xcshareddata/xcschemes/StorageHostApp.xcscheme +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/xcshareddata/xcschemes/StorageHostApp.xcscheme @@ -43,7 +43,7 @@ parallelizable = "YES"> diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift index d1990781a5..ffc25e3d2c 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift @@ -59,63 +59,74 @@ final class StorageStressTests: XCTestCase { /// When: Upload the data simultaneously from 10 tasks /// Then: The operation completes successfully func testUploadMultipleSmallDataObjects() async { - let uploadExpectation = asyncExpectation(description: "Small data object uploaded successfully", - expectedFulfillmentCount: concurrencyLimit) - let removeExpectation = asyncExpectation(description: "Data object removed successfully", - expectedFulfillmentCount: concurrencyLimit) + let uploadExpectation = expectation(description: "Small data object uploaded successfully") + uploadExpectation.expectedFulfillmentCount = concurrencyLimit + + let removeExpectation = expectation(description: "Data object removed successfully") + removeExpectation.expectedFulfillmentCount = concurrencyLimit + for _ in 1...concurrencyLimit { Task { do { let key = UUID().uuidString - let uploadKey = try await Amplify.Storage.uploadData(key: key, - data: smallDataObjectForStressTest, - options: nil).value + let uploadKey = try await Amplify.Storage.uploadData( + key: key, + data: smallDataObjectForStressTest, + options: nil + ).value + XCTAssertEqual(uploadKey, key) - await uploadExpectation.fulfill() + uploadExpectation.fulfill() try await Amplify.Storage.remove(key: key) - await removeExpectation.fulfill() + removeExpectation.fulfill() } catch { XCTFail("Error: \(error)") } } } - await waitForExpectations([uploadExpectation, removeExpectation], timeout: 60) + await fulfillment(of: [uploadExpectation, removeExpectation], timeout: 60) } /// Given: A very large data object(100MB) /// When: Upload the data /// Then: The operation completes successfully func testUploadLargeDataObject() async { - let uploadExpectation = asyncExpectation(description: "Large data object uploaded successfully") - let removeExpectation = asyncExpectation(description: "Data object removed successfully") + let uploadExpectation = expectation(description: "Large data object uploaded successfully") + let removeExpectation = expectation(description: "Data object removed successfully") do { let key = UUID().uuidString - let uploadKey = try await Amplify.Storage.uploadData(key: key, - data: largeDataObjectForStressTest, - options: nil).value + let uploadKey = try await Amplify.Storage.uploadData( + key: key, + data: largeDataObjectForStressTest, + options: nil + ).value + XCTAssertEqual(uploadKey, key) - await uploadExpectation.fulfill() + uploadExpectation.fulfill() try await Amplify.Storage.remove(key: key) - await removeExpectation.fulfill() + removeExpectation.fulfill() } catch { XCTFail("Error: \(error)") } - await waitForExpectations([uploadExpectation, removeExpectation], timeout: 180) + await fulfillment(of: [uploadExpectation, removeExpectation], timeout: 180) } /// Given: An object in storage /// When: Object is downloaded simultaneously from 10 tasks /// Then: The operation completes successfully with the data retrieved func testDownloadMultipleSmallDataObjects() async { - let downloadExpectation = asyncExpectation(description: "Data object downloaded successfully", - expectedFulfillmentCount: concurrencyLimit) - let uploadExpectation = asyncExpectation(description: "Data object uploaded successfully", - expectedFulfillmentCount: concurrencyLimit) - let removeExpectation = asyncExpectation(description: "Data object removed successfully", - expectedFulfillmentCount: concurrencyLimit) + let downloadExpectation = expectation(description: "Data object downloaded successfully") + downloadExpectation.expectedFulfillmentCount = concurrencyLimit + + let uploadExpectation = expectation(description: "Data object uploaded successfully") + uploadExpectation.expectedFulfillmentCount = concurrencyLimit + + let removeExpectation = expectation(description: "Data object removed successfully") + removeExpectation.expectedFulfillmentCount = concurrencyLimit + for _ in 1...concurrencyLimit { Task { let key = UUID().uuidString @@ -123,43 +134,46 @@ final class StorageStressTests: XCTestCase { data: smallDataObjectForStressTest, options: nil).value XCTAssertEqual(uploadKey, key) - await uploadExpectation.fulfill() + uploadExpectation.fulfill() - let _ = try await Amplify.Storage.downloadData(key: key, options: .init()).value - await downloadExpectation.fulfill() + _ = try await Amplify.Storage.downloadData(key: key, options: .init()).value + downloadExpectation.fulfill() try await Amplify.Storage.remove(key: key) - await removeExpectation.fulfill() + removeExpectation.fulfill() } } - await waitForExpectations([downloadExpectation, uploadExpectation, removeExpectation], timeout: 60) + await fulfillment(of: [downloadExpectation, uploadExpectation, removeExpectation], timeout: 60) } /// Given: A very large data object(100MB) in storage /// When: Download the data /// Then: The operation completes successfully func testDownloadLargeDataObject() async { - let downloadExpectation = asyncExpectation(description: "Data object downloaded successfully") - let uploadExpectation = asyncExpectation(description: "Data object uploaded successfully") - let removeExpectation = asyncExpectation(description: "Data object removed successfully") + let downloadExpectation = expectation(description: "Data object downloaded successfully") + let uploadExpectation = expectation(description: "Data object uploaded successfully") + let removeExpectation = expectation(description: "Data object removed successfully") do { let key = UUID().uuidString - let uploadKey = try await Amplify.Storage.uploadData(key: key, - data: largeDataObjectForStressTest, - options: nil).value + let uploadKey = try await Amplify.Storage.uploadData( + key: key, + data: largeDataObjectForStressTest, + options: nil + ).value + XCTAssertEqual(uploadKey, key) - await uploadExpectation.fulfill() + uploadExpectation.fulfill() let _ = try await Amplify.Storage.downloadData(key: key, options: .init()).value - await downloadExpectation.fulfill() + downloadExpectation.fulfill() try await Amplify.Storage.remove(key: key) - await removeExpectation.fulfill() + removeExpectation.fulfill() } catch { XCTFail("Error: \(error)") } - await waitForExpectations([uploadExpectation, removeExpectation], timeout: 180) + await fulfillment(of: [downloadExpectation, uploadExpectation, removeExpectation], timeout: 180) } @@ -170,35 +184,35 @@ final class StorageStressTests: XCTestCase { return } - let registerFirstUserComplete = asyncExpectation(description: "register firt user completed") + let registerFirstUserComplete = expectation(description: "register firt user completed") Task { do { try await AuthSignInHelper.signUpUser(username: AWSS3StoragePluginTestBase.user1, password: AWSS3StoragePluginTestBase.password, email: AWSS3StoragePluginTestBase.email1) Self.isFirstUserSignedUp = true - await registerFirstUserComplete.fulfill() + registerFirstUserComplete.fulfill() } catch { XCTFail("Failed to Sign up user: \(error)") - await registerFirstUserComplete.fulfill() + registerFirstUserComplete.fulfill() } } - let registerSecondUserComplete = asyncExpectation(description: "register second user completed") + let registerSecondUserComplete = expectation(description: "register second user completed") Task { do { try await AuthSignInHelper.signUpUser(username: AWSS3StoragePluginTestBase.user2, password: AWSS3StoragePluginTestBase.password, email: AWSS3StoragePluginTestBase.email2) Self.isSecondUserSignedUp = true - await registerSecondUserComplete.fulfill() + registerSecondUserComplete.fulfill() } catch { XCTFail("Failed to Sign up user: \(error)") - await registerSecondUserComplete.fulfill() + registerSecondUserComplete.fulfill() } } - await waitForExpectations([registerFirstUserComplete, registerSecondUserComplete], + await fulfillment(of: [registerFirstUserComplete, registerSecondUserComplete], timeout: TestCommonConstants.networkTimeout) } diff --git a/AmplifyTestCommon/Mocks/MockCredentialsProvider.swift b/AmplifyTestCommon/Mocks/MockCredentialsProvider.swift index 732384c615..90a1984d6b 100644 --- a/AmplifyTestCommon/Mocks/MockCredentialsProvider.swift +++ b/AmplifyTestCommon/Mocks/MockCredentialsProvider.swift @@ -8,7 +8,7 @@ import AWSClientRuntime import Foundation -class MockCredentialsProvider: CredentialsProvider { +class MockCredentialsProvider: CredentialsProviding { func getCredentials() async throws -> AWSCredentials { return AWSCredentials( accessKey: "accessKey", diff --git a/AmplifyTests/CategoryTests/API/APICategoryClientGraphQLTests.swift b/AmplifyTests/CategoryTests/API/APICategoryClientGraphQLTests.swift index 55e6c11611..09a85d35ab 100644 --- a/AmplifyTests/CategoryTests/API/APICategoryClientGraphQLTests.swift +++ b/AmplifyTests/CategoryTests/API/APICategoryClientGraphQLTests.swift @@ -35,14 +35,14 @@ class APICategoryClientGraphQLTests: XCTestCase { } let request = GraphQLRequest(document: "", variables: nil, responseType: JSONValue.self) - let queryCompleted = asyncExpectation(description: "query completed") + let queryCompleted = expectation(description: "query completed") Task { _ = try await Amplify.API.query(request: request) - await queryCompleted.fulfill() + queryCompleted.fulfill() } - await waitForExpectations([queryCompleted], timeout: 0.5) - - await waitForExpectations(timeout: 0.5) + + await fulfillment(of: [queryCompleted], timeout: 0.5) + await fulfillment(of: [methodWasInvokedOnPlugin], timeout: 0.5) } func testMutate() async throws { @@ -56,14 +56,13 @@ class APICategoryClientGraphQLTests: XCTestCase { let request = GraphQLRequest(document: "", variables: nil, responseType: JSONValue.self) - let mutateCompleted = asyncExpectation(description: "mutate completed") + let mutateCompleted = expectation(description: "mutate completed") Task { _ = try await Amplify.API.mutate(request: request) - await mutateCompleted.fulfill() + mutateCompleted.fulfill() } - await waitForExpectations([mutateCompleted], timeout: 0.5) - - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [mutateCompleted], timeout: 0.5) + await fulfillment(of: [methodWasInvokedOnPlugin], timeout: 0.5) } // MARK: - Utilities diff --git a/AmplifyTests/CategoryTests/API/APICategoryConfigurationTests.swift b/AmplifyTests/CategoryTests/API/APICategoryConfigurationTests.swift index 08146071e5..c15801e609 100644 --- a/AmplifyTests/CategoryTests/API/APICategoryConfigurationTests.swift +++ b/AmplifyTests/CategoryTests/API/APICategoryConfigurationTests.swift @@ -115,14 +115,14 @@ class APICategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) - let getCompleted = asyncExpectation(description: "get completed") + let getCompleted = expectation(description: "get completed") Task { _ = try await Amplify.API.get(request: RESTRequest()) - await getCompleted.fulfill() + getCompleted.fulfill() } - await waitForExpectations([getCompleted], timeout: 0.5) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [getCompleted], timeout: 0.5) + await fulfillment(of: [methodInvokedOnDefaultPlugin], timeout: 1) } // TODO: this test is disabled for now since `catchBadInstruction` only takes in closure @@ -184,15 +184,20 @@ class APICategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) - let getCompleted = asyncExpectation(description: "get completed") + let getCompleted = expectation(description: "get completed") Task { let plugin = try Amplify.API.getPlugin(for: "MockSecondAPICategoryPlugin") _ = try await plugin.get(request: RESTRequest()) - await getCompleted.fulfill() + getCompleted.fulfill() } - await waitForExpectations([getCompleted], timeout: 0.5) - - await waitForExpectations(timeout: 1.0) + await fulfillment( + of: [ + getCompleted, + methodShouldBeInvokedOnSecondPlugin, + methodShouldNotBeInvokedOnDefaultPlugin + ], + timeout: 1.0 + ) } func testCanConfigurePluginDirectly() throws { diff --git a/AmplifyTests/CategoryTests/Auth/AuthCategoryConfigurationTests.swift b/AmplifyTests/CategoryTests/Auth/AuthCategoryConfigurationTests.swift index 21e47000ae..f9c3b38696 100644 --- a/AmplifyTests/CategoryTests/Auth/AuthCategoryConfigurationTests.swift +++ b/AmplifyTests/CategoryTests/Auth/AuthCategoryConfigurationTests.swift @@ -200,7 +200,7 @@ class AuthCategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) _ = try await Amplify.Auth.getPlugin(for: "MockSecondAuthCategoryPlugin") .update(oldPassword: "current", to: "new", options: nil) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [methodShouldBeInvokedOnSecondPlugin], timeout: 1.0) } /// Test if we get error when trying default plugin when multiple plugin added. diff --git a/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryClientAPITests.swift b/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryClientAPITests.swift index ec0be7eb0f..5a7ad073b2 100644 --- a/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryClientAPITests.swift +++ b/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryClientAPITests.swift @@ -36,15 +36,15 @@ class DataStoreCategoryClientAPITests: XCTestCase { } } - let saveSuccess = asyncExpectation(description: "saved successful") + let saveSuccess = expectation(description: "saved successful") Task { _ = try await Amplify.DataStore.save(TestModel.make()) - await saveSuccess.fulfill() + saveSuccess.fulfill() } - await waitForExpectations([saveSuccess], timeout: 0.5) - - - await waitForExpectations(timeout: 0.5) + await fulfillment( + of: [saveSuccess, methodWasInvokedOnPlugin], + timeout: 1 + ) } } diff --git a/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryConfigurationTests.swift b/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryConfigurationTests.swift index 58ad36daea..871f372c46 100644 --- a/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryConfigurationTests.swift +++ b/AmplifyTests/CategoryTests/DataStore/DataStoreCategoryConfigurationTests.swift @@ -30,7 +30,6 @@ class DataStoreCategoryConfigurationTests: XCTestCase { } try Amplify.add(plugin: plugin) - let amplifyConfig = AmplifyConfiguration() try Amplify.configure(amplifyConfig) @@ -134,15 +133,16 @@ class DataStoreCategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) - let saveSuccess = asyncExpectation(description: "save successful") + let saveSuccess = expectation(description: "save successful") Task { _ = try await Amplify.DataStore.save(TestModel.make()) - await saveSuccess.fulfill() + saveSuccess.fulfill() } - await waitForExpectations([saveSuccess], timeout: 1.0) - - await waitForExpectations(timeout: 1.0) + await fulfillment( + of: [saveSuccess, methodInvokedOnDefaultPlugin], + timeout: 1.0 + ) } // TODO: this test is disabled for now since `catchBadInstruction` only takes in closure @@ -204,15 +204,21 @@ class DataStoreCategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) - let saveSuccess = asyncExpectation(description: "save success") + let saveSuccess = expectation(description: "save success") Task { _ = try await Amplify.DataStore.getPlugin(for: "MockSecondDataStoreCategoryPlugin") .save(TestModel.make(), where: nil) - await saveSuccess.fulfill() + saveSuccess.fulfill() } - await waitForExpectations([saveSuccess], timeout: 1.0) - - await waitForExpectations(timeout: 1.0) + + await fulfillment( + of: [ + saveSuccess, + methodShouldBeInvokedOnSecondPlugin, + methodShouldNotBeInvokedOnDefaultPlugin + ], + timeout: 1.0 + ) } func testCanConfigurePluginDirectly() throws { diff --git a/AmplifyTests/CategoryTests/DataStore/Model/ListPaginationTests.swift b/AmplifyTests/CategoryTests/DataStore/Model/ListPaginationTests.swift index 703bdfeafc..83215fc64e 100644 --- a/AmplifyTests/CategoryTests/DataStore/Model/ListPaginationTests.swift +++ b/AmplifyTests/CategoryTests/DataStore/Model/ListPaginationTests.swift @@ -17,7 +17,7 @@ extension ListTests { XCTFail("Should not be loaded") return } - let fetchComplete = asyncExpectation(description: "fetch completed") + let fetchComplete = expectation(description: "fetch completed") Task { try await list.fetch() @@ -25,16 +25,16 @@ extension ListTests { XCTFail("Should be loaded") return } - await fetchComplete.fulfill() + fetchComplete.fulfill() } - await waitForExpectations([fetchComplete], timeout: 1) + await fulfillment(of: [fetchComplete], timeout: 1) - let fetchComplete2 = asyncExpectation(description: "fetch completed") + let fetchComplete2 = expectation(description: "fetch completed") Task { try await list.fetch() - await fetchComplete2.fulfill() + fetchComplete2.fulfill() } - await waitForExpectations([fetchComplete2], timeout: 1) + await fulfillment(of: [fetchComplete2], timeout: 1) } func testFetchFailure() async throws { @@ -46,7 +46,7 @@ extension ListTests { XCTFail("Should not be loaded") return } - let fetchCompleted = asyncExpectation(description: "fetch completed") + let fetchCompleted = expectation(description: "fetch completed") Task { do { try await list.fetch() @@ -58,10 +58,10 @@ extension ListTests { XCTFail("Should not be loaded") return } - await fetchCompleted.fulfill() + fetchCompleted.fulfill() } - await waitForExpectations([fetchCompleted], timeout: 1.0) + await fulfillment(of: [fetchCompleted], timeout: 1.0) } func testHasNextPageSuccess() async throws { @@ -72,7 +72,7 @@ extension ListTests { XCTFail("Should not be loaded") return } - let fetchCompleted = asyncExpectation(description: "fetch completed") + let fetchCompleted = expectation(description: "fetch completed") Task { try await list.fetch() guard case .loaded = list.loadedState else { @@ -80,9 +80,9 @@ extension ListTests { return } XCTAssertTrue(list.hasNextPage()) - await fetchCompleted.fulfill() + fetchCompleted.fulfill() } - await waitForExpectations([fetchCompleted], timeout: 1.0) + await fulfillment(of: [fetchCompleted], timeout: 1.0) } func testGetNextPageSuccess() async throws { @@ -94,23 +94,23 @@ extension ListTests { return } try await list.fetch() - let getNextPageSuccess = asyncExpectation(description: "getNextPage successful") + let getNextPageSuccess = expectation(description: "getNextPage successful") Task { _ = try await list.getNextPage() - await getNextPageSuccess.fulfill() + getNextPageSuccess.fulfill() } - await waitForExpectations([getNextPageSuccess], timeout: 1.0) + await fulfillment(of: [getNextPageSuccess], timeout: 1.0) guard case .loaded = list.loadedState else { XCTFail("Should be loaded") return } - let getNextPageSuccess2 = asyncExpectation(description: "getNextPage successful") + let getNextPageSuccess2 = expectation(description: "getNextPage successful") Task { _ = try await list.getNextPage() - await getNextPageSuccess2.fulfill() + getNextPageSuccess2.fulfill() } - await waitForExpectations([getNextPageSuccess2], timeout: 1.0) + await fulfillment(of: [getNextPageSuccess2], timeout: 1.0) } @@ -122,14 +122,14 @@ extension ListTests { XCTFail("Should not be loaded") return } - let fetchCompleted = asyncExpectation(description: "fetch completed") + let fetchCompleted = expectation(description: "fetch completed") Task { try await list.fetch() - await fetchCompleted.fulfill() + fetchCompleted.fulfill() } - await waitForExpectations([fetchCompleted], timeout: 1.0) + await fulfillment(of: [fetchCompleted], timeout: 1.0) - let getNextPageSuccess = asyncExpectation(description: "getNextPage successful") + let getNextPageSuccess = expectation(description: "getNextPage successful") Task { do { _ = try await list.getNextPage() @@ -137,8 +137,8 @@ extension ListTests { } catch { XCTAssertNotNil(error) } - await getNextPageSuccess.fulfill() + getNextPageSuccess.fulfill() } - await waitForExpectations([getNextPageSuccess], timeout: 1.0) + await fulfillment(of: [getNextPageSuccess], timeout: 1.0) } } diff --git a/AmplifyTests/CategoryTests/DataStore/Model/ListTests.swift b/AmplifyTests/CategoryTests/DataStore/Model/ListTests.swift index bf81e4b425..26cefa0422 100644 --- a/AmplifyTests/CategoryTests/DataStore/Model/ListTests.swift +++ b/AmplifyTests/CategoryTests/DataStore/Model/ListTests.swift @@ -131,12 +131,12 @@ class ListTests: XCTestCase { let serializedData = try ListTests.encode(json: data) let list = try ListTests.decode(serializedData, responseType: BasicModel.self) - let fetchSuccess = asyncExpectation(description: "fetch successful") + let fetchSuccess = expectation(description: "fetch successful") Task { try await list.fetch() - await fetchSuccess.fulfill() + fetchSuccess.fulfill() } - await waitForExpectations([fetchSuccess], timeout: 1.0) + await fulfillment(of: [fetchSuccess], timeout: 1.0) XCTAssertEqual(list.count, 2) XCTAssertEqual(list.startIndex, 0) @@ -148,7 +148,7 @@ class ListTests: XCTestCase { list.makeIterator().forEach { _ in iterateSuccess.fulfill() } - wait(for: [iterateSuccess], timeout: 1) + await fulfillment(of: [iterateSuccess], timeout: 1) let json = try? ListTests.toJSON(list: list) XCTAssertEqual(json, """ [{\"id\":\"1\"},{\"id\":\"2\"}] @@ -165,12 +165,12 @@ class ListTests: XCTestCase { let serializedData = try ListTests.encode(json: data) let list = try ListTests.decode(serializedData, responseType: BasicModel.self) XCTAssertNotNil(list) - let fetchSuccess = asyncExpectation(description: "fetch successful") + let fetchSuccess = expectation(description: "fetch successful") Task { try await list.fetch() - await fetchSuccess.fulfill() + fetchSuccess.fulfill() } - await waitForExpectations([fetchSuccess], timeout: 1.0) + await fulfillment(of: [fetchSuccess], timeout: 1.0) XCTAssertEqual(list.count, 2) XCTAssertEqual(list.startIndex, 0) XCTAssertEqual(list.endIndex, 2) @@ -181,7 +181,7 @@ class ListTests: XCTestCase { list.makeIterator().forEach { _ in iterateSuccess.fulfill() } - await waitForExpectations(timeout: 1) + await fulfillment(of: [iterateSuccess], timeout: 1) XCTAssertFalse(list.listProvider.hasNextPage()) do { _ = try await list.listProvider.getNextPage() @@ -197,12 +197,12 @@ class ListTests: XCTestCase { let serializedData = try ListTests.encode(json: data) let list = try ListTests.decode(serializedData, responseType: BasicModel.self) XCTAssertNotNil(list) - let fetchSuccess = asyncExpectation(description: "fetch successful") + let fetchSuccess = expectation(description: "fetch successful") Task { try await list.fetch() - await fetchSuccess.fulfill() + fetchSuccess.fulfill() } - await waitForExpectations([fetchSuccess], timeout: 1.0) + await fulfillment(of: [fetchSuccess], timeout: 1.0) XCTAssertEqual(list.count, 0) let json = try? ListTests.toJSON(list: list) XCTAssertEqual(json, "[]") @@ -217,7 +217,7 @@ class ListTests: XCTestCase { XCTFail("Should not be loaded") return } - let fetchCompleted = asyncExpectation(description: "fetch completed") + let fetchCompleted = expectation(description: "fetch completed") Task { do { _ = try await list.fetch() @@ -225,9 +225,9 @@ class ListTests: XCTestCase { } catch { XCTAssertNotNil(error) } - await fetchCompleted.fulfill() + fetchCompleted.fulfill() } - await waitForExpectations([fetchCompleted], timeout: 1.0) + await fulfillment(of: [fetchCompleted], timeout: 1.0) } // MARK: - Helpers diff --git a/AmplifyTests/CategoryTests/Hub/AmplifyOperationHubTests.swift b/AmplifyTests/CategoryTests/Hub/AmplifyOperationHubTests.swift index ae1c51b53a..baa6629239 100644 --- a/AmplifyTests/CategoryTests/Hub/AmplifyOperationHubTests.swift +++ b/AmplifyTests/CategoryTests/Hub/AmplifyOperationHubTests.swift @@ -48,7 +48,7 @@ class AmplifyOperationHubTests: XCTestCase { operation.doMockDispatch() - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [listenerWasInvoked], timeout: 1.0) } /// Given: A configured system diff --git a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeHubListenToOperationTests.swift b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeHubListenToOperationTests.swift index 7f589d0d26..199249a8af 100644 --- a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeHubListenToOperationTests.swift +++ b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeHubListenToOperationTests.swift @@ -63,11 +63,11 @@ class AutoUnsubscribeHubListenToOperationTests: XCTestCase { // } // // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForCompleted], timeout: 0.1) // // operation.doMockProgress() // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) } /// - Given: An Amplify operation class @@ -104,11 +104,11 @@ class AutoUnsubscribeHubListenToOperationTests: XCTestCase { // } // // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForFailed], timeout: 0.1) // // operation.doMockProgress() // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) } /// - Given: An Amplify operation class @@ -162,12 +162,12 @@ class AutoUnsubscribeHubListenToOperationTests: XCTestCase { // } // // operation.doMockProgress() -// wait(for: [listenerWasInvokedForInProcess], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess], timeout: 0.1) // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForCompleted], timeout: 0.1) // // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForFailed], timeout: 0.1) } /// - Given: An Amplify operation class @@ -222,10 +222,10 @@ class AutoUnsubscribeHubListenToOperationTests: XCTestCase { // // operation.doMockProgress() // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) // // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForCompleted], timeout: 0.1) } } diff --git a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeOperationTests.swift b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeOperationTests.swift index c37de11dd3..f8d60a087a 100644 --- a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeOperationTests.swift +++ b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/AutoUnsubscribeOperationTests.swift @@ -64,11 +64,11 @@ class AutoUnsubscribeOperationTests: XCTestCase { // } // // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForCompleted], timeout: 0.1) // // operation.doMockProgress() // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) } /// - Given: An Amplify operation class @@ -106,11 +106,11 @@ class AutoUnsubscribeOperationTests: XCTestCase { // } // // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForFailed], timeout: 0.1) // // operation.doMockProgress() // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) } /// - Given: An Amplify operation class @@ -149,10 +149,10 @@ class AutoUnsubscribeOperationTests: XCTestCase { // // operation.doMockProgress() // operation.doMockDispatch() -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForCompleted], timeout: 0.1) // // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForFailed], timeout: 0.1) } /// - Given: An Amplify operation class @@ -191,10 +191,10 @@ class AutoUnsubscribeOperationTests: XCTestCase { // // operation.doMockProgress() // operation.doMockDispatch(result: .failure(StorageError.accessDenied("", ""))) -// wait(for: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForInProcess, listenerWasInvokedForFailed], timeout: 0.1) // // operation.doMockProgress() -// wait(for: [listenerWasInvokedForCompleted], timeout: 0.1) +// await fulfillment(of: [listenerWasInvokedForCompleted], timeout: 0.1) } } diff --git a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginConcurrencyTests.swift b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginConcurrencyTests.swift index eb07b39b2a..0f8284a3d5 100644 --- a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginConcurrencyTests.swift +++ b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginConcurrencyTests.swift @@ -79,7 +79,7 @@ class DefaultHubPluginConcurrencyTests: XCTestCase { } } - await waitForExpectations(timeout: 5.0) + await fulfillment(of: messagesReceived, timeout: 5.0) } } diff --git a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginCustomChannelTests.swift b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginCustomChannelTests.swift index 76248f4893..0e7e0892f5 100644 --- a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginCustomChannelTests.swift +++ b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginCustomChannelTests.swift @@ -51,7 +51,7 @@ class DefaultHubPluginCustomChannelTests: XCTestCase { plugin.dispatch(to: .custom("CustomChannel1"), payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [eventReceived], timeout: 0.5) } /// Given: A listener to a custom channel @@ -72,7 +72,7 @@ class DefaultHubPluginCustomChannelTests: XCTestCase { plugin.dispatch(to: .custom("CustomChannel2"), payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [eventReceived], timeout: 0.5) } /// Given: Multiple listeners to a custom channel @@ -102,7 +102,7 @@ class DefaultHubPluginCustomChannelTests: XCTestCase { plugin.dispatch(to: .custom("CustomChannel1"), payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [listener1Invoked, listener2Invoked], timeout: 0.5) } } diff --git a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginTests.swift b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginTests.swift index 04a909b2cc..e6a3bf08ab 100644 --- a/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginTests.swift +++ b/AmplifyTests/CategoryTests/Hub/DefaultPluginTests/DefaultHubPluginTests.swift @@ -69,7 +69,7 @@ class DefaultHubPluginTests: XCTestCase { } plugin.dispatch(to: .storage, payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [expectedMessageReceived], timeout: 0.5) } /// Given: The default Hub plugin with a registered listener @@ -92,7 +92,7 @@ class DefaultHubPluginTests: XCTestCase { } plugin.dispatch(to: .storage, payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [messageReceived], timeout: 0.5) } /// Given: A subscription token from a previous call to the default Hub plugin's `listen` method @@ -129,7 +129,7 @@ class DefaultHubPluginTests: XCTestCase { } plugin.dispatch(to: .storage, payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [try XCTUnwrap(currentExpectation)], timeout: 0.5) plugin.removeListener(unsubscribeToken) try? await Task.sleep(seconds: 0.01) @@ -147,7 +147,7 @@ class DefaultHubPluginTests: XCTestCase { XCTAssertFalse(isStillRegistered.get(), "Should not be registered after removeListener") plugin.dispatch(to: .storage, payload: HubPayload(eventName: "TEST_EVENT")) - await waitForExpectations(timeout: 0.5) + await fulfillment(of: [try XCTUnwrap(currentExpectation)], timeout: 0.5) } /// Given: The default Hub plugin diff --git a/AmplifyTests/CategoryTests/Notifications/Push/PushNotificationsCategoryClientAPITests.swift b/AmplifyTests/CategoryTests/Notifications/Push/PushNotificationsCategoryClientAPITests.swift index 137579cb39..bd5d44a782 100644 --- a/AmplifyTests/CategoryTests/Notifications/Push/PushNotificationsCategoryClientAPITests.swift +++ b/AmplifyTests/CategoryTests/Notifications/Push/PushNotificationsCategoryClientAPITests.swift @@ -44,7 +44,7 @@ class PushNotificationsCategoryClientAPITests: XCTestCase { } try await category.identifyUser(userId: "test") - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [methodInvoked], timeout: 1.0) } func testRegisterDeviceToken_shouldSucceed() async throws { @@ -58,7 +58,7 @@ class PushNotificationsCategoryClientAPITests: XCTestCase { } try await category.registerDevice(apnsToken: data) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [methodInvoked], timeout: 1.0) } func testRecordNotificationReceived_shouldSucceed() async throws { @@ -72,7 +72,7 @@ class PushNotificationsCategoryClientAPITests: XCTestCase { } try await category.recordNotificationReceived(userInfo) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [methodInvoked], timeout: 1.0) } #if !os(tvOS) @@ -87,7 +87,7 @@ class PushNotificationsCategoryClientAPITests: XCTestCase { } try await category.recordNotificationOpened(response) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [methodInvoked], timeout: 1.0) } #endif diff --git a/AmplifyTests/CategoryTests/Storage/StorageCategoryConfigurationTests.swift b/AmplifyTests/CategoryTests/Storage/StorageCategoryConfigurationTests.swift index 53ded46ea9..f653699f38 100644 --- a/AmplifyTests/CategoryTests/Storage/StorageCategoryConfigurationTests.swift +++ b/AmplifyTests/CategoryTests/Storage/StorageCategoryConfigurationTests.swift @@ -102,12 +102,10 @@ class StorageCategoryConfigurationTests: XCTestCase { func testCanUseDefaultPluginIfOnlyOnePlugin() async throws { let plugin = MockStorageCategoryPlugin() - let methodInvokedOnDefaultPlugin = asyncExpectation(description: "test method invoked on default plugin") + let methodInvokedOnDefaultPlugin = expectation(description: "test method invoked on default plugin") plugin.listeners.append { message in if message == "downloadData" { - Task { - await methodInvokedOnDefaultPlugin.fulfill() - } + methodInvokedOnDefaultPlugin.fulfill() } } try Amplify.add(plugin: plugin) @@ -122,11 +120,12 @@ class StorageCategoryConfigurationTests: XCTestCase { func testCanUseSpecifiedPlugin() async throws { let plugin1 = MockStorageCategoryPlugin() let methodShouldNotBeInvokedOnDefaultPlugin = - asyncExpectation(description: "test method should not be invoked on default plugin", isInverted: true) + expectation(description: "test method should not be invoked on default plugin") + methodShouldNotBeInvokedOnDefaultPlugin.isInverted = true plugin1.listeners.append { message in if message == "downloadData" { Task { - await methodShouldNotBeInvokedOnDefaultPlugin.fulfill() + methodShouldNotBeInvokedOnDefaultPlugin.fulfill() } } } @@ -134,11 +133,11 @@ class StorageCategoryConfigurationTests: XCTestCase { let plugin2 = MockSecondStorageCategoryPlugin() let methodShouldBeInvokedOnSecondPlugin = - asyncExpectation(description: "test method should be invoked on second plugin") + expectation(description: "test method should be invoked on second plugin") plugin2.listeners.append { message in if message == "downloadData" { Task { - await methodShouldBeInvokedOnSecondPlugin.fulfill() + methodShouldBeInvokedOnSecondPlugin.fulfill() } } } @@ -158,7 +157,7 @@ class StorageCategoryConfigurationTests: XCTestCase { _ = try Amplify.Storage.getPlugin(for: "MockSecondStorageCategoryPlugin") .downloadData(key: "", options: nil) - await waitForExpectations([methodShouldNotBeInvokedOnDefaultPlugin, methodShouldBeInvokedOnSecondPlugin]) + await fulfillment(of: [methodShouldNotBeInvokedOnDefaultPlugin, methodShouldBeInvokedOnSecondPlugin]) } func testPreconditionFailureInvokingWithMultiplePlugins() async throws { @@ -192,17 +191,17 @@ class StorageCategoryConfigurationTests: XCTestCase { func testCanConfigurePluginDirectly() async throws { let plugin = MockStorageCategoryPlugin() let configureShouldBeInvokedFromCategory = - asyncExpectation(description: "Configure should be invoked by Amplify.configure()") + expectation(description: "Configure should be invoked by Amplify.configure()") let configureShouldBeInvokedDirectly = - asyncExpectation(description: "Configure should be invoked by getPlugin().configure()") + expectation(description: "Configure should be invoked by getPlugin().configure()") var invocationCount = 0 plugin.listeners.append { message in if message == "configure(using:)" { invocationCount += 1 switch invocationCount { - case 1: Task { await configureShouldBeInvokedFromCategory.fulfill() } - case 2: Task { await configureShouldBeInvokedDirectly.fulfill() } + case 1: configureShouldBeInvokedFromCategory.fulfill() + case 2: configureShouldBeInvokedDirectly.fulfill() default: XCTFail("Expected configure() to be called only two times, but got \(invocationCount)") } } @@ -218,7 +217,7 @@ class StorageCategoryConfigurationTests: XCTestCase { try Amplify.configure(amplifyConfig) try Amplify.Storage.getPlugin(for: "MockStorageCategoryPlugin").configure(using: true) - await waitForExpectations([configureShouldBeInvokedFromCategory, configureShouldBeInvokedDirectly]) + await fulfillment(of: [configureShouldBeInvokedFromCategory, configureShouldBeInvokedDirectly]) } func testPreconditionFailureInvokingBeforeConfig() async throws { diff --git a/AmplifyTests/CoreTests/AmplifyAsyncSequenceTests.swift b/AmplifyTests/CoreTests/AmplifyAsyncSequenceTests.swift index 1c01e5416c..044282d285 100644 --- a/AmplifyTests/CoreTests/AmplifyAsyncSequenceTests.swift +++ b/AmplifyTests/CoreTests/AmplifyAsyncSequenceTests.swift @@ -122,64 +122,64 @@ final class AmplifyAsyncSequenceTests: XCTestCase { // parent task is canceled while reducing values from sequence // before a value is sent which should result in a sum of zero let input = 2006 - let reduced = asyncExpectation(description: "reduced") - let done = asyncExpectation(description: "done") + let reduced = expectation(description: "reduced") + let done = expectation(description: "done") let channel = AmplifyAsyncSequence() let task = Task { let sum = await channel.reduce(0, +) - await reduced.fulfill() + reduced.fulfill() return sum } // cancel before value is sent task.cancel() - await waitForExpectations([reduced]) + await fulfillment(of: [reduced]) channel.send(input) Task { let output = await task.value XCTAssertNotEqual(input, output) XCTAssertEqual(0, output) - await done.fulfill() + done.fulfill() } - await waitForExpectations([done]) + await fulfillment(of: [done]) } func testThrowingChannelCancelled() async throws { // parent task is canceled while reducing values from sequence // before a value is sent which should result in a sum of zero let input = 2006 - let reduced = asyncExpectation(description: "reduced") - let done = asyncExpectation(description: "done") + let reduced = expectation(description: "reduced") + let done = expectation(description: "done") let channel = AmplifyAsyncThrowingSequence() let task = Task { let sum = try await channel.reduce(0, +) - await reduced.fulfill() + reduced.fulfill() return sum } // cancel before any value is sent task.cancel() - await waitForExpectations([reduced]) + await fulfillment(of: [reduced]) channel.send(input) Task { let output = try await task.value XCTAssertNotEqual(input, output) XCTAssertEqual(0, output) - await done.fulfill() + done.fulfill() } - await waitForExpectations([done]) + await fulfillment(of: [done]) } func testValueProducingParentOperation() async throws { - let sent = asyncExpectation(description: "sent") - let received = asyncExpectation(description: "received") + let sent = expectation(description: "sent") + let received = expectation(description: "received") let steps = 10 let delay = 0.01 let request = LongOperationRequest(steps: steps, delay: delay) @@ -191,9 +191,7 @@ final class AmplifyAsyncSequenceTests: XCTestCase { channel.send(value) if value.totalUnitCount == value.completedUnitCount { channel.finish() - Task { - await sent.fulfill() - } + sent.fulfill() } } queue.addOperation(operation) @@ -204,10 +202,10 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } let count = await values.elements.count XCTAssertGreaterThanOrEqual(count, steps) - await received.fulfill() + received.fulfill() } - await waitForExpectations([sent, received]) + await fulfillment(of: [sent, received]) XCTAssertFalse(operation.isCancelled) XCTAssertGreaterThanOrEqual(count, steps) @@ -216,8 +214,8 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } func testCancellingWithParentOperation() async throws { - let sent = asyncExpectation(description: "sent") - let received = asyncExpectation(description: "received") + let sent = expectation(description: "sent") + let received = expectation(description: "received") let steps = 10 let delay = 0.01 let request = LongOperationRequest(steps: steps, delay: delay) @@ -229,9 +227,7 @@ final class AmplifyAsyncSequenceTests: XCTestCase { channel.send(value) if value.completedUnitCount >= steps/2 { channel.cancel() - Task { - await sent.fulfill() - } + sent.fulfill() } } queue.addOperation(operation) @@ -242,10 +238,10 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } let count = await values.elements.count XCTAssertLessThan(count, steps) - await received.fulfill() + received.fulfill() } - await waitForExpectations([sent, received]) + await fulfillment(of: [sent, received]) XCTAssertTrue(operation.isCancelled) XCTAssertLessThan(count, steps) @@ -254,8 +250,8 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } func testThrowingValueProducingParentOperation() async throws { - let sent = asyncExpectation(description: "sent") - let received = asyncExpectation(description: "received") + let sent = expectation(description: "sent") + let received = expectation(description: "received") let steps = 10 let delay = 0.01 let request = LongOperationRequest(steps: steps, delay: delay) @@ -267,9 +263,7 @@ final class AmplifyAsyncSequenceTests: XCTestCase { channel.send(value) if value.totalUnitCount == value.completedUnitCount { channel.finish() - Task { - await sent.fulfill() - } + sent.fulfill() } } queue.addOperation(operation) @@ -280,10 +274,10 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } let count = await values.elements.count XCTAssertGreaterThanOrEqual(count, steps) - await received.fulfill() + received.fulfill() } - await waitForExpectations([sent, received]) + await fulfillment(of: [sent, received]) XCTAssertFalse(operation.isCancelled) XCTAssertGreaterThanOrEqual(count, steps) @@ -292,8 +286,8 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } func testThrowingCancellingWithParentOperation() async throws { - let sent = asyncExpectation(description: "sent") - let received = asyncExpectation(description: "received") + let sent = expectation(description: "sent") + let received = expectation(description: "received") let steps = 10 let delay = 0.01 let request = LongOperationRequest(steps: steps, delay: delay) @@ -305,9 +299,7 @@ final class AmplifyAsyncSequenceTests: XCTestCase { channel.send(value) if value.completedUnitCount >= steps/2 { channel.cancel() - Task { - await sent.fulfill() - } + sent.fulfill() } } queue.addOperation(operation) @@ -318,10 +310,10 @@ final class AmplifyAsyncSequenceTests: XCTestCase { } let count = await values.elements.count XCTAssertLessThan(count, steps) - await received.fulfill() + received.fulfill() } - await waitForExpectations([sent, received]) + await fulfillment(of: [sent, received]) XCTAssertTrue(operation.isCancelled) XCTAssertLessThan(count, steps) diff --git a/AmplifyTests/CoreTests/AmplifyConfigurationInitializationTests.swift b/AmplifyTests/CoreTests/AmplifyConfigurationInitializationTests.swift index 3ab56abf21..ca7e49c381 100644 --- a/AmplifyTests/CoreTests/AmplifyConfigurationInitializationTests.swift +++ b/AmplifyTests/CoreTests/AmplifyConfigurationInitializationTests.swift @@ -167,7 +167,7 @@ class AmplifyConfigurationInitializationTests: XCTestCase { let config = AmplifyConfiguration(analytics: analyticsConfiguration) try Amplify.configure(config) - await waitForExpectations(timeout: 1.0) + await fulfillment(of: [notificationReceived], timeout: 1.0) } // MARK: - Utilities diff --git a/AmplifyTests/CoreTests/AmplifyPublisherTests.swift b/AmplifyTests/CoreTests/AmplifyPublisherTests.swift index 1ea7074085..dcdab6ed19 100644 --- a/AmplifyTests/CoreTests/AmplifyPublisherTests.swift +++ b/AmplifyTests/CoreTests/AmplifyPublisherTests.swift @@ -18,8 +18,9 @@ class AmplifyPublisherTests: XCTestCase { } func testCreateFromTaskSuccess() async throws { - let notDone = asyncExpectation(description: "notDone", isInverted: true) - let done = asyncExpectation(description: "done") + let notDone = expectation(description: "notDone") + notDone.isInverted = true + let done = expectation(description: "done") let input = 7 var output: Int = 0 var success = false @@ -34,19 +35,15 @@ class AmplifyPublisherTests: XCTestCase { success = true case .failure(let error): thrown = error - Task { - await notDone.fulfill() - } - } - Task { - await done.fulfill() + notDone.fulfill() } + done.fulfill() } receiveValue: { value in output = value } - await waitForExpectations([notDone], timeout: 0.01) - await waitForExpectations([done]) + await fulfillment(of: [notDone], timeout: 0.01) + await fulfillment(of: [done]) XCTAssertEqual(input, output) XCTAssertTrue(success) @@ -56,8 +53,8 @@ class AmplifyPublisherTests: XCTestCase { } func testCreateFromTaskFail() async throws { - let failed = asyncExpectation(description: "failed") - let done = asyncExpectation(description: "done") + let failed = expectation(description: "failed") + let done = expectation(description: "done") let input = 13 var output: Int = 0 var success = false @@ -72,19 +69,15 @@ class AmplifyPublisherTests: XCTestCase { success = true case .failure(let error): thrown = error - Task { - await failed.fulfill() - } - } - Task { - await done.fulfill() + failed.fulfill() } + done.fulfill() } receiveValue: { value in output = value } - await waitForExpectations([failed]) - await waitForExpectations([done]) + await fulfillment(of: [failed]) + await fulfillment(of: [done]) XCTAssertNotEqual(input, output) XCTAssertFalse(success) @@ -94,8 +87,10 @@ class AmplifyPublisherTests: XCTestCase { } func testCreateFromTaskCancellation() async throws { - let noCompletion = asyncExpectation(description: "noCompletion", isInverted: true) - let noValueReceived = asyncExpectation(description: "noValueReceived", isInverted: true) + let noCompletion = expectation(description: "noCompletion") + noCompletion.isInverted = true + let noValueReceived = expectation(description: "noValueReceived") + noValueReceived.isInverted = true let input = 7 var output: Int = 0 var success = false @@ -111,20 +106,16 @@ class AmplifyPublisherTests: XCTestCase { case .failure(let error): thrown = error } - Task { - await noCompletion.fulfill() - } + noCompletion.fulfill() } receiveValue: { value in output = value - Task { - await noValueReceived.fulfill() - } + noValueReceived.fulfill() } // cancel immediately sink.cancel() - await waitForExpectations([noCompletion, noValueReceived], timeout: 0.01) + await fulfillment(of: [noCompletion, noValueReceived], timeout: 0.01) // completion and value are not expected when sink is cancelled XCTAssertNotEqual(input, output) @@ -136,31 +127,26 @@ class AmplifyPublisherTests: XCTestCase { let input = Array(1...100) let sequence = AmplifyAsyncSequence() var output = [Int]() - let finished = asyncExpectation(description: "completion finished") - let received = asyncExpectation(description: "values received") + let finished = expectation(description: "completion finished") + let received = expectation(description: "values received") let sink = Amplify.Publisher.create(sequence) .sink { completion in switch completion { - case .finished: - Task { - await finished.fulfill() - } + case .finished: finished.fulfill() case .failure(let error): XCTFail("Failed with error: \(error)") } } receiveValue: { value in output.append(value) if output.count == input.count { - Task { - await received.fulfill() - } + received.fulfill() } } send(input: input, sequence: sequence) - await waitForExpectations([received, finished]) + await fulfillment(of: [received, finished]) XCTAssertEqual(input, output) sink.cancel() } @@ -185,7 +171,7 @@ class AmplifyPublisherTests: XCTestCase { send(input: input, throwingSequence: sequence) - await waitForExpectations(timeout: 1) + await fulfillment(of: [finished], timeout: 1) XCTAssertEqual(input, output) sink.cancel() } @@ -208,7 +194,7 @@ class AmplifyPublisherTests: XCTestCase { output.append(value) } - await waitForExpectations(timeout: 3) + await fulfillment(of: [finished], timeout: 3) for element in output { XCTAssertTrue(expected.contains(element)) } @@ -234,7 +220,7 @@ class AmplifyPublisherTests: XCTestCase { output.append(value) } - await waitForExpectations(timeout: 3) + await fulfillment(of: [failed], timeout: 3) for element in output { XCTAssertTrue(expected.contains(element)) } @@ -260,7 +246,7 @@ class AmplifyPublisherTests: XCTestCase { send(input: input, sequence: sequence) - await waitForExpectations(timeout: 0.1) + await fulfillment(of: [completed], timeout: 0.1) XCTAssertEqual(expected, output) } @@ -268,15 +254,12 @@ class AmplifyPublisherTests: XCTestCase { let expected = [Int]() let sequence = AmplifyAsyncSequence() var output = [Int]() - let finished = asyncExpectation(description: "completion finished") + let finished = expectation(description: "completion finished") let sink = Amplify.Publisher.create(sequence) .sink { completion in switch completion { - case .finished: - Task { - await finished.fulfill() - } + case .finished: finished.fulfill() case .failure(let error): XCTFail("Failed with error: \(error)") } @@ -286,7 +269,7 @@ class AmplifyPublisherTests: XCTestCase { sequence.cancel() - await waitForExpectations([finished]) + await fulfillment(of: [finished]) XCTAssertEqual(expected, output) sink.cancel() } diff --git a/AmplifyTests/CoreTests/AmplifyTaskTests.swift b/AmplifyTests/CoreTests/AmplifyTaskTests.swift index a1b7e2943c..318b0077ee 100644 --- a/AmplifyTests/CoreTests/AmplifyTaskTests.swift +++ b/AmplifyTests/CoreTests/AmplifyTaskTests.swift @@ -146,7 +146,7 @@ class AmplifyTaskTests: XCTestCase { resultSink.cancel() } - wait(for: [exp1, exp2], timeout: 10.0) + await fulfillment(of: [exp1, exp2], timeout: 10.0) XCTAssertGreaterThanOrEqual(progressCount, 10) XCTAssertEqual(lastProgress, 1) diff --git a/AmplifyTests/CoreTests/ChildTaskTests.swift b/AmplifyTests/CoreTests/ChildTaskTests.swift index 30d2800bd1..2a093f87e4 100644 --- a/AmplifyTests/CoreTests/ChildTaskTests.swift +++ b/AmplifyTests/CoreTests/ChildTaskTests.swift @@ -85,7 +85,7 @@ class ChildTaskTests: XCTestCase { XCTAssertTrue(thrown is CancellationError) } - await waitForExpectations(timeout: 0.01) + await fulfillment(of: [cancelExp], timeout: 0.01) task.cancel() // Ensure the channel's AsyncSequence does not block after completion @@ -115,7 +115,7 @@ class ChildTaskTests: XCTestCase { XCTAssertTrue(thrown is CancellationError) } - await waitForExpectations(timeout: 0.01) + await fulfillment(of: [cancelExp], timeout: 0.01) // Ensure the channel's AsyncSequence does not block after completion for await _ in progressSequence { diff --git a/AmplifyTests/CoreTests/InternalTaskTests.swift b/AmplifyTests/CoreTests/InternalTaskTests.swift index d6e72f9030..cc7bec1611 100644 --- a/AmplifyTests/CoreTests/InternalTaskTests.swift +++ b/AmplifyTests/CoreTests/InternalTaskTests.swift @@ -21,29 +21,27 @@ class InternalTaskTests: XCTestCase { // MARK: - Magic Eight Ball (Non-Throwing) - func testMagicEightBallTaskRunner() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay let request = MagicEightBallRequest(total: total, delay: delay) let runner = MagicEightBallTaskRunner(request: request) let task = Task<[String], Never> { - let hubDone = asyncExpectation(description: "hub done") + let hubDone = expectation(description: "hub done") var emojis = [String]() var hubValues = [String]() let token = runner.subscribe { emoji in hubValues.append(emoji) if hubValues.count == total { - Task { - await hubDone.fulfill() - } + hubDone.fulfill() } } await runner.sequence.forEach { emoji in emojis.append(emoji) } - await waitForExpectations([hubDone]) - await done.fulfill() + await fulfillment(of: [hubDone]) + done.fulfill() XCTAssertEqual(total, hubValues.count) XCTAssertEqual(total, emojis.count) runner.unsubscribe(token) @@ -53,11 +51,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(request.total, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testMagicEightBallTaskRunnerWithRunnerCancellation() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay @@ -70,7 +68,7 @@ class InternalTaskTests: XCTestCase { await sequence.forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(0, emojis.count) return emojis } @@ -80,11 +78,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(0, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testMagicEightBallTaskRunnerWithSequenceCancellation() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay @@ -97,7 +95,7 @@ class InternalTaskTests: XCTestCase { await sequence.forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(0, emojis.count) return emojis } @@ -105,11 +103,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(0, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testMagicEightBallPluginAPI() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let total = 10 let delay = 0.01 let timeout = Double(total) * 2.0 * delay @@ -119,7 +117,7 @@ class InternalTaskTests: XCTestCase { await plugin.getAnswers(total: total, delay: delay).forEach { emoji in answers.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(total, answers.count) return answers } @@ -127,13 +125,13 @@ class InternalTaskTests: XCTestCase { let answers = await task.value XCTAssertEqual(answers.count, total) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } // MARK: - Random Emoji (Throwing) - func testRandomEmojiTaskRunner() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay @@ -146,7 +144,7 @@ class InternalTaskTests: XCTestCase { try await runner.sequence.forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(total, emojis.count) } catch { thrown = error @@ -158,11 +156,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(request.total, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testRandomEmojiTaskRunnerWithRunnerCancellation() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay @@ -177,7 +175,7 @@ class InternalTaskTests: XCTestCase { try await sequence.forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(0, emojis.count) } catch { thrown = error @@ -191,11 +189,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(0, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testRandomEmojiTaskRunnerWithSequenceCancellation() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let delay = 0.01 let total = 10 let timeout = Double(total) * 2.0 * delay @@ -210,7 +208,7 @@ class InternalTaskTests: XCTestCase { try await sequence.forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(0, emojis.count) } catch { thrown = error @@ -222,11 +220,11 @@ class InternalTaskTests: XCTestCase { let output = await task.value XCTAssertEqual(0, output.count) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } func testEmojisPluginAPI() async throws { - let done = asyncExpectation(description: "done") + let done = expectation(description: "done") let total = 10 let delay = 0.01 let timeout = Double(total) * 2.0 * delay @@ -236,7 +234,7 @@ class InternalTaskTests: XCTestCase { try await plugin.getEmojis(total: total, delay: delay).forEach { emoji in emojis.append(emoji) } - await done.fulfill() + done.fulfill() XCTAssertEqual(total, emojis.count) return emojis } @@ -244,7 +242,7 @@ class InternalTaskTests: XCTestCase { let emojis = try await task.value XCTAssertEqual(emojis.count, total) - await waitForExpectations([done], timeout: timeout) + await fulfillment(of: [done], timeout: timeout) } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 63811f878b..e84141e6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 2.21.3 (2023-11-01) + +### Bug Fixes + +- bump swift sdk to 0.26.1 (#3333) + +## 2.21.2 (2023-10-31) + +### Bug Fixes + +- **Analytics**: Handling certain auth errors as retryable errors (#3322) + +## 2.21.1 (2023-10-23) + +### Bug Fixes + +- **storage**: include user-controlled metadata in upload requests (#3315) +- **PushNotifications**: Fixing .network error not being correctly reported (#3314) + +## 2.21.0 (2023-10-21) + +### Features + +- **datastore**: Add `isLoaded` public property in List+Model (#3296) + +## 2.20.1 (2023-10-19) + +### Bug Fixes + +- **storage**: add metadata support to uploads (#3295) + +## 2.20.0 (2023-10-18) + +### Features + +- **core**: update swift sdk dependency version to 0.26.0 (#3248) + ## 2.19.0 (2023-09-28) ### Features diff --git a/Package.resolved b/Package.resolved index 681d2bacf7..58bb1ea03d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift", "state" : { - "revision" : "6feec6c3787877807aa9a00fad09591b96752376", - "version" : "0.6.1" + "revision" : "fd1756b6e5c9fd1a906edfb743f7cb64c2c98639", + "version" : "0.17.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift.git", "state" : { - "revision" : "24bae88a2391fe75da8a940a544d1ef6441f5321", - "version" : "0.13.0" + "revision" : "4ecdba6927ff4be92e53c1834284ad1de60f8899", + "version" : "0.26.1" } }, { @@ -57,10 +57,10 @@ { "identity" : "smithy-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/smithy-swift", + "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "7b28da158d92cd06a3549140d43b8fbcf64a94a6", - "version" : "0.15.0" + "revision" : "d580ddb8c2929b380a665a884935901b5ad808cc", + "version" : "0.30.1" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", + "version" : "1.0.5" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MaxDesiatov/XMLCoder.git", "state" : { - "revision" : "b1e944cbd0ef33787b13f639a5418d55b3bed501", - "version" : "0.17.1" + "revision" : "80b4a1646399b8e4e0ce80711653476a85bd5e37", + "version" : "0.17.0" } } ], diff --git a/Package.swift b/Package.swift index 959479d75f..2fa9b9a4e8 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let platforms: [SupportedPlatform] = [ .watchOS(.v9) ] let dependencies: [Package.Dependency] = [ - .package(url: "https://github.com/awslabs/aws-sdk-swift.git", exact: "0.13.0"), + .package(url: "https://github.com/awslabs/aws-sdk-swift.git", exact: "0.26.1"), .package(url: "https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git", from: "3.0.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", exact: "0.13.2"), .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", from: "2.1.0"), @@ -23,6 +23,9 @@ let amplifyTargets: [Target] = [ exclude: [ "Info.plist", "Categories/DataStore/Model/Temporal/README.md" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .target( @@ -34,6 +37,9 @@ let amplifyTargets: [Target] = [ path: "AmplifyPlugins/Core/AWSPluginsCore", exclude: [ "Info.plist" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .target( @@ -116,6 +122,9 @@ let apiTargets: [Target] = [ exclude: [ "Info.plist", "AWSAPIPlugin.md" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .testTarget( @@ -157,7 +166,10 @@ let authTargets: [Target] = [ .product(name: "AWSCognitoIdentityProvider", package: "aws-sdk-swift"), .product(name: "AWSCognitoIdentity", package: "aws-sdk-swift") ], - path: "AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin" + path: "AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .target( name: "libtommathAmplify", @@ -198,6 +210,9 @@ let dataStoreTargets: [Target] = [ exclude: [ "Info.plist", "Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.mmd" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .testTarget( @@ -224,6 +239,9 @@ let storageTargets: [Target] = [ path: "AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin", exclude: [ "Resources/Info.plist" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .testTarget( @@ -251,6 +269,9 @@ let geoTargets: [Target] = [ path: "AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin", exclude: [ "Resources/Info.plist" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .testTarget( @@ -279,7 +300,10 @@ let internalPinpointTargets: [Target] = [ .product(name: "AWSPinpoint", package: "aws-sdk-swift"), .product(name: "AmplifyUtilsNotifications", package: "amplify-swift-utils-notifications") ], - path: "AmplifyPlugins/Internal/Sources/InternalAWSPinpoint" + path: "AmplifyPlugins/Internal/Sources/InternalAWSPinpoint", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .testTarget( name: "InternalAWSPinpointUnitTests", @@ -298,7 +322,10 @@ let analyticsTargets: [Target] = [ dependencies: [ .target(name: "InternalAWSPinpoint") ], - path: "AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin" + path: "AmplifyPlugins/Analytics/Sources/AWSPinpointAnalyticsPlugin", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .testTarget( name: "AWSPinpointAnalyticsPluginUnitTests", @@ -316,7 +343,10 @@ let pushNotificationsTargets: [Target] = [ dependencies: [ .target(name: "InternalAWSPinpoint") ], - path: "AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin" + path: "AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .testTarget( name: "AWSPinpointPushNotificationsPluginUnitTests", @@ -343,7 +373,10 @@ let predictionsTargets: [Target] = [ .product(name: "AWSTranslate", package: "aws-sdk-swift") ], path: "AmplifyPlugins/Predictions/AWSPredictionsPlugin", - exclude: [] + exclude: [], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .testTarget( name: "AWSPredictionsPluginUnitTests", @@ -359,6 +392,9 @@ let predictionsTargets: [Target] = [ path: "AmplifyPlugins/Predictions/CoreMLPredictionsPlugin", exclude: [ "Resources/Info.plist" + ], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") ] ), .testTarget( @@ -380,7 +416,10 @@ let loggingTargets: [Target] = [ .target(name: "AWSPluginsCore"), .product(name: "AWSCloudWatchLogs", package: "aws-sdk-swift"), ], - path: "AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin" + path: "AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ] ), .testTarget( name: "AWSCloudWatchLoggingPluginTests", diff --git a/Resources/PrivacyInfo.xcprivacy b/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c69ba3b3a --- /dev/null +++ b/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + +