diff --git a/build-support/integ_test_android.sh b/build-support/integ_test_android.sh new file mode 100755 index 0000000000..56589961fd --- /dev/null +++ b/build-support/integ_test_android.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +if [ ! -d android ]; then + echo "No Android project to test" >&2 + exit +fi + +DEFAULT_DEVICE_ID="sdk" +DEFAULT_ENABLE_CLOUD_SYNC="true" + +while [ $# -gt 0 ]; do + case "$1" in + -d|--device-id) + deviceId="$2" + ;; + -ec|--enable-cloud-sync) + case "$2" in + true|false) + enableCloudSync="$2" + ;; + *) + echo "Invalid value for $1" + exit 1 + esac + ;; + *) + echo "Invalid arguments" + exit 1 + esac + shift + shift +done + +deviceId=${deviceId:-$DEFAULT_DEVICE_ID} +enableCloudSync=${enableCloudSync:-$DEFAULT_ENABLE_CLOUD_SYNC} + +declare -a testsList +declare -a resultsList + +TARGET=integration_test/main_test.dart +if [ ! -e $TARGET ]; then + echo "$TARGET file not found" >&2 + exit +fi + +testsList+=("$TARGET") +if flutter test \ + --no-pub \ + -d $deviceId \ + $TARGET; then + resultsList+=(0) +else + resultsList+=(1) +fi + +TEST_ENTRIES="integration_test/separate_integration_tests/*.dart" +for ENTRY in $TEST_ENTRIES; do + if [ ! -f "${ENTRY}" ]; then + continue + fi + testsList+=("$ENTRY") + if [ $enableCloudSync == "true" ]; then + echo "Run $ENTRY WITH API Sync" + else + echo "Run $ENTRY WITHOUT API Sync" + fi + + if flutter test \ + --no-pub \ + --dart-define ENABLE_CLOUD_SYNC=$enableCloudSync \ + -d $deviceId \ + $ENTRY; then + resultsList+=(0) + else + resultsList+=(1) + fi +done + +testFailure=0 +for i in "${!testsList[@]}"; do + if [ "${resultsList[i]}" == 0 ]; then + echo "βœ… ${testsList[i]}" + else + testFailure=1 + echo "❌ ${testsList[i]}" + fi +done + +exit $testFailure diff --git a/build-support/integ_test_ios.sh b/build-support/integ_test_ios.sh index 609fa3d16d..c7a6f59707 100755 --- a/build-support/integ_test_ios.sh +++ b/build-support/integ_test_ios.sh @@ -7,25 +7,99 @@ if [ ! -d ios ]; then exit fi +DEFAULT_DEVICE_ID="iPhone" +DEFAULT_ENABLE_CLOUD_SYNC="true" + +while [ $# -gt 0 ]; do + case "$1" in + -d|--device-id) + deviceId="$2" + ;; + -ec|--enable-cloud-sync) + case "$2" in + true|false) + enableCloudSync="$2" + ;; + *) + echo "Invalid value for $1" + exit 1 + esac + ;; + *) + echo "Invalid arguments" + exit 1 + esac + shift + shift +done + +deviceId=${deviceId:-$DEFAULT_DEVICE_ID} +enableCloudSync=${enableCloudSync:-$DEFAULT_ENABLE_CLOUD_SYNC} + +declare -a testsList +declare -a resultsList + TARGET=integration_test/main_test.dart if [ ! -e $TARGET ]; then echo "$TARGET file not found" >&2 exit fi + + # Use xcodebuild if 'RunnerTests' scheme exists, else `flutter test` if xcodebuild -workspace ios/Runner.xcworkspace -list -json | jq -e '.workspace.schemes | index("RunnerTests")' >/dev/null; then # Build app for testing flutter build ios --no-pub --config-only --simulator --target=$TARGET - + xcodebuild \ -workspace ios/Runner.xcworkspace \ -scheme RunnerTests \ -destination "platform=iOS Simulator,name=iPhone 12 Pro Max" \ test else - flutter test \ + testsList+=("$TARGET") + if flutter test \ --no-pub \ - -d iPhone \ - $TARGET + -d $deviceId \ + $TARGET; then + resultsList+=(0) + else + resultsList+=(1) + fi fi + +TEST_ENTRIES="integration_test/separate_integration_tests/*.dart" +for ENTRY in $TEST_ENTRIES; do + if [ ! -f "${ENTRY}" ]; then + continue + fi + testsList+=("$ENTRY") + if [ $enableCloudSync == "true" ]; then + echo "Run $ENTRY WITH API Sync" + else + echo "Run $ENTRY WITHOUT API Sync" + fi + + if flutter test \ + --no-pub \ + --dart-define ENABLE_CLOUD_SYNC=$enableCloudSync \ + -d $deviceId \ + $ENTRY; then + resultsList+=(0) + else + resultsList+=(1) + fi +done + +testFailure=0 +for i in "${!testsList[@]}"; do + if [ "${resultsList[i]}" == 0 ]; then + echo "βœ… ${testsList[i]}" + else + testFailure=1 + echo "❌ ${testsList[i]}" + fi +done + +exit $testFailure diff --git a/melos.yaml b/melos.yaml index 49b37733ab..3ef4639b3e 100644 --- a/melos.yaml +++ b/melos.yaml @@ -228,7 +228,7 @@ scripts: - Requires running Android and iOS simulators. test:integration:android: - run: melos exec -c 1 "flutter test --no-pub integration_test/main_test.dart -d sdk" + run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_android.sh" $@ description: Run integration tests for a single package on an Android emulator. - Run with `--no-select` to run for all applicable packages. @@ -239,7 +239,7 @@ scripts: scope: "*example*" test:integration:ios: - run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_ios.sh" + run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_ios.sh" $@ description: Run integration tests for a single package on an iOS simulator. - Run with `--no-select` to run for all applicable packages. - Requires launching an iOS simulator prior to execution. diff --git a/packages/amplify_datastore/example/.gitignore b/packages/amplify_datastore/example/.gitignore index 5976fa0dfd..5162ae8e82 100644 --- a/packages/amplify_datastore/example/.gitignore +++ b/packages/amplify_datastore/example/.gitignore @@ -43,3 +43,24 @@ app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +#amplify-do-not-edit-begin +amplify/\#current-cloud-backend +amplify/.config/local-* +amplify/logs +amplify/mock-data +amplify/backend/amplify-meta.json +amplify/backend/.temp +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplifyconfiguration.dart +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig +.secret-* +**.sample +#amplify-do-not-edit-end diff --git a/packages/amplify_datastore/example/integration_test/main_test.dart b/packages/amplify_datastore/example/integration_test/main_test.dart index abe835b896..654692fbfd 100644 --- a/packages/amplify_datastore/example/integration_test/main_test.dart +++ b/packages/amplify_datastore/example/integration_test/main_test.dart @@ -16,10 +16,8 @@ import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'save_test.dart' as save_tests; import 'query_test/query_test.dart' as query_test; import 'model_type_test.dart' as model_type_tests; -import 'relationship_type_test.dart' as relationship_type_tests; import 'observe_test.dart' as observe_tests; import 'observe_query_test.dart' as observe_query_tests; import 'clear_test.dart' as clear_tests; @@ -34,10 +32,8 @@ void main() async { await configureDataStore(); }); - save_tests.main(); query_test.main(); model_type_tests.main(); - relationship_type_tests.main(); observe_tests.main(); observe_query_tests.main(); clear_tests.main(); diff --git a/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart b/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart index e15d4728fd..3eb3a33504 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; - import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart index e47bfe26de..2e7905ad16 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart index 0adc135f0f..8dd2fde87d 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart index 366497fda8..74ee60a4da 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart index ce4c03e8d3..80f985e23f 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart b/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart index bf18413d66..bdb520dd83 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart @@ -15,7 +15,6 @@ import 'dart:math'; -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart b/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart index b5784b4ae4..f3817bcafd 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; - import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/relationship_type_test.dart b/packages/amplify_datastore/example/integration_test/relationship_type_test.dart deleted file mode 100644 index 113539c9a3..0000000000 --- a/packages/amplify_datastore/example/integration_test/relationship_type_test.dart +++ /dev/null @@ -1,1302 +0,0 @@ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import 'package:amplify_datastore/amplify_datastore.dart'; - -import 'package:amplify_datastore_example/models/ModelProvider.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; - -import 'utils/setup_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - group('relationship type', () { - group('HasOne (parent refers to child with implicit connection field)', () { - // schema - // type HasOneParent @model { - // id: ID! - // name: String - // implicitChild: HasOneChild @hasOne - // } - // type HasOneChild @model { - // id: ID! - // name: String - // } - var child = HasOneChild(name: 'child'); - // Curretly with @hasOne, parent -> child relationship is created - // by assign child.id to the connection field of the parent - // the connection field is automatically generated when the child - // is implicitly referred in the schema - var parent = HasOneParent( - name: 'HasOne (implicit)', hasOneParentImplicitChildId: child.id); - late Future> childEvent; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvent = Amplify.DataStore.observe(HasOneChild.classType).first; - parentEvent = Amplify.DataStore.observe(HasOneParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasOneChild.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasOneParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isNotEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasOneParent.classType); - var queriedParent = parents.single; - // hasOne relationships do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - expect(queriedParent.hasOneParentImplicitChildId, child.id); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = await Amplify.DataStore.query(HasOneChild.classType); - var queriedChild = children.single; - expect(queriedChild, child); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // hasOne relationships in iOS do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent.id, parent.id); - expect(observedParent.name, parent.name); - expect(observedParent.hasOneParentImplicitChildId, child.id); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - }); - - testWidgets( - 'delete parent', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isEmpty); - }, - // unskip when https://github.com/aws-amplify/amplify-flutter/issues/818 is resolved - skip: true, - ); - - testWidgets('delete child', (WidgetTester tester) async { - await Amplify.DataStore.delete(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isEmpty); - }); - }); - - group('HasOne (parent refers to child with explicit connection field)', () { - // schema - // type HasOneParent @model { - // id: ID! - // name: String - // explicitChildID: ID - // explicitChild: HasOneChild @hasOne(fields: ["explicitChildID"]) - // } - // type HasOneChild @model { - // id: ID! - // name: String - // } - var child = HasOneChild(name: 'child'); - // Curretly with @hasOne, parent -> child relationship is created - // by assign child.id to the connection field of the parent - var parent = - HasOneParent(name: 'HasOne (explicit)', explicitChildID: child.id); - late Future> childEvent; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvent = Amplify.DataStore.observe(HasOneChild.classType).first; - parentEvent = Amplify.DataStore.observe(HasOneParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasOneChild.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasOneParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isNotEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasOneParent.classType); - var queriedParent = parents.single; - // hasOne relationships do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - expect(queriedParent.explicitChildID, child.id); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = await Amplify.DataStore.query(HasOneChild.classType); - var queriedChild = children.single; - expect(queriedChild, child); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // hasOne relationships in iOS do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent.id, parent.id); - expect(observedParent.name, parent.name); - expect(observedParent.explicitChildID, child.id); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - }); - - testWidgets( - 'delete parent', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isEmpty); - }, - // unskip when https://github.com/aws-amplify/amplify-flutter/issues/818 is resolved - skip: true, - ); - - testWidgets('delete child', (WidgetTester tester) async { - await Amplify.DataStore.delete(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isEmpty); - }); - }); - - group('BelongsTo (child refers to parent with implicit connection field)', - () { - // schema - // type BelongsToParent @model { - // id: ID! - // name: String - // } - // type BelongsToChildImplicit @model { - // id: ID! - // name: String - // belongsToParent: BelongsToParent @belongsTo - // } - var parent = BelongsToParent(name: 'belongs to parent'); - var child = BelongsToChildImplicit( - name: 'belongs to child (implicit)', belongsToParent: parent); - late Future> parentEvent; - late Future> childEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - parentEvent = - Amplify.DataStore.observe(BelongsToParent.classType).first; - childEvent = - Amplify.DataStore.observe(BelongsToChildImplicit.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(children, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - var queriedChild = children.single; - expect(queriedChild, child); - expect(queriedChild.belongsToParent, parent); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - expect(observedParent, parent); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - expect(observedChild.belongsToParent, parent); - }); - - testWidgets('delete parent (cascade delete child)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isEmpty); - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(children, isEmpty); - }); - }); - - group('BelongsTo (child refers to parent with explicit connection field)', - () { - // schema - // type BelongsToParent @model { - // id: ID! - // name: String - // } - // type BelongsToChildExplicit @model { - // id: ID! - // name: String - // belongsToParentID: ID - // belongsToParent: BelongsToParent @belongsTo(fields: ["belongsToParentID"]) - // } - var parent = BelongsToParent(name: 'belongs to parent'); - var child = BelongsToChildExplicit( - name: 'belongs to child (explicit)', belongsToParent: parent); - late Future> parentEvent; - late Future> childEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - parentEvent = - Amplify.DataStore.observe(BelongsToParent.classType).first; - childEvent = - Amplify.DataStore.observe(BelongsToChildExplicit.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(children, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - var queriedChild = children.single; - expect(queriedChild, child); - expect(queriedChild.belongsToParent, parent); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - expect(observedParent, parent); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - expect(observedChild.belongsToParent, parent); - }); - - testWidgets('delete parent (cascade delete child)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isEmpty); - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(children, isEmpty); - }); - }); - - group('HasMany (parent refers to children with implicit connection field)', - () { - // schema - // type HasManyParent @model { - // id: ID! - // name: String - // implicitChildren: [HasManyChild] @hasMany - // } - // type HasManyChildImplicit @model { - // id: ID! - // name: String - // } - var parent = HasManyParent(name: 'has many parent (implicit)'); - var children = List.generate( - 5, - (i) => HasManyChildImplicit( - name: 'has many child $i (implicit)', - hasManyParentImplicitChildrenId: parent.id)); - late Future>> childEvents; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe(HasManyChildImplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe(HasManyParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasManyParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasManyParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParentImplicitChildrenId, - actualChild.hasManyParentImplicitChildrenId); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParentImplicitChildrenId, - actualChild.hasManyParentImplicitChildrenId); - } - }); - - testWidgets('delete parent', (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isEmpty); - - // cascade delete won't happen in this test case as there is no - // connection field generated in the child model - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isNotEmpty); - // skip this test until https://github.com/aws-amplify/amplify-android/pull/1614 - // is integrated into amplify-flutter - }, skip: true); - - testWidgets('delete children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.delete(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group( - 'HasMany (parent refers to children with explicit connection field and indexName)', - () { - // schema - // type HasManyParent @model { - // id: ID! - // name: String - // explicitChildren: [HasManyChildExplicit] - // @hasMany(indexName: "byHasManyParent", fields: ["id"]) - // } - // type HasManyChildExplicit @model { - // id: ID! - // name: String - // hasManyParentID: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) - // } - var parent = HasManyParent(name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildExplicit( - name: 'has many child $i (explicit)', - hasManyParentID: parent.id)); - late Future>> childEvents; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe(HasManyChildExplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe(HasManyParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasManyParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasManyParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParentID, actualChild.hasManyParentID); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParentID, actualChild.hasManyParentID); - } - }); - - testWidgets('delete parent', (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isEmpty); - - // cascade delete won't happen in this test case as there is no - // connection field generated in the child model - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isNotEmpty); - // skip this test until https://github.com/aws-amplify/amplify-android/pull/1614 - // is integrated into amplify-flutter - }, skip: true); - - testWidgets('delete children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.delete(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('HasMany (bi-directional with implicit connection field)', () { - // schema - // type HasManyParentBiDirectionalImplicit @model { - // id: ID! - // name: String - // biDirectionalImplicitChildren: [HasManyChildBiDirectionalImplicit] @hasMany - // } - - // type HasManyChildBiDirectionalImplicit @model { - // id: ID! - // name: String - // hasManyParent: HasManyParentBiDirectionalImplicit @belongsTo - // } - var parent = HasManyParentBiDirectionalImplicit( - name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildBiDirectionalImplicit( - name: 'has many child $i (explicit)', hasManyParent: parent)); - late Future>> - childEvents; - late Future> - parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe( - HasManyChildBiDirectionalImplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe( - HasManyParentBiDirectionalImplicit.classType) - .first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('delete parent (cascade delete associated children)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(parents, isEmpty); - - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('HasMany (bi-directional with explicit connection field)', () { - // schema - // type HasManyParentBiDirectionalExplicit @model { - // id: ID! - // name: String - // biDirectionalExplicitChildren: [HasManyChildBiDirectionalExplicit] - // @hasMany(indexName: "byHasManyParent", fields: ["id"]) - // } - - // type HasManyChildBiDirectionalExplicit @model { - // id: ID! - // name: String - // hasManyParentId: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) - // hasManyParent: HasManyParentBiDirectionalExplicit - // @belongsTo(fields: ["hasManyParentId"]) - // } - var parent = HasManyParentBiDirectionalExplicit( - name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildBiDirectionalExplicit( - name: 'has many child $i (explicit)', hasManyParent: parent)); - late Future>> - childEvents; - late Future> - parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe( - HasManyChildBiDirectionalExplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe( - HasManyParentBiDirectionalExplicit.classType) - .first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('delete parent (cascade delete associated children)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(parents, isEmpty); - - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('Many-to-many', () { - // schema - // type Post @model { - // id: ID! - // title: String! - // rating: Int! - // tags: [Tag] @manyToMany(relationName: "PostTags") - // } - // type Tag @model { - // id: ID! - // label: String! - // posts: [Post] @manyToMany(relationName: "PostTags") - // } - var posts = [ - Post(title: 'many to many post 1', rating: 10), - Post(title: 'many to many post 2', rating: 5), - ]; - var tags = [ - Tag(label: 'many to many tag 1'), - Tag(label: 'many to maby tag 2') - ]; - var postTags = [ - PostTags(post: posts[0], tag: tags[0]), - PostTags(post: posts[0], tag: tags[0]), - PostTags(post: posts[1], tag: tags[1]), - PostTags(post: posts[1], tag: tags[1]) - ]; - late Future>> postEvents; - late Future>> tagEvents; - late Future>> postTagsEvents; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - postEvents = Amplify.DataStore.observe(Post.classType) - .take(posts.length) - .toList(); - tagEvents = - Amplify.DataStore.observe(Tag.classType).take(tags.length).toList(); - postTagsEvents = Amplify.DataStore.observe(PostTags.classType) - .take(postTags.length) - .toList(); - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save post', (WidgetTester tester) async { - for (var post in posts) { - await Amplify.DataStore.save(post); - } - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts, isNotEmpty); - }); - - testWidgets('save tags', (WidgetTester tester) async { - for (var tag in tags) { - await Amplify.DataStore.save(tag); - } - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags, isNotEmpty); - }); - - testWidgets('save postTags', (WidgetTester tester) async { - for (var postTag in postTags) { - await Amplify.DataStore.save(postTag); - } - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isNotEmpty); - }); - - testWidgets('query posts', (WidgetTester tester) async { - var queriedPosts = await Amplify.DataStore.query(Post.classType); - for (var post in queriedPosts) { - expect(posts.contains(post), isTrue); - } - }); - - testWidgets('query tags', (WidgetTester tester) async { - var queriedTags = await Amplify.DataStore.query(Tag.classType); - for (var tag in queriedTags) { - expect(tags.contains(tag), isTrue); - } - }); - - testWidgets('query postTags', (WidgetTester tester) async { - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - for (var postTag in queriedPostTags) { - expect( - postTags.indexWhere( - (e) => e.post == postTag.post && e.tag == postTag.tag) > - -1, - isTrue); - } - }); - - testWidgets('observe posts', (WidgetTester tester) async { - var events = await postEvents; - for (var i = 0; i < posts.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedPost = event.item; - var expectedPost = posts[i]; - expect(eventType, EventType.create); - expect(observedPost, expectedPost); - } - }); - - testWidgets('observe tags', (WidgetTester tester) async { - var events = await tagEvents; - for (var i = 0; i < tags.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedTag = event.item; - var expectedTag = tags[i]; - expect(eventType, EventType.create); - expect(observedTag, expectedTag); - } - }); - - testWidgets('observe postTags', (WidgetTester tester) async { - var events = await postTagsEvents; - for (var i = 0; i < tags.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedPostTag = event.item; - var expectedPostTag = postTags[i]; - expect(eventType, EventType.create); - expect(observedPostTag, expectedPostTag); - } - }); - - testWidgets('delete post (cascade delete associated postTag)', - (WidgetTester tester) async { - var deletedPost = posts[0]; - await Amplify.DataStore.delete(deletedPost); - - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts.length, posts.length - 1); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect( - queriedPostTags - .indexWhere((postTag) => postTag.post == deletedPost), - -1); - }); - - testWidgets('delete tag (cascade delete associated postTag)', - (WidgetTester tester) async { - var deletedTag = tags[0]; - await Amplify.DataStore.delete(deletedTag); - - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags.length, tags.length - 1); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect( - queriedPostTags.indexWhere((postTag) => postTag.tag == deletedTag), - -1); - }); - - testWidgets('delete remaining post', (WidgetTester tester) async { - await Amplify.DataStore.delete(posts[1]); - - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts, isEmpty); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isEmpty); - }); - - testWidgets('delete remaining tag', (WidgetTester tester) async { - await Amplify.DataStore.delete(tags[1]); - - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags, isEmpty); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isEmpty); - }); - }); - - group('Model with multiple relationships', () { - // schema - // type MultiRelatedMeeting @model { - // id: ID! @primaryKey - // title: String! - // attendees: [MultiRelatedRegistration] - // @hasMany(indexName: "byMeeting", fields: ["id"]) - // } - - // type MultiRelatedAttendee @model { - // id: ID! @primaryKey - // meetings: [MultiRelatedRegistration] - // @hasMany(indexName: "byAttendee", fields: ["id"]) - // } - - // type MultiRelatedRegistration @model { - // id: ID! @primaryKey - // meetingId: ID @index(name: "byMeeting", sortKeyFields: ["attendeeId"]) - // meeting: MultiRelatedMeeting! @belongsTo(fields: ["meetingId"]) - // attendeeId: ID @index(name: "byAttendee", sortKeyFields: ["meetingId"]) - // attendee: MultiRelatedAttendee! @belongsTo(fields: ["attendeeId"]) - // } - var meetings = [ - MultiRelatedMeeting(title: 'test meeting 1'), - MultiRelatedMeeting(title: 'test meeting 2'), - ]; - var attendees = [ - MultiRelatedAttendee(), - MultiRelatedAttendee(), - ]; - var registrations = [ - MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[0]), - MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[1]), - MultiRelatedRegistration(attendee: attendees[1], meeting: meetings[1]), - ]; - - late Future>> meetingEvents; - late Future>> attendeeEvents; - late Future>> - registrationEvents; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - meetingEvents = Amplify.DataStore.observe(MultiRelatedMeeting.classType) - .take(meetings.length) - .toList(); - attendeeEvents = - Amplify.DataStore.observe(MultiRelatedAttendee.classType) - .take(attendees.length) - .toList(); - registrationEvents = - Amplify.DataStore.observe(MultiRelatedRegistration.classType) - .take(registrations.length) - .toList(); - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isEmpty); - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees, isEmpty); - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - - testWidgets('save meetings', (WidgetTester tester) async { - for (var meeting in meetings) { - await Amplify.DataStore.save(meeting); - } - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isNotEmpty); - }); - - testWidgets('save attendees', (WidgetTester tester) async { - for (var attendee in attendees) { - await Amplify.DataStore.save(attendee); - } - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees, isNotEmpty); - }); - - testWidgets('save registrations', (WidgetTester tester) async { - for (var registration in registrations) { - await Amplify.DataStore.save(registration); - } - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isNotEmpty); - }); - - testWidgets('query meetings', (WidgetTester tester) async { - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - for (var meeting in queriedMeetings) { - expect(meetings.contains(meeting), isTrue); - } - }); - - testWidgets('query attendees', (WidgetTester tester) async { - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - for (var attendee in queriedAttendees) { - expect(attendees.contains(attendee), isTrue); - } - }); - - testWidgets('query registraions', (WidgetTester tester) async { - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - for (var registration in queriedRegistrations) { - expect( - registrations.indexWhere((e) => - e.meeting == registration.meeting && - e.attendee == registration.attendee) > - -1, - isTrue); - } - }); - - testWidgets('observe meetings', (WidgetTester tester) async { - var events = await meetingEvents; - for (var i = 0; i < meetings.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedMeeting = event.item; - var expectedMeeting = meetings[i]; - expect(eventType, EventType.create); - expect(observedMeeting, expectedMeeting); - } - }); - - testWidgets('observe attendees', (WidgetTester tester) async { - var events = await attendeeEvents; - for (var i = 0; i < attendees.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedAttendee = event.item; - var expectedAttendee = attendees[i]; - expect(eventType, EventType.create); - expect(observedAttendee, expectedAttendee); - } - }); - - testWidgets('observe resgistrations', (WidgetTester tester) async { - var events = await registrationEvents; - for (var i = 0; i < registrations.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedRegistration = event.item; - var expectedRegistration = registrations[i]; - expect(eventType, EventType.create); - expect(observedRegistration, expectedRegistration); - } - }); - - testWidgets('delete meeting (cascade delete associated registration)', - (WidgetTester tester) async { - var deletedMeeting = meetings[0]; - await Amplify.DataStore.delete(deletedMeeting); - - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings.length, meetings.length - 1); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect( - queriedRegistrations.indexWhere( - (registration) => registration.meeting == deletedMeeting), - -1); - }); - - testWidgets('delete attendee (cascade delete associated registration)', - (WidgetTester tester) async { - var deletedAttendee = attendees[0]; - await Amplify.DataStore.delete(deletedAttendee); - - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees.length, attendees.length - 1); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect( - queriedRegistrations.indexWhere( - (registration) => registration.attendee == deletedAttendee), - -1); - }); - - testWidgets('delete remaining meeting', (WidgetTester tester) async { - await Amplify.DataStore.delete(meetings[1]); - - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isEmpty); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - - testWidgets('delete remaining attendee', (WidgetTester tester) async { - await Amplify.DataStore.delete(attendees[1]); - - var queriedAttendees = await Amplify.DataStore.query(Tag.classType); - expect(queriedAttendees, isEmpty); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - }); - }); -} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart new file mode 100644 index 0000000000..271b3dc329 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart @@ -0,0 +1,115 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/basic_model_operation/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final enableCloudSync = shouldEnableCloudSync(); + group( + 'Basic model operation${enableCloudSync ? ' with API sync 🌩 enabled' : ''} -', + () { + Blog testBlog = Blog(name: 'test blog'); + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + }); + + testWidgets( + 'should save a new model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testBlog], + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(testBlog); + } + + var queriedBlogs = await Amplify.DataStore.query(Blog.classType); + expect(queriedBlogs, contains(testBlog)); + }); + + testWidgets( + 'should update existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + var updatedTestBlog = testBlog.copyWith(name: "updated test blog"); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [updatedTestBlog], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(updatedTestBlog); + } + + var queriedBlogs = await Amplify.DataStore.query( + Blog.classType, + where: Blog.ID.eq(updatedTestBlog.id), + ); + + expect(queriedBlogs, contains(updatedTestBlog)); + }, + ); + + testWidgets( + 'should delete existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testBlog], + expectedRootModelVersion: 3, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); + }, + ); + } else { + await Amplify.DataStore.delete(testBlog); + } + + var queriedBlogs = await Amplify.DataStore.query(Blog.classType); + + // verify blog was deleted + expect(queriedBlogs, isNot(contains(testBlog))); + }, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart new file mode 100644 index 0000000000..367cd7e23a --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/belongs_to/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('BelongsTo (child refers to parent with explicit connection field)', + () { + // schema + // type BelongsToParent @model { + // id: ID! + // name: String + // explicitChild: BelongsToChildExplicit @hasOne + // } + // type BelongsToChildExplicit @model { + // id: ID! + // name: String + // belongsToParentID: ID + // belongsToParent: BelongsToParent @belongsTo(fields: ["belongsToParentID"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [BelongsToParent(name: 'belongs to parent')]; + var associatedModels = [ + BelongsToChildExplicit( + name: 'belongs to child (explicit)', + belongsToParent: rootModels.first, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: BelongsToParent.classType, + rootModels: rootModels, + associatedModelType: BelongsToChildExplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart new file mode 100644 index 0000000000..c707fd182e --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/belongs_to/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('BelongsTo (child refers to parent with implicit connection field)', + () { + // schema + // type BelongsToParent @model { + // id: ID! + // name: String + // implicitChild: BelongsToChildImplicit @hasOne + // } + // type BelongsToChildImplicit @model { + // id: ID! + // name: String + // belongsToParent: BelongsToParent @belongsTo + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [BelongsToParent(name: 'belongs to parent')]; + var associatedModels = [ + BelongsToChildImplicit( + name: 'belongs to child (implicit)', + belongsToParent: rootModels.first, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: BelongsToParent.classType, + rootModels: rootModels, + associatedModelType: BelongsToChildImplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart new file mode 100644 index 0000000000..5e791941b5 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many_bidirectional/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (bi-directional with implicit connection field', () { + // schema + // type HasManyParentBiDirectionalExplicit @model { + // id: ID! + // name: String + // biDirectionalExplicitChildren: [HasManyChildBiDirectionalExplicit] + // @hasMany(indexName: "byHasManyParent", fields: ["id"]) + // } + + // type HasManyChildBiDirectionalExplicit @model { + // id: ID! + // name: String + // hasManyParentId: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) + // hasManyParent: HasManyParentBiDirectionalExplicit + // @belongsTo(fields: ["hasManyParentId"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [ + HasManyParentBiDirectionalExplicit(name: 'has many parent (explicit)') + ]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildBiDirectionalExplicit( + name: 'has many child $i (explicit)', + hasManyParent: rootModels.first, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParentBiDirectionalExplicit.classType, + rootModels: rootModels, + associatedModelType: HasManyChildBiDirectionalExplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart new file mode 100644 index 0000000000..d62a89b74e --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many_bidirectional/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (bi-directional with implicit connection field', () { + // schema + // type HasManyParentBiDirectionalImplicit @model { + // id: ID! + // name: String + // biDirectionalImplicitChildren: [HasManyChildBiDirectionalImplicit] @hasMany + // } + + // type HasManyChildBiDirectionalImplicit @model { + // id: ID! + // name: String + // hasManyParent: HasManyParentBiDirectionalImplicit @belongsTo + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [ + HasManyParentBiDirectionalImplicit( + name: 'has many parent (explicit)', + ) + ]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildBiDirectionalImplicit( + name: 'has many child $i (explicit)', + hasManyParent: rootModels.first, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParentBiDirectionalImplicit.classType, + rootModels: rootModels, + associatedModelType: HasManyChildBiDirectionalImplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart new file mode 100644 index 0000000000..a4439c48e1 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'HasMany (parent refers to children with explicit connection field and indexName)', + () { + // schema + // type HasManyParent @model { + // id: ID! + // name: String + // explicitChildren: [HasManyChildExplicit] + // @hasMany(indexName: "byHasManyParent", fields: ["id"]) + // } + // type HasManyChildExplicit @model { + // id: ID! + // name: String + // hasManyParentID: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [HasManyParent(name: 'has many parent (explicit)')]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildExplicit( + name: 'has many child $i (explicit)', + hasManyParentID: rootModels.first.id, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParent.classType, + rootModels: rootModels, + associatedModelType: HasManyChildExplicit.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart new file mode 100644 index 0000000000..0e99ecfdf9 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (parent refers to children with implicit connection field)', + () { + // schema + // type HasManyParent @model { + // id: ID! + // name: String + // implicitChildren: [HasManyChild] @hasMany + // } + // type HasManyChildImplicit @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [HasManyParent(name: 'has many parent (implicit)')]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildImplicit( + name: 'has many child $i (implicit)', + hasManyParentImplicitChildrenId: rootModels.first.id, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParent.classType, + rootModels: rootModels, + associatedModelType: HasManyChildImplicit.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart new file mode 100644 index 0000000000..06a3fdf416 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart @@ -0,0 +1,56 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_one/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasOne (parent refers to child with explicit connection field)', () { + // schema + // type HasOneParent @model { + // id: ID! + // name: String + // explicitChildID: ID + // explicitChild: HasOneChild @hasOne(fields: ["explicitChildID"]) + // } + // type HasOneChild @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var associatedModels = [HasOneChild(name: 'child')]; + // Currently with @hasOne, parent -> child relationship is created + // by assign child.id to the connection field of the parent + var rootModels = [ + HasOneParent( + name: 'HasOne (explicit)', explicitChildID: associatedModels.first.id) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasOneParent.classType, + rootModels: rootModels, + associatedModelType: HasOneChild.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart new file mode 100644 index 0000000000..5dc7a1666c --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_one/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasOne (parent refers to child with implicit connection field)', () { + // schema + // type HasOneParent @model { + // id: ID! + // name: String + // implicitChild: HasOneChild @hasOne + // } + // type HasOneChild @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var associatedModels = [HasOneChild(name: 'child')]; + // Currently with @hasOne, parent -> child relationship is created + // by assign child.id to the connection field of the parent + // the connection field is automatically generated when the child + // is implicitly referred in the schema + var rootModels = [ + HasOneParent( + name: 'HasOne (implicit)', + hasOneParentImplicitChildId: associatedModels.first.id, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasOneParent.classType, + rootModels: rootModels, + associatedModelType: HasOneChild.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart new file mode 100644 index 0000000000..282e5d94de --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart @@ -0,0 +1,247 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:amplify_datastore/amplify_datastore.dart'; + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:tuple/tuple.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/many_to_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Many-to-many', () { + // schema + // type Post @model { + // id: ID! + // title: String! + // rating: Int! + // tags: [Tag] @manyToMany(relationName: "PostTags") + // } + // type Tag @model { + // id: ID! + // label: String! + // posts: [Post] @manyToMany(relationName: "PostTags") + // } + final enableCloudSync = shouldEnableCloudSync(); + var posts = [ + Post(title: 'many to many post 1', rating: 10), + Post(title: 'many to many post 2', rating: 5), + ]; + var tags = [ + Tag(label: 'many to many tag 1'), + Tag(label: 'many to maby tag 2') + ]; + var postTags = [ + PostTags(post: posts[0], tag: tags[0]), + PostTags(post: posts[0], tag: tags[0]), + PostTags(post: posts[1], tag: tags[1]), + PostTags(post: posts[1], tag: tags[1]) + ]; + late Future>> postModelEventsGetter; + late Future>> tagModelEventsGetter; + late Future>> postTagsModelEventsGetter; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + + postModelEventsGetter = createObservedEventsGetter( + Post.classType, + take: posts.length, + eventType: EventType.create, + ); + tagModelEventsGetter = createObservedEventsGetter( + Tag.classType, + take: tags.length, + eventType: EventType.create, + ); + postTagsModelEventsGetter = createObservedEventsGetter( + PostTags.classType, + take: postTags.length, + eventType: EventType.create, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(Post.classType, posts), + Tuple2(Tag.classType, tags), + Tuple2(PostTags.classType, postTags), + ]); + + testWidgets('save post', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: posts, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var post in posts) { + await Amplify.DataStore.save(post); + } + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, containsAll(posts)); + }); + + testWidgets('save tags', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: tags, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var tag in tags) { + await Amplify.DataStore.save(tag); + } + } + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, containsAll(tags)); + }); + + testWidgets('save postTags', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: postTags, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var postTag in postTags) { + await Amplify.DataStore.save(postTag); + } + } + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, containsAll(postTags)); + }); + + testWidgets('observe posts', (WidgetTester tester) async { + var events = await postModelEventsGetter; + expectObservedEventsToMatchModels(events: events, referenceModels: posts); + }); + + testWidgets('observe tags', (WidgetTester tester) async { + var events = await tagModelEventsGetter; + expectObservedEventsToMatchModels(events: events, referenceModels: tags); + }); + + testWidgets('observe postTags', (WidgetTester tester) async { + var events = await postTagsModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: postTags); + }); + + testWidgets('delete post (cascade delete associated postTag)', + (WidgetTester tester) async { + var deletedPost = posts[0]; + var deletedPostTags = postTags.getRange(0, 2).toList(); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedPost], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: deletedPostTags, + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedPost); + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, isNot(contains(deletedPost))); + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, isNot(containsAll(deletedPostTags))); + }); + + testWidgets('delete tag (cascade delete associated postTag)', + (WidgetTester tester) async { + var deletedTag = tags[1]; + var deletedPostTags = postTags.getRange(2, postTags.length).toList(); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedTag], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: deletedPostTags, + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedTag); + } + + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, isNot(contains(deletedTag))); + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, isNot(containsAll(deletedPostTags))); + }); + + testWidgets('delete remaining post', (WidgetTester tester) async { + var deletedPost = posts[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedPost], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedPost); + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, isNot(contains(deletedPost))); + }); + + testWidgets('delete remaining tag', (WidgetTester tester) async { + var deletedTag = tags[0]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedTag], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedTag); + } + + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, isNot(contains(deletedTag))); + }); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart new file mode 100644 index 0000000000..e95dcbda0a --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart @@ -0,0 +1,70 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/Blog.dart'; +import '../../../../lib/models/Comment.dart'; +import '../../../../lib/models/Post.dart'; +import '../../../../lib/models/PostTags.dart'; +import '../../../../lib/models/Tag.dart'; + +export '../../../../lib/models/Blog.dart'; +export '../../../../lib/models/Comment.dart'; +export '../../../../lib/models/Post.dart'; +export '../../../../lib/models/PostTags.dart'; +export '../../../../lib/models/Tag.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "f80fece878bf91a76f44577fe599b120"; + @override + List modelSchemas = [ + Blog.schema, + Comment.schema, + Post.schema, + PostTags.schema, + Tag.schema + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "Blog": + return Blog.classType; + case "Comment": + return Comment.classType; + case "Post": + return Post.classType; + case "PostTags": + return PostTags.classType; + case "Tag": + return Tag.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart new file mode 100644 index 0000000000..44b85c5984 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart @@ -0,0 +1,60 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/BelongsToChildExplicit.dart'; +import '../../../../lib/models/BelongsToChildImplicit.dart'; +import '../../../../lib/models/BelongsToParent.dart'; + +export '../../../../lib/models/BelongsToChildExplicit.dart'; +export '../../../../lib/models/BelongsToChildImplicit.dart'; +export '../../../../lib/models/BelongsToParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + BelongsToChildExplicit.schema, + BelongsToChildImplicit.schema, + BelongsToParent.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "BelongsToChildExplicit": + return BelongsToChildExplicit.classType; + case "BelongsToChildImplicit": + return BelongsToChildImplicit.classType; + case "BelongsToParent": + return BelongsToParent.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart new file mode 100644 index 0000000000..d9dc02abe4 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart @@ -0,0 +1,60 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/HasManyChildExplicit.dart'; +import '../../../../lib/models/HasManyChildImplicit.dart'; +import '../../../../lib/models/HasManyParent.dart'; + +export '../../../../lib/models/HasManyChildExplicit.dart'; +export '../../../../lib/models/HasManyChildImplicit.dart'; +export '../../../../lib/models/HasManyParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + HasManyChildExplicit.schema, + HasManyChildImplicit.schema, + HasManyParent.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasManyChildExplicit": + return HasManyChildExplicit.classType; + case "HasManyChildImplicit": + return HasManyChildImplicit.classType; + case "HasManyParent": + return HasManyParent.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart new file mode 100644 index 0000000000..fd16d1540c --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart @@ -0,0 +1,65 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/HasManyChildBiDirectionalExplicit.dart'; +import '../../../../lib/models/HasManyChildBiDirectionalImplicit.dart'; +import '../../../../lib/models/HasManyParentBiDirectionalExplicit.dart'; +import '../../../../lib/models/HasManyParentBiDirectionalImplicit.dart'; + +export '../../../../lib/models/HasManyChildBiDirectionalExplicit.dart'; +export '../../../../lib/models/HasManyChildBiDirectionalImplicit.dart'; +export '../../../../lib/models/HasManyParentBiDirectionalExplicit.dart'; +export '../../../../lib/models/HasManyParentBiDirectionalImplicit.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + HasManyChildBiDirectionalExplicit.schema, + HasManyChildBiDirectionalImplicit.schema, + HasManyParentBiDirectionalExplicit.schema, + HasManyParentBiDirectionalImplicit.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasManyChildBiDirectionalExplicit": + return HasManyChildBiDirectionalExplicit.classType; + case "HasManyChildBiDirectionalImplicit": + return HasManyChildBiDirectionalImplicit.classType; + case "HasManyParentBiDirectionalExplicit": + return HasManyParentBiDirectionalExplicit.classType; + case "HasManyParentBiDirectionalImplicit": + return HasManyParentBiDirectionalImplicit.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart new file mode 100644 index 0000000000..db81bf91dc --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart @@ -0,0 +1,54 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; + +import '../../../../lib/models/HasOneChild.dart'; +import '../../../../lib/models/HasOneParent.dart'; + +export '../../../../lib/models/HasOneChild.dart'; +export '../../../../lib/models/HasOneParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [HasOneChild.schema, HasOneParent.schema]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasOneChild": + return HasOneChild.classType; + case "HasOneParent": + return HasOneParent.classType; + case "ModelWithAppsyncScalarTypes": + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart new file mode 100644 index 0000000000..c90ce53a37 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart @@ -0,0 +1,68 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/Post.dart'; +import '../../../../lib/models/PostTags.dart'; +import '../../../../lib/models/Tag.dart'; +import '../../../../lib/models/Blog.dart'; +import '../../../../lib/models/Comment.dart'; + +export '../../../../lib/models/Post.dart'; +export '../../../../lib/models/PostTags.dart'; +export '../../../../lib/models/Tag.dart'; +export '../../../../lib/models/Blog.dart'; +export '../../../../lib/models/Comment.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + Blog.schema, + Comment.schema, + Post.schema, + PostTags.schema, + Tag.schema + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "Blog": + return Blog.classType; + case "Post": + return Post.classType; + case "PostTags": + return PostTags.classType; + case "Tag": + return Tag.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart new file mode 100644 index 0000000000..e37fd360e9 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart @@ -0,0 +1,61 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; + +import '../../../../lib/models/MultiRelatedAttendee.dart'; +import '../../../../lib/models/MultiRelatedMeeting.dart'; +import '../../../../lib/models/MultiRelatedRegistration.dart'; + +export '../../../../lib/models/MultiRelatedAttendee.dart'; +export '../../../../lib/models/MultiRelatedMeeting.dart'; +export '../../../../lib/models/MultiRelatedRegistration.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + MultiRelatedAttendee.schema, + MultiRelatedMeeting.schema, + MultiRelatedRegistration.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "MultiRelatedAttendee": + return MultiRelatedAttendee.classType; + case "MultiRelatedMeeting": + return MultiRelatedMeeting.classType; + case "MultiRelatedRegistration": + return MultiRelatedRegistration.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart new file mode 100644 index 0000000000..0497d05a2f --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart @@ -0,0 +1,276 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:amplify_datastore/amplify_datastore.dart'; + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:tuple/tuple.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/multi_relationship/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Model with multiple relationships', () { + // schema + // type MultiRelatedMeeting @model { + // id: ID! @primaryKey + // title: String! + // attendees: [MultiRelatedRegistration] + // @hasMany(indexName: "byMeeting", fields: ["id"]) + // } + + // type MultiRelatedAttendee @model { + // id: ID! @primaryKey + // meetings: [MultiRelatedRegistration] + // @hasMany(indexName: "byAttendee", fields: ["id"]) + // } + + // type MultiRelatedRegistration @model { + // id: ID! @primaryKey + // meetingId: ID @index(name: "byMeeting", sortKeyFields: ["attendeeId"]) + // meeting: MultiRelatedMeeting! @belongsTo(fields: ["meetingId"]) + // attendeeId: ID @index(name: "byAttendee", sortKeyFields: ["meetingId"]) + // attendee: MultiRelatedAttendee! @belongsTo(fields: ["attendeeId"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var meetings = [ + MultiRelatedMeeting(title: 'test meeting 1'), + MultiRelatedMeeting(title: 'test meeting 2'), + ]; + var attendees = [ + MultiRelatedAttendee(), + MultiRelatedAttendee(), + ]; + var registrations = [ + MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[0]), + MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[1]), + MultiRelatedRegistration(attendee: attendees[1], meeting: meetings[1]), + ]; + + late Future>> + meetingModelEventsGetter; + late Future>> + attendeeModelEventsGetter; + late Future>> + registrationModelEventsGetter; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + + meetingModelEventsGetter = createObservedEventsGetter( + MultiRelatedMeeting.classType, + take: meetings.length, + eventType: EventType.create, + ); + attendeeModelEventsGetter = createObservedEventsGetter( + MultiRelatedAttendee.classType, + take: attendees.length, + eventType: EventType.create, + ); + registrationModelEventsGetter = createObservedEventsGetter( + MultiRelatedRegistration.classType, + take: registrations.length, + eventType: EventType.create, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(MultiRelatedMeeting.classType, meetings), + Tuple2(MultiRelatedAttendee.classType, attendees), + Tuple2(MultiRelatedRegistration.classType, registrations), + ]); + + testWidgets('save meetings', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: meetings, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var meeting in meetings) { + await Amplify.DataStore.save(meeting); + } + } + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, containsAll(meetings)); + }); + + testWidgets('save attendees', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: attendees, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var attendee in attendees) { + await Amplify.DataStore.save(attendee); + } + } + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, containsAll(attendees)); + }); + + testWidgets('save registrations', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: registrations, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var registration in registrations) { + await Amplify.DataStore.save(registration); + } + } + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, containsAll(registrations)); + }); + + testWidgets('observe meetings', (WidgetTester tester) async { + var events = await meetingModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: meetings); + }); + + testWidgets('observe attendees', (WidgetTester tester) async { + var events = await attendeeModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: attendees); + }); + + testWidgets('observe registrations', (WidgetTester tester) async { + var events = await registrationModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: registrations); + }); + + testWidgets('delete meeting (cascade delete associated registration)', + (WidgetTester tester) async { + var deletedMeeting = meetings[0]; // cascade delete registration[0] + var deletedRegistration = registrations[0]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedMeeting], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedMeeting); + } + + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, isNot(contains(deletedMeeting))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete attendee (cascade delete associated registration)', + (WidgetTester tester) async { + var deletedAttendee = attendees[0]; + var deletedRegistration = registrations[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedAttendee], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedAttendee); + } + + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, isNot(contains(deletedAttendee))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete remaining meeting', (WidgetTester tester) async { + var deletedMeeting = meetings[1]; + var deletedRegistration = registrations[2]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedMeeting], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedMeeting); + } + + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, isNot(contains(deletedMeeting))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete remaining attendee', (WidgetTester tester) async { + var deletedAttendee = attendees[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedAttendee], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedAttendee); + } + + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, isNot(contains(deletedAttendee))); + }); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart b/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart index 667065c7d7..1e59334a6b 100644 --- a/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart +++ b/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart @@ -13,17 +13,41 @@ // permissions and limitations under the License. // +import 'dart:async'; + import 'package:amplify_datastore/amplify_datastore.dart'; +import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_datastore_example/amplifyconfiguration.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; -Future configureDataStore() async { +const ENABLE_CLOUD_SYNC = + bool.fromEnvironment('ENABLE_CLOUD_SYNC', defaultValue: false); +const DATASTORE_READY_EVENT_TIMEOUT = const Duration(minutes: 2); +const DELAY_TO_START_DATASTORE = const Duration(milliseconds: 500); +const DELAY_TO_CLEAR_DATASTORE = const Duration(seconds: 2); + +/// Configure [AmplifyDataStore] plugin with given [modelProvider]. +/// When [ENABLE_CLOUD_SYNC] environment variable is set to true, it also +/// configures [AmplifyAPI] and starts DataStore API sync automatically. +Future configureDataStore({ + bool enableCloudSync = false, + ModelProviderInterface? modelProvider, +}) async { if (!Amplify.isConfigured) { - final dataStorePlugin = - AmplifyDataStore(modelProvider: ModelProvider.instance); - await Amplify.addPlugins([dataStorePlugin]); + final dataStorePlugin = AmplifyDataStore( + modelProvider: modelProvider ?? ModelProvider.instance); + List plugins = [dataStorePlugin]; + if (enableCloudSync) { + plugins.add(AmplifyAPI()); + } + await Amplify.addPlugins(plugins); await Amplify.configure(amplifyconfig); + + // Start DataStore API sync after Amplify Configure when cloud sync is enabled + if (enableCloudSync) { + await startDataStore(); + } } } @@ -34,6 +58,38 @@ Future configureDataStore() async { /// /// see: https:///github.com/aws-amplify/amplify-android/issues/1464 Future clearDataStore() async { - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(DELAY_TO_CLEAR_DATASTORE); await Amplify.DataStore.clear(); } + +/// Am async operator that starts DataStore API sync. +/// It returns a Future which complets when [Amplify.Hub] receives the ready +/// event on [HubChannel.DataStore], or completes with an timeout error if +/// the ready event hasn't been emitted with in 2 minutes. +class DataStoreStarter { + final Completer _completer = Completer(); + late StreamSubscription hubSubscription; + + Future startDataStore() { + hubSubscription = Amplify.Hub.listen([HubChannel.DataStore], (event) { + if (event.eventName == 'ready') { + print( + 'πŸŽ‰πŸŽ‰πŸŽ‰DataStore is ready to start running test suites with API sync enabled.πŸŽ‰πŸŽ‰πŸŽ‰'); + hubSubscription.cancel(); + _completer.complete(); + } + }); + + // we are not waiting for DataStore.start to complete + // but an asynchronous DataStore ready event dispatched via the hub + Amplify.DataStore.start(); + return _completer.future.timeout(DATASTORE_READY_EVENT_TIMEOUT); + } +} + +Future startDataStore() async { + await Future.delayed(DELAY_TO_START_DATASTORE); + await DataStoreStarter().startDataStore(); +} + +bool shouldEnableCloudSync() => ENABLE_CLOUD_SYNC; diff --git a/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart b/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart index d89389f3a4..de32b85ea3 100644 --- a/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart +++ b/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart @@ -13,7 +13,6 @@ // permissions and limitations under the License. // -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart b/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart new file mode 100644 index 0000000000..afa5d900ea --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart @@ -0,0 +1,308 @@ +import 'package:amplify_datastore/amplify_datastore.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tuple/tuple.dart'; + +import 'setup_utils.dart'; +import 'wait_for_expected_event_from_hub.dart'; + +typedef EventsAssertor = void Function( + List> events); +typedef ModelOperator = Future Function(T model, + {QueryPredicate? where}); + +/// Check each of the [events] is presenting a deleted model. +void modelIsDeletedAssertor( + List> events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); +} + +/// Check each of the [events] is presenting a model that is not deleted. +void modelIsNotDeletedAssertor( + List> events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); +} + +/// A until function asserts cloud synced result of a model operation. +/// +/// It applies [rootModelOperator] to each of the [rootModels] then waits for +/// cloud sync event to be dispatched, and extract events satisfy +/// [expectedRootModelVersion] and then run [rootModelEventsAssertor] on each +/// of the extracted events. +/// +/// [associatedModels] can be provided to verify automated processes such as +/// cascade delete. [expectedAssociatedModelVersion] and +/// [associatedModelEventsAssertor] can be different from the ones set up for +/// the root models. +/// +/// [rootModelOperator] and [associatedModelOperator] can be passed with +/// [rootModelOperationPredicate] and [associatedModelOperationPredicate] +/// respectively. +Future testCloudSyncedModelOperation({ + required List rootModels, + required int expectedRootModelVersion, + required ModelOperator rootModelOperator, + required EventsAssertor rootModelEventsAssertor, + QueryPredicate? rootModelOperationPredicate, + List? associatedModels, + int? expectedAssociatedModelVersion, + ModelOperator? associatedModelOperator, + QueryPredicate? associatedModelOperationPredicate, + EventsAssertor? associatedModelEventsAssertor, +}) async { + final assertingAssociatedModels = associatedModels != null && + associatedModels.isNotEmpty && + expectedAssociatedModelVersion != null && + associatedModelEventsAssertor != null; + + var syncEventsGetters = rootModels + .map( + (rootModel) => getExpectedSubscriptionDataProcessedEvent( + eventMatcher: (event) { + var model = event.element.model; + + if (model is R) { + return model.getId() == rootModel.getId() && + event.element.version == expectedRootModelVersion; + } + + return false; + }, + ), + ) + .toList(); + + if (assertingAssociatedModels) { + var associatedModelsSyncedEventsGetters = associatedModels!.map( + (associatedModel) => getExpectedSubscriptionDataProcessedEvent( + eventMatcher: (event) { + var model = event.element.model; + + if (model is A) { + return model.getId() == associatedModel.getId() && + event.element.version == expectedRootModelVersion; + } + + return false; + }, + ), + ); + + syncEventsGetters.addAll(associatedModelsSyncedEventsGetters); + } + + for (var rootModel in rootModels) { + await rootModelOperator(rootModel); + } + + if (associatedModelOperator != null) { + for (var associatedModel in associatedModels!) { + await associatedModelOperator(associatedModel); + } + } + + var syncedEvents = await Future.wait(syncEventsGetters); + + rootModelEventsAssertor(syncedEvents.sublist(0, rootModels.length)); + + if (associatedModelEventsAssertor != null) { + associatedModelEventsAssertor(syncedEvents.sublist(rootModels.length)); + } +} + +/// Create am async getter to capture [take] of observed events on [modelType] +/// with desired [eventType]. +Future>> createObservedEventsGetter( + ModelType modelType, { + required int take, + required EventType eventType, +}) => + Amplify.DataStore.observe(modelType) + .where((event) => event.eventType == eventType) + .distinct((prev, next) => + prev.eventType == next.eventType && + prev.item.getId() == next.item.getId()) + .take(take) + .toList(); + +/// A util function runs [testWidgets] to validate [modelSpecs] specified +/// models are not in local storage. +void expectModelsNotToBeInLocalStorage( + List>> modelSpecs) { + testWidgets('testing models are not in local storage', + (WidgetTester tester) async { + for (var modelSpec in modelSpecs) { + var modelsInLocalStorage = Amplify.DataStore.query(modelSpec.item1); + expect(modelsInLocalStorage, isNot(containsAll(modelSpec.item2))); + } + }); +} + +/// A util function valid each of the [events] contained model is matching the +/// model contained in [referenceModels]. +void expectObservedEventsToMatchModels({ + required List> events, + required List referenceModels, +}) async { + for (var i = 0; i < referenceModels.length; i++) { + var event = events[i]; + var syncedRootModel = event.item; + var rootModel = referenceModels[i]; + expect(syncedRootModel, rootModel); + } +} + +/// A until function runs [testWidgets] to validate model operations on related +/// [rootModelType] and [associatedModelType]. +/// +/// This function runs save and delete operations on each of the +/// [rootModels]. +/// +/// This function runs save operation on each of the [associatedModels], and +/// delete operation if [supportCascadeDelete] is set as `false`. +/// +/// This function runs tests with cloud sync enabled when [enableCloudSync] is +/// set as `true`. +void testRootAndAssociatedModelsRelationship({ + required ModelProviderInterface modelProvider, + required ModelType rootModelType, + required List rootModels, + required ModelType associatedModelType, + required List associatedModels, + bool enableCloudSync = false, + bool supportCascadeDelete = false, +}) { + late Future>> observedRootModelsEvents; + late Future>> observedAssociatedModelsEvents; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: modelProvider, + ); + + observedRootModelsEvents = createObservedEventsGetter( + rootModelType, + eventType: EventType.create, + take: rootModels.length, + ); + + observedAssociatedModelsEvents = createObservedEventsGetter( + associatedModelType, + eventType: EventType.create, + take: associatedModels.length, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(rootModelType, rootModels), + Tuple2(associatedModelType, associatedModels), + ]); + + testWidgets('save root models', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: rootModels, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var rootModel in rootModels) { + await Amplify.DataStore.save(rootModel); + } + } + + var savedRootModels = await Amplify.DataStore.query(rootModelType); + expect(savedRootModels, containsAll(rootModels)); + }); + + testWidgets('save associated models', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: associatedModels, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var associatedModel in associatedModels) { + await Amplify.DataStore.save(associatedModel); + } + } + + var savedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(savedAssociatedModels, containsAll(associatedModels)); + }); + + testWidgets('observed root models creation events', + (WidgetTester tester) async { + var events = await observedRootModelsEvents; + expectObservedEventsToMatchModels( + events: events, referenceModels: rootModels); + }); + + testWidgets('observed associated models creation events', + (WidgetTester tester) async { + var events = await observedAssociatedModelsEvents; + expectObservedEventsToMatchModels( + events: events, referenceModels: associatedModels); + }); + + testWidgets( + 'delete root models${supportCascadeDelete ? ' (cascade delete associated models)' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: rootModels, + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: supportCascadeDelete ? associatedModels : null, + expectedAssociatedModelVersion: supportCascadeDelete ? 2 : null, + associatedModelEventsAssertor: + supportCascadeDelete ? modelIsDeletedAssertor : null, + ); + } else { + for (var rootModel in rootModels) { + await Amplify.DataStore.delete(rootModel); + } + } + var queriedRootModels = await Amplify.DataStore.query(rootModelType); + expect(queriedRootModels, isNot(containsAll(rootModels))); + + if (supportCascadeDelete) { + var queriedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(queriedAssociatedModels, isNot(containsAll(associatedModels))); + } + }); + + if (!supportCascadeDelete) { + testWidgets( + 'delete associated models separately as cascade delete is not support with this relationship', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: associatedModels, + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + for (var associatedModel in associatedModels) { + await Amplify.DataStore.delete(associatedModel); + } + } + + var queriedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(queriedAssociatedModels, isNot(containsAll(associatedModels))); + }); + } +} diff --git a/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart b/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart new file mode 100644 index 0000000000..444f9357be --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; + +const DEFAULT_TIMEOUT = const Duration(seconds: 20); + +class WaitForExpectedEventFromHub { + final Completer _completer = Completer(); + late StreamSubscription hubSubscription; + final Function eventMatcher; + final String eventName; + Duration timeout; + + WaitForExpectedEventFromHub({ + required this.eventMatcher, + required this.eventName, + Duration timeout = DEFAULT_TIMEOUT, + }) : this.timeout = timeout; + + Future start() { + hubSubscription = Amplify.Hub.listen([HubChannel.DataStore], (event) { + if (event.eventName == this.eventName) { + if (this.eventMatcher(event.payload)) { + hubSubscription.cancel(); + _completer.complete(event.payload as T); + } + } + }); + + return _completer.future.timeout(timeout); + } +} + +Future + getExpectedSubscriptionDataProcessedEvent({ + required bool Function(SubscriptionDataProcessedEvent) eventMatcher, +}) async { + var getter = WaitForExpectedEventFromHub( + eventName: 'subscriptionDataProcessed', + eventMatcher: (HubEventPayload eventPayload) { + if (eventPayload is SubscriptionDataProcessedEvent) { + return eventMatcher(eventPayload); + } + + return false; + }, + ); + return getter.start(); +} diff --git a/packages/amplify_datastore/example/pubspec.yaml b/packages/amplify_datastore/example/pubspec.yaml index 625e5cb004..335e67b8e1 100644 --- a/packages/amplify_datastore/example/pubspec.yaml +++ b/packages/amplify_datastore/example/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: path: ../../amplify_test flutter_test: sdk: flutter + tuple: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/amplify_datastore/example/tool/add_api_request.json b/packages/amplify_datastore/example/tool/add_api_request.json index cedcb66c37..bac4c4e153 100644 --- a/packages/amplify_datastore/example/tool/add_api_request.json +++ b/packages/amplify_datastore/example/tool/add_api_request.json @@ -4,9 +4,14 @@ "serviceName": "AppSync", "apiName": "dataStoreIntegrationTestGraphQL", "transformSchema": "", - "defaultAuthType": { + "defaultAuthType": { "mode": "API_KEY", "expirationTime": 365 + }, + "conflictResolution": { + "defaultResolutionStrategy": { + "type": "AUTOMERGE" + } } } } diff --git a/packages/amplify_datastore/example/tool/schema.graphql b/packages/amplify_datastore/example/tool/schema.graphql index 458138b560..ba45f73790 100644 --- a/packages/amplify_datastore/example/tool/schema.graphql +++ b/packages/amplify_datastore/example/tool/schema.graphql @@ -12,7 +12,7 @@ type Post @model { title: String! rating: Int! created: AWSDateTime - blogID: ID! @index(name: "byBlog") + blogID: ID @index(name: "byBlog") blog: Blog @belongsTo(fields: ["blogID"]) comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) tags: [Tag] @manyToMany(relationName: "PostTags") diff --git a/packages/amplify_datastore/lib/method_channel_datastore.dart b/packages/amplify_datastore/lib/method_channel_datastore.dart index 0988d49579..785c7cf1cc 100644 --- a/packages/amplify_datastore/lib/method_channel_datastore.dart +++ b/packages/amplify_datastore/lib/method_channel_datastore.dart @@ -16,7 +16,6 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:flutter/services.dart'; -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; import 'types/observe_query_executor.dart';