From 6aaf966e7907cba71b9d2cf6f84ae2dc6e4d50b0 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Wed, 4 Sep 2024 13:59:27 -0500 Subject: [PATCH] chore(datastore): Add multi auth integration tests (#5204) --- .github/dependabot.yaml | 12 +- .../workflows/amplify_datastore_example.yaml | 32 ++ .../basic_auth_model_operation_test.dart | 195 ++++++++++ .../models/auth/ModelProvider.dart | 62 +++ .../integration_test/utils/setup_utils.dart | 12 +- .../amplify_datastore/example/lib/main.dart | 354 ++---------------- .../CustomTypeWithAppsyncScalarTypes.dart | 3 +- .../example/lib/models/ModelProvider.dart | 17 +- .../lib/models/ModelProviderExampleApp.dart | 88 +++++ .../lib/models/ModelWithCustomType.dart | 3 +- .../example/lib/models/MultiAuthTodo.dart | 242 ++++++++++++ .../example/lib/models/MultiTodo.dart | 238 ++++++++++++ .../example/lib/models/PrivateTodo.dart | 228 +++++++++++ .../example/lib/widgets/auth_view.dart | 161 ++++++++ .../{ => widgets}/event_display_widgets.dart | 18 + .../lib/widgets/navigator_scaffold.dart | 65 ++++ .../example/lib/widgets/public_view.dart | 353 +++++++++++++++++ .../queries_display_widgets.dart | 29 ++ .../lib/{ => widgets}/save_model_widgets.dart | 0 .../amplify_datastore/example/pubspec.yaml | 8 +- .../example/tool/add_api_request.json | 17 - .../example/tool/deply_backend.md | 79 ++++ .../example/tool/pre_sign_up.js | 25 ++ .../provision_integration_test_resources.sh | 13 +- .../example/tool/schema.graphql | 17 + .../graphql/api_key_test.dart | 15 +- .../integration_test/graphql/iam_test.dart | 17 +- .../graphql/user_pools_test.dart | 13 +- .../example/integration_test/main_test.dart | 24 +- .../example/integration_test/rest_test.dart | 14 +- .../example/integration_test/util.dart | 84 ----- .../lib/amplify_integration_test.dart | 1 + .../auth_cognito/test_user.dart | 93 +++++ 33 files changed, 2047 insertions(+), 485 deletions(-) create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_auth_model_operation_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/auth/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/lib/models/ModelProviderExampleApp.dart create mode 100644 packages/amplify_datastore/example/lib/models/MultiAuthTodo.dart create mode 100644 packages/amplify_datastore/example/lib/models/MultiTodo.dart create mode 100644 packages/amplify_datastore/example/lib/models/PrivateTodo.dart create mode 100644 packages/amplify_datastore/example/lib/widgets/auth_view.dart rename packages/amplify_datastore/example/lib/{ => widgets}/event_display_widgets.dart (76%) create mode 100644 packages/amplify_datastore/example/lib/widgets/navigator_scaffold.dart create mode 100644 packages/amplify_datastore/example/lib/widgets/public_view.dart rename packages/amplify_datastore/example/lib/{ => widgets}/queries_display_widgets.dart (87%) rename packages/amplify_datastore/example/lib/{ => widgets}/save_model_widgets.dart (100%) delete mode 100644 packages/amplify_datastore/example/tool/add_api_request.json create mode 100644 packages/amplify_datastore/example/tool/deply_backend.md create mode 100644 packages/amplify_datastore/example/tool/pre_sign_up.js create mode 100644 packages/test/amplify_integration_test/lib/src/integration_test_utils/auth_cognito/test_user.dart diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index c270cd92d0..fff48a8f64 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -540,12 +540,16 @@ updates: - dependency-name: "worker_bee_builder" - dependency-name: "amplify_datastore" - dependency-name: "amplify_datastore_plugin_interface" - - dependency-name: "amplify_auth_cognito_dart" + - dependency-name: "amplify_auth_cognito" + - dependency-name: "amplify_analytics_pinpoint" - dependency-name: "amplify_analytics_pinpoint_dart" - dependency-name: "amplify_db_common_dart" - dependency-name: "smithy" - dependency-name: "smithy_aws" + - dependency-name: "amplify_db_common" + - dependency-name: "amplify_auth_cognito_dart" - dependency-name: "smithy_codegen" + - dependency-name: "amplify_authenticator" - package-ecosystem: "pub" directory: "packages/amplify_datastore/example" schedule: @@ -569,12 +573,16 @@ updates: - dependency-name: "worker_bee_builder" - dependency-name: "amplify_datastore" - dependency-name: "amplify_datastore_plugin_interface" - - dependency-name: "amplify_auth_cognito_dart" + - dependency-name: "amplify_auth_cognito" + - dependency-name: "amplify_analytics_pinpoint" - dependency-name: "amplify_analytics_pinpoint_dart" - dependency-name: "amplify_db_common_dart" - dependency-name: "smithy" - dependency-name: "smithy_aws" + - dependency-name: "amplify_db_common" + - dependency-name: "amplify_auth_cognito_dart" - dependency-name: "smithy_codegen" + - dependency-name: "amplify_authenticator" - package-ecosystem: "pub" directory: "packages/amplify_datastore_plugin_interface" schedule: diff --git a/.github/workflows/amplify_datastore_example.yaml b/.github/workflows/amplify_datastore_example.yaml index 077db33801..4d93d67d9d 100644 --- a/.github/workflows/amplify_datastore_example.yaml +++ b/.github/workflows/amplify_datastore_example.yaml @@ -26,18 +26,34 @@ on: - 'packages/amplify_datastore_plugin_interface/pubspec.yaml' - 'packages/amplify_lints/lib/**/*.yaml' - 'packages/amplify_lints/pubspec.yaml' + - 'packages/analytics/amplify_analytics_pinpoint/android/**/*' + - 'packages/analytics/amplify_analytics_pinpoint/lib/**/*.dart' + - 'packages/analytics/amplify_analytics_pinpoint/pubspec.yaml' - 'packages/analytics/amplify_analytics_pinpoint_dart/lib/**/*.dart' - 'packages/analytics/amplify_analytics_pinpoint_dart/pubspec.yaml' - 'packages/api/amplify_api/lib/**/*.dart' - 'packages/api/amplify_api/pubspec.yaml' - 'packages/api/amplify_api_dart/lib/**/*.dart' - 'packages/api/amplify_api_dart/pubspec.yaml' + - 'packages/auth/amplify_auth_cognito/android/**/*' + - 'packages/auth/amplify_auth_cognito/darwin/**/*' + - 'packages/auth/amplify_auth_cognito/lib/**/*.dart' + - 'packages/auth/amplify_auth_cognito/pubspec.yaml' - 'packages/auth/amplify_auth_cognito_dart/lib/**/*.dart' - 'packages/auth/amplify_auth_cognito_dart/pubspec.yaml' + - 'packages/authenticator/amplify_authenticator/lib/**/*.dart' + - 'packages/authenticator/amplify_authenticator/pubspec.yaml' - 'packages/aws_common/lib/**/*.dart' - 'packages/aws_common/pubspec.yaml' - 'packages/aws_signature_v4/lib/**/*.dart' - 'packages/aws_signature_v4/pubspec.yaml' + - 'packages/common/amplify_db_common/android/**/*' + - 'packages/common/amplify_db_common/ios/**/*' + - 'packages/common/amplify_db_common/lib/**/*.dart' + - 'packages/common/amplify_db_common/linux/**/*' + - 'packages/common/amplify_db_common/macos/**/*' + - 'packages/common/amplify_db_common/pubspec.yaml' + - 'packages/common/amplify_db_common/windows/**/*' - 'packages/common/amplify_db_common_dart/lib/**/*.dart' - 'packages/common/amplify_db_common_dart/pubspec.yaml' - 'packages/secure_storage/amplify_secure_storage/android/**/*' @@ -79,18 +95,34 @@ on: - 'packages/amplify_datastore_plugin_interface/pubspec.yaml' - 'packages/amplify_lints/lib/**/*.yaml' - 'packages/amplify_lints/pubspec.yaml' + - 'packages/analytics/amplify_analytics_pinpoint/android/**/*' + - 'packages/analytics/amplify_analytics_pinpoint/lib/**/*.dart' + - 'packages/analytics/amplify_analytics_pinpoint/pubspec.yaml' - 'packages/analytics/amplify_analytics_pinpoint_dart/lib/**/*.dart' - 'packages/analytics/amplify_analytics_pinpoint_dart/pubspec.yaml' - 'packages/api/amplify_api/lib/**/*.dart' - 'packages/api/amplify_api/pubspec.yaml' - 'packages/api/amplify_api_dart/lib/**/*.dart' - 'packages/api/amplify_api_dart/pubspec.yaml' + - 'packages/auth/amplify_auth_cognito/android/**/*' + - 'packages/auth/amplify_auth_cognito/darwin/**/*' + - 'packages/auth/amplify_auth_cognito/lib/**/*.dart' + - 'packages/auth/amplify_auth_cognito/pubspec.yaml' - 'packages/auth/amplify_auth_cognito_dart/lib/**/*.dart' - 'packages/auth/amplify_auth_cognito_dart/pubspec.yaml' + - 'packages/authenticator/amplify_authenticator/lib/**/*.dart' + - 'packages/authenticator/amplify_authenticator/pubspec.yaml' - 'packages/aws_common/lib/**/*.dart' - 'packages/aws_common/pubspec.yaml' - 'packages/aws_signature_v4/lib/**/*.dart' - 'packages/aws_signature_v4/pubspec.yaml' + - 'packages/common/amplify_db_common/android/**/*' + - 'packages/common/amplify_db_common/ios/**/*' + - 'packages/common/amplify_db_common/lib/**/*.dart' + - 'packages/common/amplify_db_common/linux/**/*' + - 'packages/common/amplify_db_common/macos/**/*' + - 'packages/common/amplify_db_common/pubspec.yaml' + - 'packages/common/amplify_db_common/windows/**/*' - 'packages/common/amplify_db_common_dart/lib/**/*.dart' - 'packages/common/amplify_db_common_dart/pubspec.yaml' - 'packages/secure_storage/amplify_secure_storage/android/**/*' diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_auth_model_operation_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_auth_model_operation_test.dart new file mode 100644 index 0000000000..e3786e35c1 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_auth_model_operation_test.dart @@ -0,0 +1,195 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/auth/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final enableCloudSync = shouldEnableCloudSync(); + group( + 'Basic auth model operation${enableCloudSync ? ' with API sync 🌩 enabled' : ''} -', + () { + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + }); + tearDownAll(() async { + if (enableCloudSync) { + await deleteTestUser(testUser); + } + }); + + group("owner only auth model", () { + PrivateTodo testPrivateTodo = PrivateTodo(content: 'test content'); + testWidgets( + 'should save a new model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testPrivateTodo], + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(testPrivateTodo); + } + + var queriedTodos = await Amplify.DataStore.query(PrivateTodo.classType); + expect(queriedTodos, contains(testPrivateTodo)); + }); + + testWidgets( + 'should update existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + var updatedTestTodo = + testPrivateTodo.copyWith(content: "updated test todo"); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [updatedTestTodo], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(updatedTestTodo); + } + + var queriedTodos = await Amplify.DataStore.query( + PrivateTodo.classType, + where: PrivateTodo.ID.eq(updatedTestTodo.id), + ); + + expect(queriedTodos, contains(updatedTestTodo)); + }, + ); + + testWidgets( + 'should delete existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testPrivateTodo], + expectedRootModelVersion: 3, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); + }, + ); + } else { + await Amplify.DataStore.delete(testPrivateTodo); + } + + var queriedTodos = + await Amplify.DataStore.query(PrivateTodo.classType); + + // verify Todo was deleted + expect(queriedTodos, isNot(contains(testPrivateTodo))); + }, + ); + }); + + group("multi auth model", () { + MultiAuthTodo testMultiAuthTodo = MultiAuthTodo(content: 'test content'); + + testWidgets( + 'should save a new model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testMultiAuthTodo], + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(testMultiAuthTodo); + } + + var queriedTodos = + await Amplify.DataStore.query(MultiAuthTodo.classType); + expect(queriedTodos, contains(testMultiAuthTodo)); + }); + + testWidgets( + 'should update existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + var updatedTestTodo = + testMultiAuthTodo.copyWith(content: "updated test todo"); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [updatedTestTodo], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(updatedTestTodo); + } + + var queriedTodos = await Amplify.DataStore.query( + MultiAuthTodo.classType, + where: MultiAuthTodo.ID.eq(updatedTestTodo.id), + ); + + expect(queriedTodos, contains(updatedTestTodo)); + }, + ); + + testWidgets( + 'should delete existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testMultiAuthTodo], + expectedRootModelVersion: 3, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); + }, + ); + } else { + await Amplify.DataStore.delete(testMultiAuthTodo); + } + + var queriedTodos = + await Amplify.DataStore.query(MultiAuthTodo.classType); + + // verify Todo was deleted + expect(queriedTodos, isNot(contains(testMultiAuthTodo))); + }, + ); + }); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/auth/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/auth/ModelProvider.dart new file mode 100644 index 0000000000..e01ff2b85e --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/auth/ModelProvider.dart @@ -0,0 +1,62 @@ +/* +* 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, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +import '../../../../lib/models/MultiAuthTodo.dart'; +import '../../../../lib/models/PrivateTodo.dart'; + +export '../../../../lib/models/MultiAuthTodo.dart'; +export '../../../../lib/models/PrivateTodo.dart'; + +class ModelProvider implements amplify_core.ModelProviderInterface { + @override + String version = "44ab790c924e12028850f0fe58e4adb4"; + @override + List modelSchemas = [ + MultiAuthTodo.schema, + PrivateTodo.schema, + ]; + @override + List customTypeSchemas = []; + static final ModelProvider _instance = ModelProvider(); + + static ModelProvider get instance => _instance; + + amplify_core.ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "MultiAuthTodo": + return MultiAuthTodo.classType; + case "PrivateTodo": + return PrivateTodo.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} + +class ModelFieldValue { + const ModelFieldValue.value(this.value); + + final T value; +} 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 54de45602d..c883666f28 100644 --- a/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart +++ b/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart @@ -4,16 +4,19 @@ import 'dart:async'; import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/amplifyconfiguration.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; const ENABLE_CLOUD_SYNC = bool.fromEnvironment('ENABLE_CLOUD_SYNC', defaultValue: false); const DATASTORE_READY_EVENT_TIMEOUT = const Duration(minutes: 10); const DELAY_TO_CLEAR_DATASTORE = const Duration(seconds: 2); const DELAY_FOR_OBSERVE = const Duration(milliseconds: 100); +TestUser? testUser; /// Configure [AmplifyDataStore] plugin with given [modelProvider]. /// When [ENABLE_CLOUD_SYNC] environment variable is set to true, it also @@ -24,16 +27,23 @@ Future configureDataStore({ }) async { if (!Amplify.isConfigured) { final dataStorePlugin = AmplifyDataStore( - modelProvider: modelProvider ?? ModelProvider.instance); + modelProvider: modelProvider ?? ModelProvider.instance, + options: DataStorePluginOptions( + authModeStrategy: AuthModeStrategy.multiAuth, + ), + ); List plugins = [dataStorePlugin]; if (enableCloudSync) { plugins.add(AmplifyAPI()); } + plugins.add(AmplifyAuthCognito()); await Amplify.addPlugins(plugins); await Amplify.configure(amplifyconfig); // Start DataStore API sync after Amplify Configure when cloud sync is enabled if (enableCloudSync) { + testUser = await signUpTestUser(testUser); + await signInTestUser(testUser); await startDataStore(); } } diff --git a/packages/amplify_datastore/example/lib/main.dart b/packages/amplify_datastore/example/lib/main.dart index 3789a5d1d5..73725bc75f 100644 --- a/packages/amplify_datastore/example/lib/main.dart +++ b/packages/amplify_datastore/example/lib/main.dart @@ -5,19 +5,21 @@ library sample_app; import 'dart:async'; +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_authenticator/amplify_authenticator.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; -// Uncomment the below line to enable online sync -// import 'package:amplify_api/amplify_api.dart'; - +import 'package:amplify_datastore_example/amplifyconfiguration.dart'; +import 'package:amplify_datastore_example/widgets/navigator_scaffold.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'models/ModelProvider.dart'; +import 'models/ModelProviderExampleApp.dart'; -part 'event_display_widgets.dart'; -part 'queries_display_widgets.dart'; -part 'save_model_widgets.dart'; +part 'widgets/event_display_widgets.dart'; +part 'widgets/queries_display_widgets.dart'; +part 'widgets/save_model_widgets.dart'; void main() { runApp(MyApp()); @@ -34,33 +36,8 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - List _posts = []; - List _comments = []; - List _blogs = []; - List _postStreamingData = []; - List _blogStreamingData = []; - List _commentStreamingData = []; - bool _isAmplifyConfigured = false; - String _queriesToView = "Post"; //default view - Blog? _selectedBlogForNewPost; - Post? _selectedPostForNewComment; - late Stream> postStream; - late Stream> blogStream; - late Stream> commentStream; - late StreamSubscription hubSubscription; - bool _listeningToHub = true; late AmplifyDataStore datastorePlugin; - - final _titleController = TextEditingController(); - final _ratingController = TextEditingController(); - final _nameController = TextEditingController(); - final _contentController = TextEditingController(); - ScrollController _postScrollController = - ScrollController(initialScrollOffset: 50.0); - ScrollController _blogScrollController = - ScrollController(initialScrollOffset: 50.0); - ScrollController _commentScrollController = - ScrollController(initialScrollOffset: 50.0); + bool _isAmplifyConfigured = false; @override void initState() { @@ -76,325 +53,38 @@ class _MyAppState extends State { options: DataStorePluginOptions( errorHandler: ((error) => {print("Custom ErrorHandler received: " + error.toString())}), + authModeStrategy: AuthModeStrategy.multiAuth, ), ); await Amplify.addPlugin(datastorePlugin); // Configure - // Uncomment the below lines to enable online sync. - // await Amplify.addPlugin(AmplifyAPI()); - // await Amplify.configure(amplifyconfig); - - // Remove this line when using the lines above for online sync - await Amplify.configure("{}"); + // Comment the below lines to disable remote sync. + await Amplify.addPlugin(AmplifyAuthCognito()); + await Amplify.addPlugin(AmplifyAPI()); + await Amplify.configure(amplifyconfig); + // replace the above two lines with this to force local sync only + // await Amplify.configure("{}"); } on AmplifyAlreadyConfiguredException { print( 'Amplify was already configured. Looks like app restarted on android.'); } - listenToHub(); - - Amplify.DataStore.observeQuery( - Blog.classType, - ).listen((QuerySnapshot snapshot) { - var count = snapshot.items.length; - var now = DateTime.now().toIso8601String(); - bool status = snapshot.isSynced; - print( - '[Observe Query] Blog snapshot received with $count models, status: $status at $now'); - setState(() { - _blogs = snapshot.items; - }); - }); - - Amplify.DataStore.observeQuery( - Post.classType, - ).listen((QuerySnapshot snapshot) { - setState(() { - _posts = snapshot.items; - }); - }); - - Amplify.DataStore.observeQuery( - Comment.classType, - ).listen((QuerySnapshot snapshot) { - setState(() { - _comments = snapshot.items; - }); - }); - - // setup streams - postStream = Amplify.DataStore.observe(Post.classType); - postStream.listen((event) { - _postStreamingData.add('Post: ' + - (event.eventType.toString() == EventType.delete.toString() - ? event.item.id - : event.item.title) + - ', of type: ' + - event.eventType.toString()); - }).onError((error) => print(error)); - - blogStream = Amplify.DataStore.observe(Blog.classType); - blogStream.listen((event) { - _blogStreamingData.add('Blog: ' + - (event.eventType.toString() == EventType.delete.toString() - ? event.item.id - : event.item.name) + - ', of type: ' + - event.eventType.toString()); - }).onError((error) => print(error)); - - commentStream = Amplify.DataStore.observe(Comment.classType); - commentStream.listen((event) { - _commentStreamingData.add('Comment: ' + - (event.eventType.toString() == EventType.delete.toString() - ? event.item.id - : event.item.content) + - ', of type: ' + - event.eventType.toString()); - }).onError((error) => print(error)); setState(() { _isAmplifyConfigured = true; }); } - void listenToHub() { - setState(() { - hubSubscription = Amplify.Hub.listen(HubChannel.DataStore, (msg) { - final payload = msg.payload; - if (payload is NetworkStatusEvent) { - print('Network status active: ${payload.active}'); - return; - } - print(msg.type); - }); - _listeningToHub = true; - }); - } - - void stopListeningToHub() { - hubSubscription.cancel(); - setState(() { - _listeningToHub = false; - }); - } - - savePost(String title, int rating, Blog associatedBlog) async { - try { - Post post = Post( - title: title, - rating: rating, - created: TemporalDateTime.now(), - blog: associatedBlog); - await Amplify.DataStore.save(post); - } catch (e) { - print(e); - } - } - - saveBlog(String name) async { - try { - Blog blog = Blog( - name: name, - ); - await Amplify.DataStore.save(blog); - } catch (e) { - print(e); - } - } - - saveComment(String content, Post associatedPost) async { - try { - Comment comment = Comment(content: content, post: associatedPost); - await Amplify.DataStore.save(comment); - } catch (e) { - print(e); - } - } - - deletePost(String id) async { - try { - _selectedPostForNewComment = null; - await Amplify.DataStore.delete( - Post(id: id, title: "", rating: 0, created: TemporalDateTime.now())); - } catch (e) { - print(e); - } - } - - deleteBlog(String id) async { - try { - _selectedBlogForNewPost = null; - await Amplify.DataStore.delete(Blog(id: id, name: "")); - } catch (e) { - print(e); - } - } - - deleteComment(String id) async { - try { - await Amplify.DataStore.delete(Comment(id: id, content: "")); - } catch (e) { - print(e); - } - } - - void updateQueriesToView(String value) { - setState(() { - _queriesToView = value; - }); - } - - void updateSelectedBlogForNewPost(Blog value) { - setState(() { - _selectedBlogForNewPost = value; - }); - } - - void updateSelectedPostForNewComment(Post value) { - setState(() { - _selectedPostForNewComment = value; - }); - } - @override Widget build(BuildContext context) { - executeAfterBuild(); - return MaterialApp( - home: Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - 'Best DataStore App Ever', - textAlign: TextAlign.center, - ), - actions: [ - Padding( - padding: EdgeInsets.only(right: 20.0), - child: GestureDetector( - onTap: () async { - await Amplify.DataStore.clear(); - }, - child: Icon( - Icons.clear, - semanticLabel: "Clear", - size: 24.0, - ), - )), - ], - ), - body: Column( - children: [ - Padding(padding: EdgeInsets.all(10.0)), - - // Row for saving blog - addBlogWidget(_nameController, _isAmplifyConfigured, saveBlog), - - // Row for saving post - addPostWidget( - titleController: _titleController, - ratingController: _ratingController, - isAmplifyConfigured: _isAmplifyConfigured, - allBlogs: _blogs, - selectedBlog: _selectedBlogForNewPost, - saveFn: savePost, - updateSelectedBlogForNewPost: updateSelectedBlogForNewPost, - ), - - // Row for saving comment - addCommentWidget( - _contentController, - _isAmplifyConfigured, - _selectedPostForNewComment, - _posts, - _selectedPostForNewComment, - saveComment, - updateSelectedPostForNewComment), - - Padding(padding: EdgeInsets.all(10.0)), - - // Row for query buttons - displayQueryButtons( - _isAmplifyConfigured, _queriesToView, updateQueriesToView), - - // Start/Stop/Clear buttons - displaySyncButtons(), - - Padding(padding: EdgeInsets.all(5.0)), - Text("Listen to DataStore Hub"), - Switch( - value: _listeningToHub, - onChanged: (value) { - if (_listeningToHub) { - stopListeningToHub(); - } else { - listenToHub(); - } - }, - activeTrackColor: Colors.lightGreenAccent, - activeColor: Colors.green, - ), - - Padding(padding: EdgeInsets.all(5.0)), - - // Showing relevant queries - if (_queriesToView == "Post") - getWidgetToDisplayPost(_posts, deletePost, _blogs) - else if (_queriesToView == "Blog") - getWidgetToDisplayBlog(_blogs, deleteBlog) - else if (_queriesToView == "Comment") - getWidgetToDisplayComment(_comments, deleteComment, _posts), - - Text(_queriesToView + " Events", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black, - fontSize: 14)), - - Padding(padding: EdgeInsets.all(5.0)), - if (_queriesToView == "Post") - getWidgetToDisplayPostEvents( - _postScrollController, _postStreamingData, executeAfterBuild) - else if (_queriesToView == "Blog") - getWidgetToDisplayBlogEvents( - _blogScrollController, _blogStreamingData, executeAfterBuild) - else if (_queriesToView == "Comment") - getWidgetToDisplayCommentEvents(_commentScrollController, - _commentStreamingData, executeAfterBuild), - ], - // replace with any or all query results as needed + return Authenticator( + child: MaterialApp( + title: 'Best DataStore App Ever', + home: NavigatorScaffold( + isAmplifyConfigured: _isAmplifyConfigured, ), ), ); } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - WidgetsBinding.instance.addPostFrameCallback((_) => executeAfterBuild()); - } - - Future executeAfterBuild() async { - // this code will get executed after the build method - // because of the way async functions are scheduled - - Future.delayed(const Duration(milliseconds: 500), () { - if (_postScrollController.hasClients) - _postScrollController.animateTo( - _postScrollController.position.maxScrollExtent, - duration: Duration(milliseconds: 200), - curve: Curves.easeOut); - if (_blogScrollController.hasClients) - _blogScrollController.animateTo( - _blogScrollController.position.maxScrollExtent, - duration: Duration(milliseconds: 200), - curve: Curves.easeOut); - if (_commentScrollController.hasClients) - _commentScrollController.animateTo( - _commentScrollController.position.maxScrollExtent, - duration: Duration(milliseconds: 200), - curve: Curves.easeOut); - }); - } } diff --git a/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart b/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart index bdf0530756..5bd9f64912 100644 --- a/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart +++ b/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart @@ -19,11 +19,10 @@ // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously +import 'ModelProvider.dart'; import 'package:amplify_core/amplify_core.dart' as amplify_core; import 'package:collection/collection.dart'; -import 'ModelProvider.dart'; - /** This is an auto generated class representing the CustomTypeWithAppsyncScalarTypes type in your schema. */ class CustomTypeWithAppsyncScalarTypes { final String? _stringValue; diff --git a/packages/amplify_datastore/example/lib/models/ModelProvider.dart b/packages/amplify_datastore/example/lib/models/ModelProvider.dart index 6658c1d1ff..02ebebf58a 100644 --- a/packages/amplify_datastore/example/lib/models/ModelProvider.dart +++ b/packages/amplify_datastore/example/lib/models/ModelProvider.dart @@ -20,6 +20,7 @@ // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously import 'package:amplify_core/amplify_core.dart' as amplify_core; + import 'BelongsToChildExplicit.dart'; import 'BelongsToChildImplicit.dart'; import 'BelongsToParent.dart'; @@ -44,6 +45,7 @@ import 'CpkOneToOneBidirectionalChildImplicitID.dart'; import 'CpkOneToOneBidirectionalParentCD.dart'; import 'CpkOneToOneBidirectionalParentID.dart'; import 'CpkPostTags.dart'; +import 'CustomTypeWithAppsyncScalarTypes.dart'; import 'HasManyChildBiDirectionalExplicit.dart'; import 'HasManyChildBiDirectionalImplicit.dart'; import 'HasManyChildExplicit.dart'; @@ -56,14 +58,15 @@ import 'HasOneParent.dart'; import 'ModelWithAppsyncScalarTypes.dart'; import 'ModelWithCustomType.dart'; import 'ModelWithEnum.dart'; +import 'MultiAuthTodo.dart'; import 'MultiRelatedAttendee.dart'; import 'MultiRelatedMeeting.dart'; import 'MultiRelatedRegistration.dart'; import 'Post.dart'; import 'PostTags.dart'; -import 'Tag.dart'; -import 'CustomTypeWithAppsyncScalarTypes.dart'; +import 'PrivateTodo.dart'; import 'SimpleCustomType.dart'; +import 'Tag.dart'; export 'BelongsToChildExplicit.dart'; export 'BelongsToChildImplicit.dart'; @@ -103,17 +106,19 @@ export 'HasOneParent.dart'; export 'ModelWithAppsyncScalarTypes.dart'; export 'ModelWithCustomType.dart'; export 'ModelWithEnum.dart'; +export 'MultiAuthTodo.dart'; export 'MultiRelatedAttendee.dart'; export 'MultiRelatedMeeting.dart'; export 'MultiRelatedRegistration.dart'; export 'Post.dart'; export 'PostTags.dart'; +export 'PrivateTodo.dart'; export 'SimpleCustomType.dart'; export 'Tag.dart'; class ModelProvider implements amplify_core.ModelProviderInterface { @override - String version = "bc8b47d938d0b7deff50ac977653bed7"; + String version = "44ab790c924e12028850f0fe58e4adb4"; @override List modelSchemas = [ BelongsToChildExplicit.schema, @@ -152,11 +157,13 @@ class ModelProvider implements amplify_core.ModelProviderInterface { ModelWithAppsyncScalarTypes.schema, ModelWithCustomType.schema, ModelWithEnum.schema, + MultiAuthTodo.schema, MultiRelatedAttendee.schema, MultiRelatedMeeting.schema, MultiRelatedRegistration.schema, Post.schema, PostTags.schema, + PrivateTodo.schema, Tag.schema ]; @override @@ -242,6 +249,8 @@ class ModelProvider implements amplify_core.ModelProviderInterface { return ModelWithCustomType.classType; case "ModelWithEnum": return ModelWithEnum.classType; + case "MultiAuthTodo": + return MultiAuthTodo.classType; case "MultiRelatedAttendee": return MultiRelatedAttendee.classType; case "MultiRelatedMeeting": @@ -252,6 +261,8 @@ class ModelProvider implements amplify_core.ModelProviderInterface { return Post.classType; case "PostTags": return PostTags.classType; + case "PrivateTodo": + return PrivateTodo.classType; case "Tag": return Tag.classType; default: diff --git a/packages/amplify_datastore/example/lib/models/ModelProviderExampleApp.dart b/packages/amplify_datastore/example/lib/models/ModelProviderExampleApp.dart new file mode 100644 index 0000000000..9dceb2240b --- /dev/null +++ b/packages/amplify_datastore/example/lib/models/ModelProviderExampleApp.dart @@ -0,0 +1,88 @@ +/* +* 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, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +import 'Blog.dart'; +import 'Comment.dart'; +import 'MultiAuthTodo.dart'; +import 'Post.dart'; +import 'PostTags.dart'; +import 'PrivateTodo.dart'; +import 'Tag.dart'; + +export 'Blog.dart'; +export 'Comment.dart'; +export 'MultiAuthTodo.dart'; +export 'Post.dart'; +export 'PostTags.dart'; +export 'PrivateTodo.dart'; +export 'Tag.dart'; + +class ModelProvider implements amplify_core.ModelProviderInterface { + @override + String version = "44ab790c924e12028850f0fe58e4adb4"; + @override + List modelSchemas = [ + Blog.schema, + Comment.schema, + MultiAuthTodo.schema, + Post.schema, + PostTags.schema, + PrivateTodo.schema, + Tag.schema + ]; + @override + List customTypeSchemas = []; + static final ModelProvider _instance = ModelProvider(); + + static ModelProvider get instance => _instance; + + amplify_core.ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "Blog": + return Blog.classType; + case "Comment": + return Comment.classType; + case "MultiAuthTodo": + return MultiAuthTodo.classType; + case "MultiRelatedAttendee": + case "Post": + return Post.classType; + case "PostTags": + return PostTags.classType; + case "PrivateTodo": + return PrivateTodo.classType; + case "Tag": + return Tag.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} + +class ModelFieldValue { + const ModelFieldValue.value(this.value); + + final T value; +} diff --git a/packages/amplify_datastore/example/lib/models/ModelWithCustomType.dart b/packages/amplify_datastore/example/lib/models/ModelWithCustomType.dart index 5cf1c363f7..2c64cf54ac 100644 --- a/packages/amplify_datastore/example/lib/models/ModelWithCustomType.dart +++ b/packages/amplify_datastore/example/lib/models/ModelWithCustomType.dart @@ -19,11 +19,10 @@ // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously +import 'ModelProvider.dart'; import 'package:amplify_core/amplify_core.dart' as amplify_core; import 'package:collection/collection.dart'; -import 'ModelProvider.dart'; - /** This is an auto generated class representing the ModelWithCustomType type in your schema. */ class ModelWithCustomType extends amplify_core.Model { static const classType = const _ModelWithCustomTypeModelType(); diff --git a/packages/amplify_datastore/example/lib/models/MultiAuthTodo.dart b/packages/amplify_datastore/example/lib/models/MultiAuthTodo.dart new file mode 100644 index 0000000000..6471ece037 --- /dev/null +++ b/packages/amplify_datastore/example/lib/models/MultiAuthTodo.dart @@ -0,0 +1,242 @@ +/* +* 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, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'ModelProvider.dart'; +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +/** This is an auto generated class representing the MultiAuthTodo type in your schema. */ +class MultiAuthTodo extends amplify_core.Model { + static const classType = const _MultiAuthTodoModelType(); + final String id; + final String? _content; + final amplify_core.TemporalDateTime? _createdAt; + final amplify_core.TemporalDateTime? _updatedAt; + + @override + getInstanceType() => classType; + + @Deprecated( + '[getId] is being deprecated in favor of custom primary key feature. Use getter [modelIdentifier] to get model identifier.') + @override + String getId() => id; + + MultiAuthTodoModelIdentifier get modelIdentifier { + return MultiAuthTodoModelIdentifier(id: id); + } + + String? get content { + return _content; + } + + amplify_core.TemporalDateTime? get createdAt { + return _createdAt; + } + + amplify_core.TemporalDateTime? get updatedAt { + return _updatedAt; + } + + const MultiAuthTodo._internal( + {required this.id, content, createdAt, updatedAt}) + : _content = content, + _createdAt = createdAt, + _updatedAt = updatedAt; + + factory MultiAuthTodo({String? id, String? content}) { + return MultiAuthTodo._internal( + id: id == null ? amplify_core.UUID.getUUID() : id, content: content); + } + + bool equals(Object other) { + return this == other; + } + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is MultiAuthTodo && + id == other.id && + _content == other._content; + } + + @override + int get hashCode => toString().hashCode; + + @override + String toString() { + var buffer = new StringBuffer(); + + buffer.write("MultiAuthTodo {"); + buffer.write("id=" + "$id" + ", "); + buffer.write("content=" + "$_content" + ", "); + buffer.write("createdAt=" + + (_createdAt != null ? _createdAt.format() : "null") + + ", "); + buffer.write( + "updatedAt=" + (_updatedAt != null ? _updatedAt.format() : "null")); + buffer.write("}"); + + return buffer.toString(); + } + + MultiAuthTodo copyWith({String? content}) { + return MultiAuthTodo._internal(id: id, content: content ?? this.content); + } + + MultiAuthTodo copyWithModelFieldValues({ModelFieldValue? content}) { + return MultiAuthTodo._internal( + id: id, content: content == null ? this.content : content.value); + } + + MultiAuthTodo.fromJson(Map json) + : id = json['id'], + _content = json['content'], + _createdAt = json['createdAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['createdAt']) + : null, + _updatedAt = json['updatedAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['updatedAt']) + : null; + + Map toJson() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt?.format(), + 'updatedAt': _updatedAt?.format() + }; + + Map toMap() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt, + 'updatedAt': _updatedAt + }; + + static final amplify_core.QueryModelIdentifier + MODEL_IDENTIFIER = + amplify_core.QueryModelIdentifier(); + static final ID = amplify_core.QueryField(fieldName: "id"); + static final CONTENT = amplify_core.QueryField(fieldName: "content"); + static var schema = amplify_core.Model.defineSchema( + define: (amplify_core.ModelSchemaDefinition modelSchemaDefinition) { + modelSchemaDefinition.name = "MultiAuthTodo"; + modelSchemaDefinition.pluralName = "MultiAuthTodos"; + + modelSchemaDefinition.authRules = [ + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PUBLIC, + provider: amplify_core.AuthRuleProvider.APIKEY, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PRIVATE, + provider: amplify_core.AuthRuleProvider.IAM, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.OWNER, + ownerField: "owner", + identityClaim: "cognito:username", + provider: amplify_core.AuthRuleProvider.USERPOOLS, + operations: const [ + amplify_core.ModelOperation.CREATE, + amplify_core.ModelOperation.READ, + amplify_core.ModelOperation.UPDATE, + amplify_core.ModelOperation.DELETE + ]) + ]; + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.id()); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: MultiAuthTodo.CONTENT, + isRequired: false, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'createdAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'updatedAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + }); +} + +class _MultiAuthTodoModelType extends amplify_core.ModelType { + const _MultiAuthTodoModelType(); + + @override + MultiAuthTodo fromJson(Map jsonData) { + return MultiAuthTodo.fromJson(jsonData); + } + + @override + String modelName() { + return 'MultiAuthTodo'; + } +} + +/** + * This is an auto generated class representing the model identifier + * of [MultiAuthTodo] in your schema. + */ +class MultiAuthTodoModelIdentifier + implements amplify_core.ModelIdentifier { + final String id; + + /** Create an instance of MultiAuthTodoModelIdentifier using [id] the primary key. */ + const MultiAuthTodoModelIdentifier({required this.id}); + + @override + Map serializeAsMap() => ({'id': id}); + + @override + List> serializeAsList() => serializeAsMap() + .entries + .map((entry) => ({entry.key: entry.value})) + .toList(); + + @override + String serializeAsString() => serializeAsMap().values.join('#'); + + @override + String toString() => 'MultiAuthTodoModelIdentifier(id: $id)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is MultiAuthTodoModelIdentifier && id == other.id; + } + + @override + int get hashCode => id.hashCode; +} diff --git a/packages/amplify_datastore/example/lib/models/MultiTodo.dart b/packages/amplify_datastore/example/lib/models/MultiTodo.dart new file mode 100644 index 0000000000..244a8836ec --- /dev/null +++ b/packages/amplify_datastore/example/lib/models/MultiTodo.dart @@ -0,0 +1,238 @@ +/* +* 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, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'ModelProvider.dart'; +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +/** This is an auto generated class representing the MultiTodo type in your schema. */ +class MultiTodo extends amplify_core.Model { + static const classType = const _MultiTodoModelType(); + final String id; + final String? _content; + final amplify_core.TemporalDateTime? _createdAt; + final amplify_core.TemporalDateTime? _updatedAt; + + @override + getInstanceType() => classType; + + @Deprecated( + '[getId] is being deprecated in favor of custom primary key feature. Use getter [modelIdentifier] to get model identifier.') + @override + String getId() => id; + + MultiTodoModelIdentifier get modelIdentifier { + return MultiTodoModelIdentifier(id: id); + } + + String? get content { + return _content; + } + + amplify_core.TemporalDateTime? get createdAt { + return _createdAt; + } + + amplify_core.TemporalDateTime? get updatedAt { + return _updatedAt; + } + + const MultiTodo._internal({required this.id, content, createdAt, updatedAt}) + : _content = content, + _createdAt = createdAt, + _updatedAt = updatedAt; + + factory MultiTodo({String? id, String? content}) { + return MultiTodo._internal( + id: id == null ? amplify_core.UUID.getUUID() : id, content: content); + } + + bool equals(Object other) { + return this == other; + } + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is MultiTodo && id == other.id && _content == other._content; + } + + @override + int get hashCode => toString().hashCode; + + @override + String toString() { + var buffer = new StringBuffer(); + + buffer.write("MultiTodo {"); + buffer.write("id=" + "$id" + ", "); + buffer.write("content=" + "$_content" + ", "); + buffer.write("createdAt=" + + (_createdAt != null ? _createdAt.format() : "null") + + ", "); + buffer.write( + "updatedAt=" + (_updatedAt != null ? _updatedAt.format() : "null")); + buffer.write("}"); + + return buffer.toString(); + } + + MultiTodo copyWith({String? content}) { + return MultiTodo._internal(id: id, content: content ?? this.content); + } + + MultiTodo copyWithModelFieldValues({ModelFieldValue? content}) { + return MultiTodo._internal( + id: id, content: content == null ? this.content : content.value); + } + + MultiTodo.fromJson(Map json) + : id = json['id'], + _content = json['content'], + _createdAt = json['createdAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['createdAt']) + : null, + _updatedAt = json['updatedAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['updatedAt']) + : null; + + Map toJson() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt?.format(), + 'updatedAt': _updatedAt?.format() + }; + + Map toMap() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt, + 'updatedAt': _updatedAt + }; + + static final amplify_core.QueryModelIdentifier + MODEL_IDENTIFIER = + amplify_core.QueryModelIdentifier(); + static final ID = amplify_core.QueryField(fieldName: "id"); + static final CONTENT = amplify_core.QueryField(fieldName: "content"); + static var schema = amplify_core.Model.defineSchema( + define: (amplify_core.ModelSchemaDefinition modelSchemaDefinition) { + modelSchemaDefinition.name = "MultiTodo"; + modelSchemaDefinition.pluralName = "MultiTodos"; + + modelSchemaDefinition.authRules = [ + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.OWNER, + ownerField: "owner", + identityClaim: "cognito:username", + provider: amplify_core.AuthRuleProvider.USERPOOLS, + operations: const [ + amplify_core.ModelOperation.READ, + amplify_core.ModelOperation.CREATE, + amplify_core.ModelOperation.UPDATE, + amplify_core.ModelOperation.DELETE + ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PRIVATE, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PUBLIC, + provider: amplify_core.AuthRuleProvider.APIKEY, + operations: const [amplify_core.ModelOperation.READ]) + ]; + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.id()); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: MultiTodo.CONTENT, + isRequired: false, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'createdAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'updatedAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + }); +} + +class _MultiTodoModelType extends amplify_core.ModelType { + const _MultiTodoModelType(); + + @override + MultiTodo fromJson(Map jsonData) { + return MultiTodo.fromJson(jsonData); + } + + @override + String modelName() { + return 'MultiTodo'; + } +} + +/** + * This is an auto generated class representing the model identifier + * of [MultiTodo] in your schema. + */ +class MultiTodoModelIdentifier + implements amplify_core.ModelIdentifier { + final String id; + + /** Create an instance of MultiTodoModelIdentifier using [id] the primary key. */ + const MultiTodoModelIdentifier({required this.id}); + + @override + Map serializeAsMap() => ({'id': id}); + + @override + List> serializeAsList() => serializeAsMap() + .entries + .map((entry) => ({entry.key: entry.value})) + .toList(); + + @override + String serializeAsString() => serializeAsMap().values.join('#'); + + @override + String toString() => 'MultiTodoModelIdentifier(id: $id)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is MultiTodoModelIdentifier && id == other.id; + } + + @override + int get hashCode => id.hashCode; +} diff --git a/packages/amplify_datastore/example/lib/models/PrivateTodo.dart b/packages/amplify_datastore/example/lib/models/PrivateTodo.dart new file mode 100644 index 0000000000..3a14ae7b6d --- /dev/null +++ b/packages/amplify_datastore/example/lib/models/PrivateTodo.dart @@ -0,0 +1,228 @@ +/* +* 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, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'ModelProvider.dart'; +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +/** This is an auto generated class representing the PrivateTodo type in your schema. */ +class PrivateTodo extends amplify_core.Model { + static const classType = const _PrivateTodoModelType(); + final String id; + final String? _content; + final amplify_core.TemporalDateTime? _createdAt; + final amplify_core.TemporalDateTime? _updatedAt; + + @override + getInstanceType() => classType; + + @Deprecated( + '[getId] is being deprecated in favor of custom primary key feature. Use getter [modelIdentifier] to get model identifier.') + @override + String getId() => id; + + PrivateTodoModelIdentifier get modelIdentifier { + return PrivateTodoModelIdentifier(id: id); + } + + String? get content { + return _content; + } + + amplify_core.TemporalDateTime? get createdAt { + return _createdAt; + } + + amplify_core.TemporalDateTime? get updatedAt { + return _updatedAt; + } + + const PrivateTodo._internal({required this.id, content, createdAt, updatedAt}) + : _content = content, + _createdAt = createdAt, + _updatedAt = updatedAt; + + factory PrivateTodo({String? id, String? content}) { + return PrivateTodo._internal( + id: id == null ? amplify_core.UUID.getUUID() : id, content: content); + } + + bool equals(Object other) { + return this == other; + } + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is PrivateTodo && id == other.id && _content == other._content; + } + + @override + int get hashCode => toString().hashCode; + + @override + String toString() { + var buffer = new StringBuffer(); + + buffer.write("PrivateTodo {"); + buffer.write("id=" + "$id" + ", "); + buffer.write("content=" + "$_content" + ", "); + buffer.write("createdAt=" + + (_createdAt != null ? _createdAt.format() : "null") + + ", "); + buffer.write( + "updatedAt=" + (_updatedAt != null ? _updatedAt.format() : "null")); + buffer.write("}"); + + return buffer.toString(); + } + + PrivateTodo copyWith({String? content}) { + return PrivateTodo._internal(id: id, content: content ?? this.content); + } + + PrivateTodo copyWithModelFieldValues({ModelFieldValue? content}) { + return PrivateTodo._internal( + id: id, content: content == null ? this.content : content.value); + } + + PrivateTodo.fromJson(Map json) + : id = json['id'], + _content = json['content'], + _createdAt = json['createdAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['createdAt']) + : null, + _updatedAt = json['updatedAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['updatedAt']) + : null; + + Map toJson() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt?.format(), + 'updatedAt': _updatedAt?.format() + }; + + Map toMap() => { + 'id': id, + 'content': _content, + 'createdAt': _createdAt, + 'updatedAt': _updatedAt + }; + + static final amplify_core.QueryModelIdentifier + MODEL_IDENTIFIER = + amplify_core.QueryModelIdentifier(); + static final ID = amplify_core.QueryField(fieldName: "id"); + static final CONTENT = amplify_core.QueryField(fieldName: "content"); + static var schema = amplify_core.Model.defineSchema( + define: (amplify_core.ModelSchemaDefinition modelSchemaDefinition) { + modelSchemaDefinition.name = "PrivateTodo"; + modelSchemaDefinition.pluralName = "PrivateTodos"; + + modelSchemaDefinition.authRules = [ + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PRIVATE, + operations: const [ + amplify_core.ModelOperation.CREATE, + amplify_core.ModelOperation.UPDATE, + amplify_core.ModelOperation.DELETE, + amplify_core.ModelOperation.READ + ]) + ]; + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.id()); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: PrivateTodo.CONTENT, + isRequired: false, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'createdAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'updatedAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + }); +} + +class _PrivateTodoModelType extends amplify_core.ModelType { + const _PrivateTodoModelType(); + + @override + PrivateTodo fromJson(Map jsonData) { + return PrivateTodo.fromJson(jsonData); + } + + @override + String modelName() { + return 'PrivateTodo'; + } +} + +/** + * This is an auto generated class representing the model identifier + * of [PrivateTodo] in your schema. + */ +class PrivateTodoModelIdentifier + implements amplify_core.ModelIdentifier { + final String id; + + /** Create an instance of PrivateTodoModelIdentifier using [id] the primary key. */ + const PrivateTodoModelIdentifier({required this.id}); + + @override + Map serializeAsMap() => ({'id': id}); + + @override + List> serializeAsList() => serializeAsMap() + .entries + .map((entry) => ({entry.key: entry.value})) + .toList(); + + @override + String serializeAsString() => serializeAsMap().values.join('#'); + + @override + String toString() => 'PrivateTodoModelIdentifier(id: $id)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is PrivateTodoModelIdentifier && id == other.id; + } + + @override + int get hashCode => id.hashCode; +} diff --git a/packages/amplify_datastore/example/lib/widgets/auth_view.dart b/packages/amplify_datastore/example/lib/widgets/auth_view.dart new file mode 100644 index 0000000000..34f6db43ba --- /dev/null +++ b/packages/amplify_datastore/example/lib/widgets/auth_view.dart @@ -0,0 +1,161 @@ +import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_datastore_example/main.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../models/ModelProvider.dart'; + +class AuthView extends StatefulWidget { + const AuthView({super.key, this.isAmplifyConfigured = false}); + final bool isAmplifyConfigured; + + @override + State createState() => _AuthViewState(); +} + +class _AuthViewState extends State { + List _privateTodos = []; + List _multiAuthTodos = []; + List _streamingData = []; + late Stream> _privateTodoStream; + late Stream> _multiAuthTodoStream; + ScrollController _privateTodosScrollController = + ScrollController(initialScrollOffset: 50.0); + + @override + void initState() { + super.initState(); + initPageState(); + } + + @override + void setState(fn) { + if (mounted) { + super.setState(fn); + } + } + + Future initPageState() async { + Amplify.DataStore.observeQuery( + PrivateTodo.classType, + ).listen((QuerySnapshot snapshot) { + setState(() { + _privateTodos = snapshot.items; + }); + }); + Amplify.DataStore.observeQuery( + MultiAuthTodo.classType, + ).listen((QuerySnapshot snapshot) { + setState(() { + _multiAuthTodos = snapshot.items; + }); + }); + + _privateTodoStream = Amplify.DataStore.observe(PrivateTodo.classType); + _privateTodoStream.listen((event) { + print("PrivateTodo: ${event.item.content}, of type: ${event.eventType}"); + _streamingData.add('PrivateTodo: ' + + (event.eventType.toString() == EventType.delete.toString() + ? event.item.id + : event.item.content ?? "") + + ', of type: ' + + event.eventType.toString()); + }).onError((error) => print(error)); + + _multiAuthTodoStream = Amplify.DataStore.observe(MultiAuthTodo.classType); + _multiAuthTodoStream.listen((event) { + print( + "MultiAuthTodo: ${event.item.content}, of type: ${event.eventType}"); + _streamingData.add('MultiAuthTodo: ' + + (event.eventType.toString() == EventType.delete.toString() + ? event.item.id + : event.item.content ?? "") + + ', of type: ' + + event.eventType.toString()); + }).onError((error) => print(error)); + } + + Future _restartDataStore() async { + await Amplify.DataStore.clear(); + await Amplify.DataStore.start(); + } + + Future _createPrivateTodo() async { + final item = PrivateTodo(content: "Test Private Todo - ${uuid()}"); + await Amplify.DataStore.save(item); + } + + deletePrivateTodo(String id) async { + try { + await Amplify.DataStore.delete(PrivateTodo(id: id)); + } catch (e) { + print(e); + } + } + + Future _createMultiTodo() async { + final item = MultiAuthTodo(content: "Test multi Todo - ${uuid()}"); + await Amplify.DataStore.save(item); + } + + @override + Widget build(BuildContext context) { + return AuthenticatedView( + child: Column( + children: [ + // Start / Stop / Clear buttons + displaySyncButtons(), + + ElevatedButton( + onPressed: () async { + await _restartDataStore(); + }, + child: Text('Restart DataStore'), + ), + ElevatedButton( + onPressed: () async { + await _createPrivateTodo(); + }, + child: Text('Create Private Todo'), + ), + ElevatedButton( + onPressed: () async { + await _createMultiTodo(); + }, + child: Text('Create Multi Todo'), + ), + + getWidgetToDisplayPrivateTodo( + [..._privateTodos, ..._multiAuthTodos], deletePrivateTodo), + Text("Events", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 14)), + Padding(padding: EdgeInsets.all(5.0)), + getWidgetToDisplayAuthTodoEvents( + _privateTodosScrollController, _streamingData, executeAfterBuild) + ], + ), + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) => executeAfterBuild()); + } + + Future executeAfterBuild() async { + // this code will get executed after the build method + // because of the way async functions are scheduled + + Future.delayed(const Duration(milliseconds: 500), () { + if (_privateTodosScrollController.hasClients) + _privateTodosScrollController.animateTo( + _privateTodosScrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 200), + curve: Curves.easeOut); + }); + } +} diff --git a/packages/amplify_datastore/example/lib/event_display_widgets.dart b/packages/amplify_datastore/example/lib/widgets/event_display_widgets.dart similarity index 76% rename from packages/amplify_datastore/example/lib/event_display_widgets.dart rename to packages/amplify_datastore/example/lib/widgets/event_display_widgets.dart index 9c7932894b..353bfbb0e6 100644 --- a/packages/amplify_datastore/example/lib/event_display_widgets.dart +++ b/packages/amplify_datastore/example/lib/widgets/event_display_widgets.dart @@ -56,3 +56,21 @@ Widget getWidgetToDisplayCommentEvents(ScrollController scrollController, ); })); } + +Widget getWidgetToDisplayAuthTodoEvents(ScrollController scrollController, + List streamingData, Future Function() executeAfterBuild) { + return SizedBox( + height: 100, + child: ListView.builder( + controller: scrollController, + shrinkWrap: true, + reverse: true, + itemCount: streamingData.length, + itemBuilder: (BuildContext context, int index) { + executeAfterBuild(); + return Container( + margin: EdgeInsets.fromLTRB(30, 0, 0, 0), + child: Text(streamingData[index]), + ); + })); +} diff --git a/packages/amplify_datastore/example/lib/widgets/navigator_scaffold.dart b/packages/amplify_datastore/example/lib/widgets/navigator_scaffold.dart new file mode 100644 index 0000000000..4ea47b9c58 --- /dev/null +++ b/packages/amplify_datastore/example/lib/widgets/navigator_scaffold.dart @@ -0,0 +1,65 @@ +import 'package:amplify_datastore_example/widgets/auth_view.dart'; +import 'package:amplify_datastore_example/widgets/public_view.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +class NavigatorScaffold extends StatefulWidget { + const NavigatorScaffold({ + super.key, + this.isAmplifyConfigured = false, + }); + final bool isAmplifyConfigured; + + @override + State createState() => _NavigatorScaffoldState(); +} + +class _NavigatorScaffoldState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Best DataStore App Ever'), + bottom: TabBar( + controller: _tabController, + tabs: [ + Tab(icon: Icon(Icons.public), text: "Public"), + Tab(icon: Icon(Icons.verified_user), text: "Protected"), + ], + ), + ), + floatingActionButton: ElevatedButton( + onPressed: () async { + await Amplify.Auth.signOut(); + }, + child: Text('Sign Out'), + ), + body: widget.isAmplifyConfigured + ? TabBarView( + controller: _tabController, + children: [ + PublicView(isAmplifyConfigured: widget.isAmplifyConfigured), + AuthView(isAmplifyConfigured: widget.isAmplifyConfigured), + ], + ) + : Center( + child: CircularProgressIndicator.adaptive(), + ), + ); + } +} diff --git a/packages/amplify_datastore/example/lib/widgets/public_view.dart b/packages/amplify_datastore/example/lib/widgets/public_view.dart new file mode 100644 index 0000000000..0f95d06678 --- /dev/null +++ b/packages/amplify_datastore/example/lib/widgets/public_view.dart @@ -0,0 +1,353 @@ +import 'dart:async'; + +import 'package:amplify_datastore_example/main.dart'; +import 'package:amplify_datastore_example/models/ModelProvider.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +class PublicView extends StatefulWidget { + const PublicView({super.key, this.isAmplifyConfigured = false}); + final bool isAmplifyConfigured; + + @override + State createState() => _PublicViewState(); +} + +class _PublicViewState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + List _posts = []; + List _comments = []; + List _blogs = []; + late Stream> postStream; + late Stream> blogStream; + late Stream> commentStream; + List _postStreamingData = []; + List _blogStreamingData = []; + List _commentStreamingData = []; + String _queriesToView = "Post"; //default view + Blog? _selectedBlogForNewPost; + Post? _selectedPostForNewComment; + + late StreamSubscription hubSubscription; + bool _listeningToHub = true; + + final _titleController = TextEditingController(); + final _ratingController = TextEditingController(); + final _nameController = TextEditingController(); + final _contentController = TextEditingController(); + ScrollController _postScrollController = + ScrollController(initialScrollOffset: 50.0); + ScrollController _blogScrollController = + ScrollController(initialScrollOffset: 50.0); + ScrollController _commentScrollController = + ScrollController(initialScrollOffset: 50.0); + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + initPageState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + void setState(fn) { + if (mounted) { + super.setState(fn); + } + } + + Future initPageState() async { + listenToHub(); + + Amplify.DataStore.observeQuery( + Blog.classType, + ).listen((QuerySnapshot snapshot) { + var count = snapshot.items.length; + var now = DateTime.now().toIso8601String(); + bool status = snapshot.isSynced; + print( + '[Observe Query] Blog snapshot received with $count models, status: $status at $now'); + setState(() { + _blogs = snapshot.items; + }); + }); + + Amplify.DataStore.observeQuery( + Post.classType, + ).listen((QuerySnapshot snapshot) { + setState(() { + _posts = snapshot.items; + }); + }); + + Amplify.DataStore.observeQuery( + Comment.classType, + ).listen((QuerySnapshot snapshot) { + setState(() { + _comments = snapshot.items; + }); + }); + + // setup streams + postStream = Amplify.DataStore.observe(Post.classType); + postStream.listen((event) { + _postStreamingData.add('Post: ' + + (event.eventType.toString() == EventType.delete.toString() + ? event.item.id + : event.item.title) + + ', of type: ' + + event.eventType.toString()); + }).onError((error) => print(error)); + + blogStream = Amplify.DataStore.observe(Blog.classType); + blogStream.listen((event) { + _blogStreamingData.add('Blog: ' + + (event.eventType.toString() == EventType.delete.toString() + ? event.item.id + : event.item.name) + + ', of type: ' + + event.eventType.toString()); + }).onError((error) => print(error)); + + commentStream = Amplify.DataStore.observe(Comment.classType); + commentStream.listen((event) { + _commentStreamingData.add('Comment: ' + + (event.eventType.toString() == EventType.delete.toString() + ? event.item.id + : event.item.content) + + ', of type: ' + + event.eventType.toString()); + }).onError((error) => print(error)); + } + + void listenToHub() { + setState(() { + hubSubscription = Amplify.Hub.listen(HubChannel.DataStore, (msg) { + print(msg.type); + }); + _listeningToHub = true; + }); + } + + void stopListeningToHub() { + hubSubscription.cancel(); + setState(() { + _listeningToHub = false; + }); + } + + savePost(String title, int rating, Blog associatedBlog) async { + try { + Post post = Post( + title: title, + rating: rating, + created: TemporalDateTime.now(), + blog: associatedBlog); + await Amplify.DataStore.save(post); + } catch (e) { + print(e); + } + } + + saveBlog(String name) async { + try { + Blog blog = Blog( + name: name, + ); + await Amplify.DataStore.save(blog); + } catch (e) { + print(e); + } + } + + saveComment(String content, Post associatedPost) async { + try { + Comment comment = Comment(content: content, post: associatedPost); + await Amplify.DataStore.save(comment); + } catch (e) { + print(e); + } + } + + deletePost(String id) async { + try { + _selectedPostForNewComment = null; + await Amplify.DataStore.delete( + Post(id: id, title: "", rating: 0, created: TemporalDateTime.now())); + } catch (e) { + print(e); + } + } + + deleteBlog(String id) async { + try { + _selectedBlogForNewPost = null; + await Amplify.DataStore.delete(Blog(id: id, name: "")); + } catch (e) { + print(e); + } + } + + deleteComment(String id) async { + try { + await Amplify.DataStore.delete(Comment(id: id, content: "")); + } catch (e) { + print(e); + } + } + + void updateQueriesToView(String value) { + setState(() { + _queriesToView = value; + }); + } + + void updateSelectedBlogForNewPost(Blog value) { + setState(() { + _selectedBlogForNewPost = value; + }); + } + + void updateSelectedPostForNewComment(Post value) { + setState(() { + _selectedPostForNewComment = value; + }); + } + + @override + Widget build(BuildContext context) { + // actions: [ + // Padding( + // padding: EdgeInsets.only(right: 20.0), + // child: GestureDetector( + // onTap: () async { + // await Amplify.DataStore.clear(); + // }, + // child: Icon( + // Icons.clear, + // semanticLabel: "Clear", + // size: 24.0, + // ), + // )), + // ], + // ), + executeAfterBuild(); + return Column( + children: [ + Padding(padding: EdgeInsets.all(10.0)), + + // Row for saving blog + addBlogWidget(_nameController, widget.isAmplifyConfigured, saveBlog), + + // Row for saving post + addPostWidget( + titleController: _titleController, + ratingController: _ratingController, + isAmplifyConfigured: widget.isAmplifyConfigured, + allBlogs: _blogs, + selectedBlog: _selectedBlogForNewPost, + saveFn: savePost, + updateSelectedBlogForNewPost: updateSelectedBlogForNewPost, + ), + + // Row for saving comment + addCommentWidget( + _contentController, + widget.isAmplifyConfigured, + _selectedPostForNewComment, + _posts, + _selectedPostForNewComment, + saveComment, + updateSelectedPostForNewComment), + + Padding(padding: EdgeInsets.all(10.0)), + + // Start / Stop / Clear buttons + displaySyncButtons(), + + // Row for query buttons + displayQueryButtons( + widget.isAmplifyConfigured, _queriesToView, updateQueriesToView), + + Padding(padding: EdgeInsets.all(5.0)), + Text("Listen to DataStore Hub"), + Switch( + value: _listeningToHub, + onChanged: (value) { + if (_listeningToHub) { + stopListeningToHub(); + } else { + listenToHub(); + } + }, + activeTrackColor: Colors.lightGreenAccent, + activeColor: Colors.green, + ), + + Padding(padding: EdgeInsets.all(5.0)), + + // Showing relevant queries + if (_queriesToView == "Post") + getWidgetToDisplayPost(_posts, deletePost, _blogs) + else if (_queriesToView == "Blog") + getWidgetToDisplayBlog(_blogs, deleteBlog) + else if (_queriesToView == "Comment") + getWidgetToDisplayComment(_comments, deleteComment, _posts), + + Text(_queriesToView + " Events", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 14)), + + Padding(padding: EdgeInsets.all(5.0)), + if (_queriesToView == "Post") + getWidgetToDisplayPostEvents( + _postScrollController, _postStreamingData, executeAfterBuild) + else if (_queriesToView == "Blog") + getWidgetToDisplayBlogEvents( + _blogScrollController, _blogStreamingData, executeAfterBuild) + else if (_queriesToView == "Comment") + getWidgetToDisplayCommentEvents(_commentScrollController, + _commentStreamingData, executeAfterBuild), + ], + // replace with any or all query results as needed + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) => executeAfterBuild()); + } + + Future executeAfterBuild() async { + // this code will get executed after the build method + // because of the way async functions are scheduled + + Future.delayed(const Duration(milliseconds: 500), () { + if (_postScrollController.hasClients) + _postScrollController.animateTo( + _postScrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 200), + curve: Curves.easeOut); + if (_blogScrollController.hasClients) + _blogScrollController.animateTo( + _blogScrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 200), + curve: Curves.easeOut); + if (_commentScrollController.hasClients) + _commentScrollController.animateTo( + _commentScrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 200), + curve: Curves.easeOut); + }); + } +} diff --git a/packages/amplify_datastore/example/lib/queries_display_widgets.dart b/packages/amplify_datastore/example/lib/widgets/queries_display_widgets.dart similarity index 87% rename from packages/amplify_datastore/example/lib/queries_display_widgets.dart rename to packages/amplify_datastore/example/lib/widgets/queries_display_widgets.dart index 792a39857a..af699e3115 100644 --- a/packages/amplify_datastore/example/lib/queries_display_widgets.dart +++ b/packages/amplify_datastore/example/lib/widgets/queries_display_widgets.dart @@ -207,3 +207,32 @@ Widget displaySyncButtons() { ), ]); } + +Widget getWidgetToDisplayPrivateTodo( + List _todoToView, Function deleteTodo) { + return Expanded( + child: ListView.builder( + itemCount: _todoToView.length, + padding: const EdgeInsets.all(16.0), + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemBuilder: (context, i) { + return ListTile( + title: Text( + "${_todoToView[i].toMap()['content']}", + style: TextStyle(fontSize: 14.0), + ), + trailing: IconButton( + onPressed: () { + print("Deleting ${_todoToView[i].toMap()['content']}}"); + deleteTodo(_todoToView[i].toMap()['id']); + }, + icon: Icon( + Icons.delete_forever, + color: Colors.red, + ), + ), + ); + }), + ); +} diff --git a/packages/amplify_datastore/example/lib/save_model_widgets.dart b/packages/amplify_datastore/example/lib/widgets/save_model_widgets.dart similarity index 100% rename from packages/amplify_datastore/example/lib/save_model_widgets.dart rename to packages/amplify_datastore/example/lib/widgets/save_model_widgets.dart diff --git a/packages/amplify_datastore/example/pubspec.yaml b/packages/amplify_datastore/example/pubspec.yaml index 20f09f3c41..bbfd9f218b 100644 --- a/packages/amplify_datastore/example/pubspec.yaml +++ b/packages/amplify_datastore/example/pubspec.yaml @@ -12,9 +12,11 @@ environment: dependencies: flutter: sdk: flutter - amplify_api: ">=1.0.0-next.8 <1.0.0-next.9" - amplify_datastore: ">=1.0.0-next.8 <1.0.0-next.9" - amplify_flutter: ">=1.0.0-next.8 <1.0.0-next.9" + amplify_api: ^2.0.0 + amplify_datastore: ^2.0.0 + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 flutter_driver: sdk: flutter integration_test: diff --git a/packages/amplify_datastore/example/tool/add_api_request.json b/packages/amplify_datastore/example/tool/add_api_request.json deleted file mode 100644 index bac4c4e153..0000000000 --- a/packages/amplify_datastore/example/tool/add_api_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": 1, - "serviceConfiguration": { - "serviceName": "AppSync", - "apiName": "dataStoreIntegrationTestGraphQL", - "transformSchema": "", - "defaultAuthType": { - "mode": "API_KEY", - "expirationTime": 365 - }, - "conflictResolution": { - "defaultResolutionStrategy": { - "type": "AUTOMERGE" - } - } - } -} diff --git a/packages/amplify_datastore/example/tool/deply_backend.md b/packages/amplify_datastore/example/tool/deply_backend.md new file mode 100644 index 0000000000..851e6d3507 --- /dev/null +++ b/packages/amplify_datastore/example/tool/deply_backend.md @@ -0,0 +1,79 @@ +## DataStore Integration Tests + +The following steps demonstrate how to set up DataStore with Auth. +Auth category is also required for models with auth rules and requesting with IAM credentials to allow unauthenticated and authenticated access. + +### Pre-req + +Install `jq` - https://jqlang.github.io/jq/download/ + +`$ brew install jq` + +### Set-up + +1. `sh tool/provision_integration_test_resources.sh` + +2. `amplify add api` + +```perl +? Select from one of the below mentioned services: `GraphQL` +? Here is the GraphQL API that we will create. Select a setting to edit or continue Conflict detection (required for DataStore: `Disabled` +? Enable conflict detection? `Yes` +? Select the default resolution strategy Auto Merge `Auto Merge` +? Here is the GraphQL API that we will create. Select a setting to edit or continue `Continue` +? Choose a schema template: `Blank Schema` + +⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules + +✅ GraphQL schema compiled successfully. + +Edit your schema at /Users/quaelija/Code/playground/dsbackend/amplify/backend/api/amplifydatastoreinte/schema.graphql or place .graphql files in a directory at /Users/quaelija/Code/playground/dsbackend/amplify/backend/api/amplifydatastoreinte/schema +✔ Do you want to edit the schema now? (Y/n) · yes + +# Now replace with the contents of `/tool/schema.graphql` +``` + +3. `amplify add auth` + +```perl +amplify add auth + + Do you want to use the default authentication and security configuration? `M +anual configuration` + Select the authentication/authorization services that you want to use: `User + Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage + features for images or other content, Analytics, and more)` + Provide a friendly name for your resource that will be used to label this c +ategory in the project: `BACKEND_NAME` + Enter a name for your identity pool. `IDENTITY_POOL_NAME` + Allow unauthenticated logins? (Provides scoped down permissions that you ca +n control via AWS IAM) `Yes` + Do you want to enable 3rd party authentication providers in your identity p +ool? `No` + Provide a name for your user pool: `USER_POOL_NAME` + Warning: you will not be able to edit these selections. + How do you want users to be able to sign in? `Email` + Do you want to add User Pool Groups? `No` + Do you want to add an admin queries API? `No` + Multifactor authentication (MFA) user login options: `OFF` + Email based user registration/forgot password: `Enabled (Requires per-user e +mail entry at registration)` + Specify an email verification subject: `Your verification code` + Specify an email verification message: `Your verification code is {####}` + Do you want to override the default password policy for this User Pool? No + Warning: you will not be able to edit these selections. + What attributes are required for signing up? `Email` + Specify the apps refresh token expiration period (in days): `365` + Do you want to specify the user attributes this app can read and write? `No` + Do you want to enable any of the following capabilities? `NONE` + Do you want to use an OAuth flow? `No` +? Do you want to configure Lambda Triggers for Cognito? `Yes` +? Which triggers do you want to enable for Cognito `Pre Sign-up` +? What functionality do you want to use for Pre Sign-up `Create your own module` + +# Now copy the contents of `/tool/pre_sign_up.js` to the module +``` + +4. `amplify push` + +5. You can now run all of the integration tests. diff --git a/packages/amplify_datastore/example/tool/pre_sign_up.js b/packages/amplify_datastore/example/tool/pre_sign_up.js new file mode 100644 index 0000000000..f5e6eb2ebf --- /dev/null +++ b/packages/amplify_datastore/example/tool/pre_sign_up.js @@ -0,0 +1,25 @@ +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +exports.handler = async (event, context) => { + console.log(`Got event: ${JSON.stringify(event, null, 2)}`); + + if (event.triggerSource !== "PreSignUp_SignUp") { + console.warn(`Not handling request of type: ${event.triggerSource}`); + return event; + } + + event.response.autoConfirmUser = true; + + // Set the email as verified if it is in the request + if (event.request.userAttributes.hasOwnProperty("email")) { + event.response.autoVerifyEmail = true; + } + + // Set the phone number as verified if it is in the request + if (event.request.userAttributes.hasOwnProperty("phone_number")) { + event.response.autoVerifyPhone = true; + } + + return event; +}; diff --git a/packages/amplify_datastore/example/tool/provision_integration_test_resources.sh b/packages/amplify_datastore/example/tool/provision_integration_test_resources.sh index 6cf1e98fc6..2312b035ec 100755 --- a/packages/amplify_datastore/example/tool/provision_integration_test_resources.sh +++ b/packages/amplify_datastore/example/tool/provision_integration_test_resources.sh @@ -33,21 +33,10 @@ PROVIDERS="{\ \"awscloudformation\":$AWSCLOUDFORMATIONCONFIG\ }" -# read the request template and the schema -requestTemplate=`cat tool/add_api_request.json` -schema=`cat tool/schema.graphql` - -# escape quotes and remove new lines from schema -schema=${schema//$'"'/'\"'} -schema=${schema//$'\n'/} - -# create the request with the actual schema -request="${requestTemplate//$schema}" - amplify init \ --amplify $AMPLIFY \ --frontend $FRONTEND \ --providers $PROVIDERS \ --yes -echo "$request" | jq -c | amplify add api --headless + amplify push --yes diff --git a/packages/amplify_datastore/example/tool/schema.graphql b/packages/amplify_datastore/example/tool/schema.graphql index 53b5c8bc64..ac8ea9c40a 100644 --- a/packages/amplify_datastore/example/tool/schema.graphql +++ b/packages/amplify_datastore/example/tool/schema.graphql @@ -345,3 +345,20 @@ type CpkManyToManyTag @model { label: String! posts: [CpkManyToManyPost] @manyToMany(relationName: "CpkPostTags") } + +type PrivateTodo @model @auth(rules: [{ allow: private }]) { + id: ID! + content: String +} +type MultiAuthTodo + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: apiKey } + { allow: private, operations: [read], provider: iam } + { allow: owner, operations: [create, read, update, delete] } + ] + ) { + id: ID! + content: String +} diff --git a/packages/api/amplify_api/example/integration_test/graphql/api_key_test.dart b/packages/api/amplify_api/example/integration_test/graphql/api_key_test.dart index 4bc0cdb532..b8be9c3e7b 100644 --- a/packages/api/amplify_api/example/integration_test/graphql/api_key_test.dart +++ b/packages/api/amplify_api/example/integration_test/graphql/api_key_test.dart @@ -5,12 +5,17 @@ import 'dart:async'; import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_api_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import '../util.dart'; -void main({bool useExistingTestUser = false, bool useGen1 = false}) { +void main({ + bool useExistingTestUser = false, + bool useGen1 = false, + TestUser? testUser, +}) { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group( @@ -18,7 +23,7 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { () { setUpAll(() async { await configureAmplify(useGen1: useGen1); - await signOutTestUser(); + await signOutTestUser(testUser); }); group('queries', () { @@ -55,9 +60,9 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { late StreamSubscription hubEventsSubscription; setUpAll(() async { if (!useExistingTestUser) { - await signUpTestUser(); + testUser = await signUpTestUser(testUser); } - await signInTestUser(); + await signInTestUser(testUser); hubEventsController = StreamController.broadcast(); hubEvents = hubEventsController.stream; @@ -68,7 +73,7 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { tearDownAll(() async { await deleteTestModels(); if (!useExistingTestUser) { - await deleteTestUser(); + await deleteTestUser(testUser); } await hubEventsSubscription.cancel(); diff --git a/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart b/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart index 55918568cb..4251046d25 100644 --- a/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart +++ b/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart @@ -8,6 +8,7 @@ import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_api_example/models/ModelProvider.dart'; import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -23,7 +24,11 @@ const _limit = 10000; const _max = 10000; -void main({bool useExistingTestUser = false, bool useGen1 = false}) { +void main({ + bool useExistingTestUser = false, + bool useGen1 = false, + TestUser? testUser, +}) { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('GraphQL IAM', () { @@ -31,15 +36,15 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { await configureAmplify(useGen1: useGen1); if (!useExistingTestUser) { - await signUpTestUser(); + testUser = await signUpTestUser(testUser); } - await signInTestUser(); + await signInTestUser(testUser); }); tearDownAll(() async { await deleteTestModels(); if (!useExistingTestUser) { - await deleteTestUser(); + await deleteTestUser(testUser); } }); @@ -432,7 +437,7 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { group('queries (guest access)', () { setUpAll(() async { - await signOutTestUser(); + await signOutTestUser(testUser); }); testWidgets('should fetch model that allows guest access', @@ -459,7 +464,7 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { }); tearDownAll(() async { - await signInTestUser(); + await signInTestUser(testUser); }); }); diff --git a/packages/api/amplify_api/example/integration_test/graphql/user_pools_test.dart b/packages/api/amplify_api/example/integration_test/graphql/user_pools_test.dart index 83d2a95b8c..0df3a369a9 100644 --- a/packages/api/amplify_api/example/integration_test/graphql/user_pools_test.dart +++ b/packages/api/amplify_api/example/integration_test/graphql/user_pools_test.dart @@ -4,12 +4,17 @@ import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_api_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import '../util.dart'; -void main({bool useExistingTestUser = false, bool useGen1 = false}) { +void main({ + bool useExistingTestUser = false, + bool useGen1 = false, + TestUser? testUser, +}) { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('GraphQL Cognito User Pools', () { @@ -17,15 +22,15 @@ void main({bool useExistingTestUser = false, bool useGen1 = false}) { await configureAmplify(useGen1: useGen1); if (!useExistingTestUser) { - await signUpTestUser(); + testUser = await signUpTestUser(testUser); } - await signInTestUser(); + await signInTestUser(testUser); }); tearDownAll(() async { await deleteTestModels(); if (!useExistingTestUser) { - await deleteTestUser(); + await deleteTestUser(testUser); } }); diff --git a/packages/api/amplify_api/example/integration_test/main_test.dart b/packages/api/amplify_api/example/integration_test/main_test.dart index 41975a1999..b31286b672 100644 --- a/packages/api/amplify_api/example/integration_test/main_test.dart +++ b/packages/api/amplify_api/example/integration_test/main_test.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -13,55 +14,64 @@ import 'util.dart'; void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + TestUser? testUser = TestUser(); group('amplify_api gen 1 config', () { setUpAll(() async { await configureAmplify(useGen1: true); - await signUpTestUser(); - await signInTestUser(); + await signUpTestUser(testUser); + await signInTestUser(testUser); }); tearDownAll(() async { - await deleteTestUser(); await Amplify.reset(); }); graph_api_key_test.main( useExistingTestUser: true, useGen1: true, + testUser: testUser, ); graph_iam_test.main( useExistingTestUser: true, useGen1: true, + testUser: testUser, ); graph_user_pools_test.main( useExistingTestUser: true, useGen1: true, + testUser: testUser, ); }); group('amplify_api gen 2 config', () { setUpAll(() async { await configureAmplify(); - await signUpTestUser(); - await signInTestUser(); + testUser ??= await signUpTestUser(testUser); + await signInTestUser(testUser); }); tearDownAll(() async { - await deleteTestUser(); + await deleteTestUser(testUser); await Amplify.reset(); }); graph_api_key_test.main( useExistingTestUser: true, + testUser: testUser, ); graph_iam_test.main( useExistingTestUser: true, + testUser: testUser, ); graph_user_pools_test.main( useExistingTestUser: true, + testUser: testUser, ); - rest_test.main(useExistingTestUser: true); + rest_test.main( + useExistingTestUser: true, + testUser: testUser, + ); }); } diff --git a/packages/api/amplify_api/example/integration_test/rest_test.dart b/packages/api/amplify_api/example/integration_test/rest_test.dart index fd9295734e..d7e72b1e2c 100644 --- a/packages/api/amplify_api/example/integration_test/rest_test.dart +++ b/packages/api/amplify_api/example/integration_test/rest_test.dart @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -9,7 +10,10 @@ import 'util.dart'; const path = 'items'; const expectedResponseText = 'Hello from Lambda!'; -void main({bool useExistingTestUser = false}) { +void main({ + bool useExistingTestUser = false, + TestUser? testUser, +}) { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); HttpPayload generateTestPayload() => HttpPayload.json({'name': 'mow lawn'}); @@ -24,19 +28,19 @@ void main({bool useExistingTestUser = false}) { await configureAmplify(); if (!useExistingTestUser) { - await signUpTestUser(); + testUser = await signUpTestUser(testUser); } }); tearDownAll(() async { if (!useExistingTestUser) { - await deleteTestUser(); + await deleteTestUser(testUser); } }); group('guest user access', () { setUpAll(() async { - await signOutTestUser(); + await signOutTestUser(testUser); }); testWidgets('should send GET request', (WidgetTester tester) async { @@ -77,7 +81,7 @@ void main({bool useExistingTestUser = false}) { group('authorized user access', () { setUpAll(() async { - await signInTestUser(); + await signInTestUser(testUser); }); testWidgets('should send GET request', (WidgetTester tester) async { diff --git a/packages/api/amplify_api/example/integration_test/util.dart b/packages/api/amplify_api/example/integration_test/util.dart index 6690dabcf9..88b60fbe76 100644 --- a/packages/api/amplify_api/example/integration_test/util.dart +++ b/packages/api/amplify_api/example/integration_test/util.dart @@ -10,13 +10,10 @@ import 'package:amplify_api_example/amplifyconfiguration.dart' as gen1; import 'package:amplify_api_example/models/ModelProvider.dart'; import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; const _subscriptionTimeoutInterval = 5; -TestUser? testUser; - // Keep track of what is created here so it can be deleted. final blogCache = []; final postCache = []; @@ -27,54 +24,6 @@ final cpkExplicitChildCache = []; final cpkImplicitChildCache = []; final sampleCache = []; -class TestUser { - TestUser({ - String? email, - String? password, - }) : _email = generateEmail(), - _password = generatePassword(); - - final String _email; - final String _password; - - Future signUp() async { - await signOut(); - final result = await Amplify.Auth.signUp( - username: _email, - password: _password, - ); - if (!result.isSignUpComplete) { - throw Exception('Unable to sign up test user.'); - } - } - - Future signOut() async { - final session = await Amplify.Auth.fetchAuthSession(); - if (!session.isSignedIn) return; - await Amplify.Auth.signOut(); - } - - /// No-op if already signed in. - Future signIn() async { - final session = await Amplify.Auth.fetchAuthSession(); - if (session.isSignedIn) return; - final result = await Amplify.Auth.signIn( - username: _email, - password: _password, - ); - if (!result.isSignedIn) { - throw Exception('Unable to sign in test user.'); - } - } - - Future delete() async { - final session = await Amplify.Auth.fetchAuthSession(); - if (!session.isSignedIn) await signInTestUser(); - await Amplify.Auth.deleteUser(); - testUser = null; - } -} - Future configureAmplify({bool useGen1 = false}) async { if (!Amplify.isConfigured) { await Amplify.addPlugins([ @@ -105,39 +54,6 @@ String _addRestConfig(String config) { return jsonEncode(json); } -Future signUpTestUser() async { - await signOutTestUser(); - - testUser = TestUser(); - await testUser!.signUp(); -} - -/// No-op if already signed in. -Future signInTestUser() async { - if (testUser == null) { - throw const InvalidStateException( - 'No test user to sign in.', - recoverySuggestion: 'Ensure test user signed up.', - ); - } - await testUser!.signIn(); -} - -// No-op if not signed in. -Future signOutTestUser() async { - await testUser?.signOut(); -} - -Future deleteTestUser() async { - if (testUser == null) { - throw const InvalidStateException( - 'No test user to delete.', - recoverySuggestion: 'Ensure test user signed up.', - ); - } - await testUser!.delete(); -} - // declare utility which creates blog with title as parameter Future addBlog(String name) async { final request = ModelMutations.create( diff --git a/packages/test/amplify_integration_test/lib/amplify_integration_test.dart b/packages/test/amplify_integration_test/lib/amplify_integration_test.dart index 9e000ad0b8..e1c9cba728 100644 --- a/packages/test/amplify_integration_test/lib/amplify_integration_test.dart +++ b/packages/test/amplify_integration_test/lib/amplify_integration_test.dart @@ -19,6 +19,7 @@ export 'package:amplify_test/amplify_test.dart'; /// Auth Utils export 'src/integration_test_utils/auth_cognito/integration_test_auth_utils.dart'; +export 'src/integration_test_utils/auth_cognito/test_user.dart'; export 'src/integration_test_utils/auth_cognito/types/create_mfa_code_response.dart'; export 'src/integration_test_utils/auth_cognito/types/delete_user_response.dart'; diff --git a/packages/test/amplify_integration_test/lib/src/integration_test_utils/auth_cognito/test_user.dart b/packages/test/amplify_integration_test/lib/src/integration_test_utils/auth_cognito/test_user.dart new file mode 100644 index 0000000000..1ac6d04fdd --- /dev/null +++ b/packages/test/amplify_integration_test/lib/src/integration_test_utils/auth_cognito/test_user.dart @@ -0,0 +1,93 @@ +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_test/amplify_test.dart'; + +/// A test user for use in integration tests. +/// Email based sign in mechanism is used. +class TestUser { + /// Create a test user with a random email and password. + TestUser({ + String? email, + String? password, + }) : _email = email ?? generateEmail(), + _password = password ?? generatePassword(); + + final String _email; + final String _password; + + /// Sign up the test user. + Future signUp() async { + await signOut(); + final result = await Amplify.Auth.signUp( + username: _email, + password: _password, + ); + if (!result.isSignUpComplete) { + throw Exception('Unable to sign up test user.'); + } + } + + /// Sign out the test user. No-op if already signed out. + Future signOut() async { + final session = await Amplify.Auth.fetchAuthSession(); + if (!session.isSignedIn) return; + await Amplify.Auth.signOut(); + } + + /// Sign in the test user. No-op if already signed in. + Future signIn() async { + final session = await Amplify.Auth.fetchAuthSession(); + if (session.isSignedIn) return; + try { + final result = await Amplify.Auth.signIn( + username: _email, + password: _password, + ); + if (!result.isSignedIn) { + throw Exception('Unable to sign in test user.'); + } + } on Exception catch (e) { + print(e); + } + } + + /// Delete the user from the auth service. + Future delete() async { + final session = await Amplify.Auth.fetchAuthSession(); + if (!session.isSignedIn) await signIn(); + await Amplify.Auth.deleteUser(); + } +} + +/// Sign up a test user and sign in. +Future signUpTestUser(TestUser? testUser) async { + testUser = testUser ?? TestUser(); + await testUser.signUp(); + return testUser; +} + +/// No-op if already signed in. +Future signInTestUser(TestUser? testUser) async { + if (testUser == null) { + throw const InvalidStateException( + 'No test user to sign in.', + recoverySuggestion: 'Ensure test user signed up.', + ); + } + await testUser.signIn(); +} + +/// No-op if not signed in. +Future signOutTestUser(TestUser? testUser) async { + await testUser?.signOut(); +} + +/// validates the test user and deletes it. +Future deleteTestUser(TestUser? testUser) async { + if (testUser == null) { + throw const InvalidStateException( + 'No test user to delete.', + recoverySuggestion: 'Ensure test user signed up.', + ); + } + await testUser.delete(); +}