From 2ec9b080ac904c3dd378a240e7f840aa26e29673 Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Tue, 8 Feb 2022 10:26:06 -0800 Subject: [PATCH 1/3] feat(datastore): emit subscriptionDataProcessed and syncReceived events --- .../amplify/amplify_core/PlatformUtil.kt | 4 +- .../DataStoreHubEventStreamHandler.kt | 19 ++++++ .../FlutterSubscriptionDataProcessedEvent.kt | 44 +++++++++++++ .../types/model/FlutterSerializedModel.kt | 56 ++++++++++------- .../DataStoreHubEventStreamHandlerTests.swift | 2 +- .../DataStoreHubEventStreamHandler.swift | 16 +++++ .../Classes/types/hub/FlutterHubElement.swift | 35 ++++++++++- .../types/hub/FlutterSyncReceivedEvent.swift | 48 ++++++++++++++ .../amplify_datastore_stream_controller.dart | 63 +++++++++++-------- .../lib/src/publicTypes.dart | 1 + .../lib/src/types/hub/HubEventElement.dart | 6 +- .../hub/HubEventElementWithMetadata.dart | 6 +- .../hub/SubscriptionDataProcessedEvent.dart | 28 +++++++++ 13 files changed, 267 insertions(+), 61 deletions(-) create mode 100644 packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/hub/FlutterSubscriptionDataProcessedEvent.kt create mode 100644 packages/amplify_datastore/ios/Classes/types/hub/FlutterSyncReceivedEvent.swift create mode 100644 packages/amplify_datastore_plugin_interface/lib/src/types/hub/SubscriptionDataProcessedEvent.dart diff --git a/packages/amplify_core/android/src/main/kotlin/com/amazonaws/amplify/amplify_core/PlatformUtil.kt b/packages/amplify_core/android/src/main/kotlin/com/amazonaws/amplify/amplify_core/PlatformUtil.kt index 82e0302f21..0ffc0bc8fa 100644 --- a/packages/amplify_core/android/src/main/kotlin/com/amazonaws/amplify/amplify_core/PlatformUtil.kt +++ b/packages/amplify_core/android/src/main/kotlin/com/amazonaws/amplify/amplify_core/PlatformUtil.kt @@ -43,8 +43,8 @@ inline fun Map<*, *>.cast(): Map = * casts and make them explicit. */ @Suppress("UNCHECKED_CAST") -inline fun List<*>.cast(): ArrayList = - this as ArrayList +inline fun List<*>.cast(): List = + this as List /** * Thrown when an argument of the wrong type is passed to a function. diff --git a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/DataStoreHubEventStreamHandler.kt b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/DataStoreHubEventStreamHandler.kt index 8bdd9f4e05..c7a45ca8f7 100644 --- a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/DataStoreHubEventStreamHandler.kt +++ b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/DataStoreHubEventStreamHandler.kt @@ -21,6 +21,7 @@ import com.amazonaws.amplify.amplify_datastore.types.hub.* import com.amplifyframework.core.Amplify import com.amplifyframework.core.model.SerializedModel import com.amplifyframework.datastore.DataStoreChannelEventName +import com.amplifyframework.datastore.appsync.ModelWithMetadata import com.amplifyframework.datastore.events.ModelSyncedEvent import com.amplifyframework.datastore.events.NetworkStatusEvent import com.amplifyframework.datastore.events.OutboxStatusEvent @@ -189,6 +190,24 @@ class DataStoreHubEventStreamHandler : EventChannel.StreamHandler { LOG.error("Failed to parse and send outboxStatus event: ", e) } } + DataStoreChannelEventName.SUBSCRIPTION_DATA_PROCESSED.toString() -> { + try { + val eventData = hubEvent.data as ModelWithMetadata<*> + val model = eventData.model + if (model is SerializedModel) { + val message = FlutterSubscriptionDataProcessedEvent( + hubEvent.name, + model, + eventData.syncMetadata, + ) + sendEvent(message.toValueMap()) + } else { + LOG.error("Element is not an instance of SerializedModel.") + } + } catch (e: Exception) { + LOG.error("Failed to parse and send ${DataStoreChannelEventName.SUBSCRIPTION_DATA_PROCESSED} event: ", e) + } + } else -> { LOG.info("Unhandled DataStoreHubEvent: " + hubEvent.name + "\n" + hubEvent.data.toString()) } diff --git a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/hub/FlutterSubscriptionDataProcessedEvent.kt b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/hub/FlutterSubscriptionDataProcessedEvent.kt new file mode 100644 index 0000000000..23d63e4196 --- /dev/null +++ b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/hub/FlutterSubscriptionDataProcessedEvent.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazonaws.amplify.amplify_datastore.types.hub + +import com.amazonaws.amplify.amplify_datastore.types.model.FlutterSerializedModel +import com.amplifyframework.core.model.Model +import com.amplifyframework.core.model.SerializedModel +import com.amplifyframework.datastore.appsync.ModelMetadata +import com.amplifyframework.datastore.appsync.ModelWithMetadata + +class FlutterSubscriptionDataProcessedEvent( + override val eventName: String, + private val model: SerializedModel, + private val syncMetadata: ModelMetadata, +) : FlutterHubEvent { + override fun toValueMap(): Map { + return mapOf( + "eventName" to eventName, + "modelName" to model.modelName, + "element" to mapOf( + "syncMetadata" to mapOf( + "id" to syncMetadata.id, + "_deleted" to syncMetadata.isDeleted, + "_version" to syncMetadata.version, + "_lastChangedAt" to syncMetadata.lastChangedAt?.secondsSinceEpoch + ), + "model" to FlutterSerializedModel(model).toMap() + ) + ) + } +} diff --git a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/model/FlutterSerializedModel.kt b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/model/FlutterSerializedModel.kt index 8322dc3c51..5b0c8742dc 100644 --- a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/model/FlutterSerializedModel.kt +++ b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/model/FlutterSerializedModel.kt @@ -15,6 +15,7 @@ package com.amazonaws.amplify.amplify_datastore.types.model +import com.amazonaws.amplify.amplify_core.cast import com.amplifyframework.core.model.Model import com.amplifyframework.core.model.ModelSchema import com.amplifyframework.core.model.SerializedCustomType @@ -54,34 +55,41 @@ data class FlutterSerializedModel(val serializedModel: SerializedModel) { ) return serializedData.mapValues { - val field = modelSchema.fields[it.key]!! - when (val value: Any = it.value) { - is Temporal.DateTime -> value.format() - is Temporal.Date -> value.format() - is Temporal.Time -> value.format() - is Model -> FlutterSerializedModel(value as SerializedModel).toMap() - is Temporal.Timestamp -> value.secondsSinceEpoch - is SerializedCustomType -> FlutterSerializedCustomType(value).toMap() - is List<*> -> { - if (field.isCustomType) { - // for a list like field if its type is CustomType - // Then the item type must be CustomType - (value as List).map { item -> - FlutterSerializedCustomType(item).toMap() + val field = modelSchema.fields[it.key] + if (field == null) { + // At some occasions the SerializeData returned from amplify-android contains meta fields + // e.g. _version, _delete which are not a model filed + // for this case we assign a null value to these meta fields as default + null + } else { + when (val value: Any = it.value) { + is Temporal.DateTime -> value.format() + is Temporal.Date -> value.format() + is Temporal.Time -> value.format() + is Model -> FlutterSerializedModel(value as SerializedModel).toMap() + is Temporal.Timestamp -> value.secondsSinceEpoch + is SerializedCustomType -> FlutterSerializedCustomType(value).toMap() + is List<*> -> { + if (field.isCustomType) { + // for a list like field if its type is CustomType + // Then the item type must be CustomType + (value.cast()).map { item -> + FlutterSerializedCustomType(item).toMap() + } } - } - // If collection is not a collection of CustomType - // return the collection directly as - // 1. currently hasMany field won't be populated - // 2. collection of primitive types could be returned as is e.g. ["1", "2"] - else { - value.map { item -> - FlutterFieldUtil.convertValueByFieldType(field.targetType, item) + // If collection is not a collection of CustomType + // return the collection directly as + // 1. currently hasMany field won't be populated + // 2. collection of primitive types could be returned as is e.g. ["1", "2"] + else { + value.map { item -> + FlutterFieldUtil.convertValueByFieldType(field.targetType, item) + } } } + // TODO add for other complex objects that can be returned or be part of the codegen model + else -> FlutterFieldUtil.convertValueByFieldType(field.targetType, value) } - // TODO add for other complex objects that can be returned or be part of the codegen model - else -> FlutterFieldUtil.convertValueByFieldType(field.targetType, value) } } } diff --git a/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift b/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift index ef70a56930..d27ffbc3d2 100644 --- a/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift +++ b/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift @@ -246,7 +246,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { XCTAssertEqual(flutterEvent["modelName"] as! String, "Post") XCTAssertEqual(syncMetaData["_lastChangedAt"] as? Int, nil) XCTAssertEqual(syncMetaData["_version"] as? Int, nil) - XCTAssertEqual(syncMetaData["_deleted"] as? Bool, nil) + XCTAssertEqual(syncMetaData["_deleted"] as? Bool, false) XCTAssertEqual(model["modelName"] as! String, "Post") XCTAssertEqual(serializedData["title"] as! String, "Title 1") innerExpect?.fulfill() diff --git a/packages/amplify_datastore/ios/Classes/DataStoreHubEventStreamHandler.swift b/packages/amplify_datastore/ios/Classes/DataStoreHubEventStreamHandler.swift index 128b3ea56b..61e545d1a2 100644 --- a/packages/amplify_datastore/ios/Classes/DataStoreHubEventStreamHandler.swift +++ b/packages/amplify_datastore/ios/Classes/DataStoreHubEventStreamHandler.swift @@ -174,6 +174,22 @@ public class DataStoreHubEventStreamHandler: NSObject, FlutterStreamHandler { } catch { print("Failed to parse and send outboxMutationProcessed event: \(error)") } + case HubPayload.EventName.DataStore.syncReceived: + do { + guard let eventData = payload.data as? MutationEvent else { + throw FlutterDataStoreError.hubEventCast + } + let schemaRegistries = try self.ensureSchemaRegistries() + let syncReceived = try FlutterSyncReceivedEvent( + event: eventData, + eventName: payload.eventName, + modelSchemaRegistry: schemaRegistries.modelSchemaRegistry, + customTypeSchemaRegistry: schemaRegistries.customTypeSchemaRegistry + ) + flutterEvent = syncReceived.toValueMap() + } catch { + print("Failed to parse and send syncReceived event: \(error)") + } case HubPayload.EventName.Amplify.configured: print("DataStorePlugin successfully initialized") default: diff --git a/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift b/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift index 86718d5b78..6e72be242f 100644 --- a/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift +++ b/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift @@ -26,7 +26,7 @@ public struct FlutterHubElement { var model: [String: Any] var version: Int? var lastChangedAt: Int? - var deleted: Bool? + var deleted: Bool init( hubElement: OutboxMutationEvent.OutboxMutationEventElement, @@ -44,7 +44,38 @@ public struct FlutterHubElement { ) self.version = hubElement.version self.lastChangedAt = hubElement.lastChangedAt - self.deleted = hubElement.deleted + self.deleted = hubElement.deleted ?? false + } + + init( + hubElement: MutationEvent, + modelSchemaRegistry: FlutterSchemaRegistry, + customTypeSchemaRegistry: FlutterSchemaRegistry, + modelName: String + ) throws { + do { + guard let decodedModel = (try ModelRegistry.decode(modelName: modelName, from: hubElement.json) as? FlutterSerializedModel) else { + throw FlutterDataStoreError.hubEventCast + } + model = try decodedModel.toMap( + modelSchemaRegistry: modelSchemaRegistry, + customTypeSchemaRegistry: customTypeSchemaRegistry, + modelName: modelName + ) + self.version = hubElement.version + let serializedData = model["serializedData"] as? [String: Any] ?? [:] + self.deleted = serializedData["_deleted"] as? Bool ?? false + if let value = serializedData["_lastChangedAt"] as? Double { + self.lastChangedAt = Int(value) + } else if let value = serializedData["_lastChangedAt"] as? String { + self.lastChangedAt = Int(value) + } else if let value = serializedData["_lastChangedAt"] as? Int { + self.lastChangedAt = value + } + } + catch { + throw FlutterDataStoreError.hubEventCast + } } func toValueMap() -> Dictionary { diff --git a/packages/amplify_datastore/ios/Classes/types/hub/FlutterSyncReceivedEvent.swift b/packages/amplify_datastore/ios/Classes/types/hub/FlutterSyncReceivedEvent.swift new file mode 100644 index 0000000000..b0c1ca4738 --- /dev/null +++ b/packages/amplify_datastore/ios/Classes/types/hub/FlutterSyncReceivedEvent.swift @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify +import AmplifyPlugins + +struct FlutterSyncReceivedEvent: FlutterHubEvent { + var eventName: String + var modelName: String + var element: FlutterHubElement + + init( + event: MutationEvent, + eventName: String, + modelSchemaRegistry: FlutterSchemaRegistry, + customTypeSchemaRegistry: FlutterSchemaRegistry + ) throws { + self.eventName = shortEventName(eventName: eventName) + self.modelName = event.modelName + do { + self.element = try FlutterHubElement( + hubElement: event, modelSchemaRegistry: modelSchemaRegistry, customTypeSchemaRegistry: customTypeSchemaRegistry, modelName: event.modelName) + } catch { + throw FlutterDataStoreError.acquireSchemaForHub + } + } + + func toValueMap() -> Dictionary { + return [ + "eventName": self.eventName, + "modelName": self.modelName, + "element": self.element.toValueMap() + ] + } +} diff --git a/packages/amplify_datastore/lib/amplify_datastore_stream_controller.dart b/packages/amplify_datastore/lib/amplify_datastore_stream_controller.dart index eea264e83c..cd7e19937b 100644 --- a/packages/amplify_datastore/lib/amplify_datastore_stream_controller.dart +++ b/packages/amplify_datastore/lib/amplify_datastore_stream_controller.dart @@ -19,8 +19,7 @@ import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_inte import 'package:flutter/services.dart'; -EventChannel channel = - const EventChannel("com.amazonaws.amplify/datastore_hub_events"); +const channel = EventChannel('com.amazonaws.amplify/datastore_hub_events'); late ModelProviderInterface modelProvider; StreamSubscription? eventStream; @@ -43,58 +42,70 @@ StreamController _controller = _onListen() { if (eventStream == null) { eventStream = channel.receiveBroadcastStream(1).listen((event) { - switch (event["eventName"]) { - case "ready": + final eventName = event['eventName']; + switch (eventName) { + case 'ready': { - _rebroadcast(event["eventName"]); + _rebroadcast(eventName); } break; - case "networkStatus": + case 'networkStatus': { - _rebroadcast(event["eventName"], - payload: NetworkStatusEvent(event)); + _rebroadcast(eventName, payload: NetworkStatusEvent(event)); } break; case 'subscriptionsEstablished': { - _rebroadcast(event["eventName"]); + _rebroadcast(eventName); } break; - case "syncQueriesStarted": + case 'syncQueriesStarted': { - _rebroadcast(event["eventName"], - payload: SyncQueriesStartedEvent(event)); + _rebroadcast(eventName, payload: SyncQueriesStartedEvent(event)); } break; - case "modelSynced": + case 'modelSynced': { - _rebroadcast(event["eventName"], payload: ModelSyncedEvent(event)); + _rebroadcast(eventName, payload: ModelSyncedEvent(event)); } break; - case "syncQueriesReady": + case 'syncQueriesReady': { - _rebroadcast(event["eventName"]); + _rebroadcast(eventName); } break; - case "outboxMutationEnqueued": + case 'outboxMutationEnqueued': { - _rebroadcast(event["eventName"], - payload: OutboxMutationEvent( - event, modelProvider, event["eventName"])); + _rebroadcast(eventName, + payload: OutboxMutationEvent(event, modelProvider, eventName)); } break; - case "outboxMutationProcessed": + case 'outboxMutationProcessed': { - _rebroadcast(event["eventName"], - payload: OutboxMutationEvent( - event, modelProvider, event["eventName"])); + _rebroadcast(eventName, + payload: OutboxMutationEvent(event, modelProvider, eventName)); } break; - case "outboxStatus": + case 'outboxStatus': { - _rebroadcast(event["eventName"], payload: OutboxStatusEvent(event)); + _rebroadcast(eventName, payload: OutboxStatusEvent(event)); } break; + // event name in amplify-android + case 'subscriptionDataProcessed': + // event name in amplify-ios + case 'syncReceived': + { + _rebroadcast( + 'subscriptionDataProcessed', + payload: SubscriptionDataProcessedEvent( + event, + modelProvider, + 'subscriptionDataProcessed', + ), + ); + break; + } default: break; } diff --git a/packages/amplify_datastore_plugin_interface/lib/src/publicTypes.dart b/packages/amplify_datastore_plugin_interface/lib/src/publicTypes.dart index fb2c1fa796..94e56a105f 100644 --- a/packages/amplify_datastore_plugin_interface/lib/src/publicTypes.dart +++ b/packages/amplify_datastore_plugin_interface/lib/src/publicTypes.dart @@ -39,6 +39,7 @@ export 'types/hub/NetworkStatusEvent.dart'; export 'types/hub/OutboxMutationEvent.dart'; export 'types/hub/OutboxStatusEvent.dart'; export 'types/hub/SyncQueriesStartedEvent.dart'; +export 'types/hub/SubscriptionDataProcessedEvent.dart'; export 'types/models/subscription_event.dart'; export 'types/models/query_snapshot.dart'; export 'types/models/observe_query_throttle_options.dart'; diff --git a/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElement.dart b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElement.dart index ce0989df6f..37e2a81690 100644 --- a/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElement.dart +++ b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElement.dart @@ -19,9 +19,9 @@ part 'HubEventElementWithMetadata.dart'; /// The model associated with a DataStore `outboxMutationEnqueued` or /// `outboxMutationProcessed` Hub event. -class HubEventElement { +class HubEventElement { /// The instance of the mutated model. - final Model model; + final M model; const HubEventElement(this.model); @@ -30,7 +30,7 @@ class HubEventElement { ModelProviderInterface provider, ) { var model = _parseModelFromMap(serializedHubEventElement, provider); - return HubEventElement(model); + return HubEventElement(model as M); } } diff --git a/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElementWithMetadata.dart b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElementWithMetadata.dart index 83fceb62fa..2ee3bc4fc7 100644 --- a/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElementWithMetadata.dart +++ b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/HubEventElementWithMetadata.dart @@ -17,7 +17,7 @@ part of 'HubEventElement.dart'; /// The model and metadata associated with a DataStore `outboxMutationProcessed` /// Hub event. -class HubEventElementWithMetadata extends HubEventElement { +class HubEventElementWithMetadata extends HubEventElement { /// The version of the model. final int version; @@ -28,7 +28,7 @@ class HubEventElementWithMetadata extends HubEventElement { final bool deleted; const HubEventElementWithMetadata( - Model model, { + M model, { required this.version, required this.lastChangedAt, bool? deleted, @@ -46,7 +46,7 @@ class HubEventElementWithMetadata extends HubEventElement { var lastChangedAt = metadata['_lastChangedAt'] as int; var deleted = metadata['_deleted'] as bool?; return HubEventElementWithMetadata( - model, + model as M, version: version, lastChangedAt: lastChangedAt, deleted: deleted, diff --git a/packages/amplify_datastore_plugin_interface/lib/src/types/hub/SubscriptionDataProcessedEvent.dart b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/SubscriptionDataProcessedEvent.dart new file mode 100644 index 0000000000..c080ea33f8 --- /dev/null +++ b/packages/amplify_datastore_plugin_interface/lib/src/types/hub/SubscriptionDataProcessedEvent.dart @@ -0,0 +1,28 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import 'package:amplify_core/amplify_core.dart'; + +class SubscriptionDataProcessedEvent extends HubEventPayload { + late HubEventElementWithMetadata element; + late String modelName; + + SubscriptionDataProcessedEvent(Map serializedData, + ModelProviderInterface provider, String eventName) { + element = HubEventElementWithMetadata.fromMap(serializedData, provider); + modelName = serializedData["modelName"] as String; + } +} From 9e074ed13c48fdc79222fb9b4b83b391a7739285 Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Tue, 8 Feb 2022 11:14:42 -0800 Subject: [PATCH 2/3] chore(datastore): Fix typo and add missing fields in test schema --- .../integration_test/model_type_test.dart | 4 +- .../CustomTypeWithAppsyncScalarTypes.dart | 226 ++++++++++++++++-- .../example/lib/models/ModelProvider.dart | 2 +- .../models/ModelWithAppsyncScalarTypes.dart | 42 ++-- .../example/tool/schema.graphql | 12 +- 5 files changed, 245 insertions(+), 41 deletions(-) diff --git a/packages/amplify_datastore/example/integration_test/model_type_test.dart b/packages/amplify_datastore/example/integration_test/model_type_test.dart index ba11dffaca..568b04b3a7 100644 --- a/packages/amplify_datastore/example/integration_test/model_type_test.dart +++ b/packages/amplify_datastore/example/integration_test/model_type_test.dart @@ -116,7 +116,7 @@ void main() { var list = List.generate( 3, (i) => TemporalDate(dateTime.add(Duration(days: i)))); var models = List.generate( - 5, (_) => ModelWithAppsyncScalarTypes(listOfAWSDataValue: list)); + 5, (_) => ModelWithAppsyncScalarTypes(listOfAWSDateValue: list)); testModelOperations(models: models); }); @@ -248,7 +248,7 @@ void main() { stringValue: 'string', intValue: 1, floatValue: 1.0, - boolValue: true, + booleanValue: true, awsDateValue: TemporalDate(DateTime.utc(2021, 9, 22)), awsDateTimeValue: TemporalDateTime(DateTime.utc(2021, 9, 22, 23, 0, 0)), awsTimeValue: TemporalTime(DateTime.utc(2021, 9, 22, 0, 0, 0)), diff --git a/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart b/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart index ce41c9de36..38c44eb85e 100644 --- a/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart +++ b/packages/amplify_datastore/example/lib/models/CustomTypeWithAppsyncScalarTypes.dart @@ -33,7 +33,7 @@ class CustomTypeWithAppsyncScalarTypes { final List? _listOfIntValue; final double? _floatValue; final List? _listOfFloatValue; - final bool? _boolValue; + final bool? _booleanValue; final List? _listOfBooleanValue; final TemporalDate? _awsDateValue; final List? _listOfAWSDateValue; @@ -43,8 +43,16 @@ class CustomTypeWithAppsyncScalarTypes { final List? _listOfAWSTimeValue; final TemporalTimestamp? _awsTimestampValue; final List? _listOfAWSTimestampValue; + final String? _awsEmailValue; + final List? _listOfAWSEmailValue; final String? _awsJsonValue; final List? _listOfAWSJsonValue; + final String? _awsPhoneValue; + final List? _listOfAWSPhoneValue; + final String? _awsURLValue; + final List? _listOfAWSURLValue; + final String? _awsIPAddressValue; + final List? _listOfAWSIPAddressValue; final EnumField? _enumValue; final List? _listOfEnumValue; final SimpleCustomType? _customTypeValue; @@ -74,8 +82,8 @@ class CustomTypeWithAppsyncScalarTypes { return _listOfFloatValue; } - bool? get boolValue { - return _boolValue; + bool? get booleanValue { + return _booleanValue; } List? get listOfBooleanValue { @@ -114,6 +122,14 @@ class CustomTypeWithAppsyncScalarTypes { return _listOfAWSTimestampValue; } + String? get awsEmailValue { + return _awsEmailValue; + } + + List? get listOfAWSEmailValue { + return _listOfAWSEmailValue; + } + String? get awsJsonValue { return _awsJsonValue; } @@ -122,6 +138,30 @@ class CustomTypeWithAppsyncScalarTypes { return _listOfAWSJsonValue; } + String? get awsPhoneValue { + return _awsPhoneValue; + } + + List? get listOfAWSPhoneValue { + return _listOfAWSPhoneValue; + } + + String? get awsURLValue { + return _awsURLValue; + } + + List? get listOfAWSURLValue { + return _listOfAWSURLValue; + } + + String? get awsIPAddressValue { + return _awsIPAddressValue; + } + + List? get listOfAWSIPAddressValue { + return _listOfAWSIPAddressValue; + } + EnumField? get enumValue { return _enumValue; } @@ -145,7 +185,7 @@ class CustomTypeWithAppsyncScalarTypes { listOfIntValue, floatValue, listOfFloatValue, - boolValue, + booleanValue, listOfBooleanValue, awsDateValue, listOfAWSDateValue, @@ -155,8 +195,16 @@ class CustomTypeWithAppsyncScalarTypes { listOfAWSTimeValue, awsTimestampValue, listOfAWSTimestampValue, + awsEmailValue, + listOfAWSEmailValue, awsJsonValue, listOfAWSJsonValue, + awsPhoneValue, + listOfAWSPhoneValue, + awsURLValue, + listOfAWSURLValue, + awsIPAddressValue, + listOfAWSIPAddressValue, enumValue, listOfEnumValue, customTypeValue, @@ -167,7 +215,7 @@ class CustomTypeWithAppsyncScalarTypes { _listOfIntValue = listOfIntValue, _floatValue = floatValue, _listOfFloatValue = listOfFloatValue, - _boolValue = boolValue, + _booleanValue = booleanValue, _listOfBooleanValue = listOfBooleanValue, _awsDateValue = awsDateValue, _listOfAWSDateValue = listOfAWSDateValue, @@ -177,8 +225,16 @@ class CustomTypeWithAppsyncScalarTypes { _listOfAWSTimeValue = listOfAWSTimeValue, _awsTimestampValue = awsTimestampValue, _listOfAWSTimestampValue = listOfAWSTimestampValue, + _awsEmailValue = awsEmailValue, + _listOfAWSEmailValue = listOfAWSEmailValue, _awsJsonValue = awsJsonValue, _listOfAWSJsonValue = listOfAWSJsonValue, + _awsPhoneValue = awsPhoneValue, + _listOfAWSPhoneValue = listOfAWSPhoneValue, + _awsURLValue = awsURLValue, + _listOfAWSURLValue = listOfAWSURLValue, + _awsIPAddressValue = awsIPAddressValue, + _listOfAWSIPAddressValue = listOfAWSIPAddressValue, _enumValue = enumValue, _listOfEnumValue = listOfEnumValue, _customTypeValue = customTypeValue, @@ -191,7 +247,7 @@ class CustomTypeWithAppsyncScalarTypes { List? listOfIntValue, double? floatValue, List? listOfFloatValue, - bool? boolValue, + bool? booleanValue, List? listOfBooleanValue, TemporalDate? awsDateValue, List? listOfAWSDateValue, @@ -201,8 +257,16 @@ class CustomTypeWithAppsyncScalarTypes { List? listOfAWSTimeValue, TemporalTimestamp? awsTimestampValue, List? listOfAWSTimestampValue, + String? awsEmailValue, + List? listOfAWSEmailValue, String? awsJsonValue, List? listOfAWSJsonValue, + String? awsPhoneValue, + List? listOfAWSPhoneValue, + String? awsURLValue, + List? listOfAWSURLValue, + String? awsIPAddressValue, + List? listOfAWSIPAddressValue, EnumField? enumValue, List? listOfEnumValue, SimpleCustomType? customTypeValue, @@ -220,7 +284,7 @@ class CustomTypeWithAppsyncScalarTypes { listOfFloatValue: listOfFloatValue != null ? List.unmodifiable(listOfFloatValue) : listOfFloatValue, - boolValue: boolValue, + booleanValue: booleanValue, listOfBooleanValue: listOfBooleanValue != null ? List.unmodifiable(listOfBooleanValue) : listOfBooleanValue, @@ -240,10 +304,26 @@ class CustomTypeWithAppsyncScalarTypes { listOfAWSTimestampValue: listOfAWSTimestampValue != null ? List.unmodifiable(listOfAWSTimestampValue) : listOfAWSTimestampValue, + awsEmailValue: awsEmailValue, + listOfAWSEmailValue: listOfAWSEmailValue != null + ? List.unmodifiable(listOfAWSEmailValue) + : listOfAWSEmailValue, awsJsonValue: awsJsonValue, listOfAWSJsonValue: listOfAWSJsonValue != null ? List.unmodifiable(listOfAWSJsonValue) : listOfAWSJsonValue, + awsPhoneValue: awsPhoneValue, + listOfAWSPhoneValue: listOfAWSPhoneValue != null + ? List.unmodifiable(listOfAWSPhoneValue) + : listOfAWSPhoneValue, + awsURLValue: awsURLValue, + listOfAWSURLValue: listOfAWSURLValue != null + ? List.unmodifiable(listOfAWSURLValue) + : listOfAWSURLValue, + awsIPAddressValue: awsIPAddressValue, + listOfAWSIPAddressValue: listOfAWSIPAddressValue != null + ? List.unmodifiable(listOfAWSIPAddressValue) + : listOfAWSIPAddressValue, enumValue: enumValue, listOfEnumValue: listOfEnumValue != null ? List.unmodifiable(listOfEnumValue) @@ -271,7 +351,7 @@ class CustomTypeWithAppsyncScalarTypes { _floatValue == other._floatValue && DeepCollectionEquality() .equals(_listOfFloatValue, other._listOfFloatValue) && - _boolValue == other._boolValue && + _booleanValue == other._booleanValue && DeepCollectionEquality() .equals(_listOfBooleanValue, other._listOfBooleanValue) && _awsDateValue == other._awsDateValue && @@ -286,9 +366,21 @@ class CustomTypeWithAppsyncScalarTypes { _awsTimestampValue == other._awsTimestampValue && DeepCollectionEquality() .equals(_listOfAWSTimestampValue, other._listOfAWSTimestampValue) && + _awsEmailValue == other._awsEmailValue && + DeepCollectionEquality() + .equals(_listOfAWSEmailValue, other._listOfAWSEmailValue) && _awsJsonValue == other._awsJsonValue && DeepCollectionEquality() .equals(_listOfAWSJsonValue, other._listOfAWSJsonValue) && + _awsPhoneValue == other._awsPhoneValue && + DeepCollectionEquality() + .equals(_listOfAWSPhoneValue, other._listOfAWSPhoneValue) && + _awsURLValue == other._awsURLValue && + DeepCollectionEquality() + .equals(_listOfAWSURLValue, other._listOfAWSURLValue) && + _awsIPAddressValue == other._awsIPAddressValue && + DeepCollectionEquality() + .equals(_listOfAWSIPAddressValue, other._listOfAWSIPAddressValue) && _enumValue == other._enumValue && DeepCollectionEquality() .equals(_listOfEnumValue, other._listOfEnumValue) && @@ -321,8 +413,8 @@ class CustomTypeWithAppsyncScalarTypes { buffer.write("listOfFloatValue=" + (_listOfFloatValue != null ? _listOfFloatValue!.toString() : "null") + ", "); - buffer.write("boolValue=" + - (_boolValue != null ? _boolValue!.toString() : "null") + + buffer.write("booleanValue=" + + (_booleanValue != null ? _booleanValue!.toString() : "null") + ", "); buffer.write("listOfBooleanValue=" + (_listOfBooleanValue != null @@ -361,12 +453,34 @@ class CustomTypeWithAppsyncScalarTypes { ? _listOfAWSTimestampValue!.toString() : "null") + ", "); + buffer.write("awsEmailValue=" + "$_awsEmailValue" + ", "); + buffer.write("listOfAWSEmailValue=" + + (_listOfAWSEmailValue != null + ? _listOfAWSEmailValue!.toString() + : "null") + + ", "); buffer.write("awsJsonValue=" + "$_awsJsonValue" + ", "); buffer.write("listOfAWSJsonValue=" + (_listOfAWSJsonValue != null ? _listOfAWSJsonValue!.toString() : "null") + ", "); + buffer.write("awsPhoneValue=" + "$_awsPhoneValue" + ", "); + buffer.write("listOfAWSPhoneValue=" + + (_listOfAWSPhoneValue != null + ? _listOfAWSPhoneValue!.toString() + : "null") + + ", "); + buffer.write("awsURLValue=" + "$_awsURLValue" + ", "); + buffer.write("listOfAWSURLValue=" + + (_listOfAWSURLValue != null ? _listOfAWSURLValue!.toString() : "null") + + ", "); + buffer.write("awsIPAddressValue=" + "$_awsIPAddressValue" + ", "); + buffer.write("listOfAWSIPAddressValue=" + + (_listOfAWSIPAddressValue != null + ? _listOfAWSIPAddressValue!.toString() + : "null") + + ", "); buffer.write("enumValue=" + (_enumValue != null ? enumToString(_enumValue)! : "null") + ", "); @@ -394,7 +508,7 @@ class CustomTypeWithAppsyncScalarTypes { List? listOfIntValue, double? floatValue, List? listOfFloatValue, - bool? boolValue, + bool? booleanValue, List? listOfBooleanValue, TemporalDate? awsDateValue, List? listOfAWSDateValue, @@ -404,8 +518,16 @@ class CustomTypeWithAppsyncScalarTypes { List? listOfAWSTimeValue, TemporalTimestamp? awsTimestampValue, List? listOfAWSTimestampValue, + String? awsEmailValue, + List? listOfAWSEmailValue, String? awsJsonValue, List? listOfAWSJsonValue, + String? awsPhoneValue, + List? listOfAWSPhoneValue, + String? awsURLValue, + List? listOfAWSURLValue, + String? awsIPAddressValue, + List? listOfAWSIPAddressValue, EnumField? enumValue, List? listOfEnumValue, SimpleCustomType? customTypeValue, @@ -417,7 +539,7 @@ class CustomTypeWithAppsyncScalarTypes { listOfIntValue: listOfIntValue ?? this.listOfIntValue, floatValue: floatValue ?? this.floatValue, listOfFloatValue: listOfFloatValue ?? this.listOfFloatValue, - boolValue: boolValue ?? this.boolValue, + booleanValue: booleanValue ?? this.booleanValue, listOfBooleanValue: listOfBooleanValue ?? this.listOfBooleanValue, awsDateValue: awsDateValue ?? this.awsDateValue, listOfAWSDateValue: listOfAWSDateValue ?? this.listOfAWSDateValue, @@ -429,8 +551,17 @@ class CustomTypeWithAppsyncScalarTypes { awsTimestampValue: awsTimestampValue ?? this.awsTimestampValue, listOfAWSTimestampValue: listOfAWSTimestampValue ?? this.listOfAWSTimestampValue, + awsEmailValue: awsEmailValue ?? this.awsEmailValue, + listOfAWSEmailValue: listOfAWSEmailValue ?? this.listOfAWSEmailValue, awsJsonValue: awsJsonValue ?? this.awsJsonValue, listOfAWSJsonValue: listOfAWSJsonValue ?? this.listOfAWSJsonValue, + awsPhoneValue: awsPhoneValue ?? this.awsPhoneValue, + listOfAWSPhoneValue: listOfAWSPhoneValue ?? this.listOfAWSPhoneValue, + awsURLValue: awsURLValue ?? this.awsURLValue, + listOfAWSURLValue: listOfAWSURLValue ?? this.listOfAWSURLValue, + awsIPAddressValue: awsIPAddressValue ?? this.awsIPAddressValue, + listOfAWSIPAddressValue: + listOfAWSIPAddressValue ?? this.listOfAWSIPAddressValue, enumValue: enumValue ?? this.enumValue, listOfEnumValue: listOfEnumValue ?? this.listOfEnumValue, customTypeValue: customTypeValue ?? this.customTypeValue, @@ -449,7 +580,7 @@ class CustomTypeWithAppsyncScalarTypes { _listOfFloatValue = (json['listOfFloatValue'] as List?) ?.map((e) => (e as num).toDouble()) .toList(), - _boolValue = json['boolValue'], + _booleanValue = json['booleanValue'], _listOfBooleanValue = json['listOfBooleanValue']?.cast(), _awsDateValue = json['awsDateValue'] != null ? TemporalDate.fromString(json['awsDateValue']) @@ -475,8 +606,17 @@ class CustomTypeWithAppsyncScalarTypes { _listOfAWSTimestampValue = (json['listOfAWSTimestampValue'] as List?) ?.map((e) => TemporalTimestamp.fromSeconds(e)) .toList(), + _awsEmailValue = json['awsEmailValue'], + _listOfAWSEmailValue = json['listOfAWSEmailValue']?.cast(), _awsJsonValue = json['awsJsonValue'], _listOfAWSJsonValue = json['listOfAWSJsonValue']?.cast(), + _awsPhoneValue = json['awsPhoneValue'], + _listOfAWSPhoneValue = json['listOfAWSPhoneValue']?.cast(), + _awsURLValue = json['awsURLValue'], + _listOfAWSURLValue = json['listOfAWSURLValue']?.cast(), + _awsIPAddressValue = json['awsIPAddressValue'], + _listOfAWSIPAddressValue = + json['listOfAWSIPAddressValue']?.cast(), _enumValue = enumFromString(json['enumValue'], EnumField.values), _listOfEnumValue = json['listOfEnumValue'] is List @@ -503,7 +643,7 @@ class CustomTypeWithAppsyncScalarTypes { 'listOfIntValue': _listOfIntValue, 'floatValue': _floatValue, 'listOfFloatValue': _listOfFloatValue, - 'boolValue': _boolValue, + 'booleanValue': _booleanValue, 'listOfBooleanValue': _listOfBooleanValue, 'awsDateValue': _awsDateValue?.format(), 'listOfAWSDateValue': @@ -517,8 +657,16 @@ class CustomTypeWithAppsyncScalarTypes { 'awsTimestampValue': _awsTimestampValue?.toSeconds(), 'listOfAWSTimestampValue': _listOfAWSTimestampValue?.map((e) => e.toSeconds()).toList(), + 'awsEmailValue': _awsEmailValue, + 'listOfAWSEmailValue': _listOfAWSEmailValue, 'awsJsonValue': _awsJsonValue, 'listOfAWSJsonValue': _listOfAWSJsonValue, + 'awsPhoneValue': _awsPhoneValue, + 'listOfAWSPhoneValue': _listOfAWSPhoneValue, + 'awsURLValue': _awsURLValue, + 'listOfAWSURLValue': _listOfAWSURLValue, + 'awsIPAddressValue': _awsIPAddressValue, + 'listOfAWSIPAddressValue': _listOfAWSIPAddressValue, 'enumValue': enumToString(_enumValue), 'listOfEnumValue': _listOfEnumValue?.map((e) => enumToString(e)).toList(), @@ -570,7 +718,7 @@ class CustomTypeWithAppsyncScalarTypes { ofModelName: describeEnum(ModelFieldTypeEnum.double)))); modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( - fieldName: 'boolValue', + fieldName: 'booleanValue', isRequired: false, ofType: ModelFieldType(ModelFieldTypeEnum.bool))); @@ -629,6 +777,18 @@ class CustomTypeWithAppsyncScalarTypes { ofType: ModelFieldType(ModelFieldTypeEnum.collection, ofModelName: describeEnum(ModelFieldTypeEnum.timestamp)))); + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'awsEmailValue', + isRequired: false, + ofType: ModelFieldType(ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'listOfAWSEmailValue', + isRequired: false, + isArray: true, + ofType: ModelFieldType(ModelFieldTypeEnum.collection, + ofModelName: describeEnum(ModelFieldTypeEnum.string)))); + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( fieldName: 'awsJsonValue', isRequired: false, @@ -641,6 +801,42 @@ class CustomTypeWithAppsyncScalarTypes { ofType: ModelFieldType(ModelFieldTypeEnum.collection, ofModelName: describeEnum(ModelFieldTypeEnum.string)))); + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'awsPhoneValue', + isRequired: false, + ofType: ModelFieldType(ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'listOfAWSPhoneValue', + isRequired: false, + isArray: true, + ofType: ModelFieldType(ModelFieldTypeEnum.collection, + ofModelName: describeEnum(ModelFieldTypeEnum.string)))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'awsURLValue', + isRequired: false, + ofType: ModelFieldType(ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'listOfAWSURLValue', + isRequired: false, + isArray: true, + ofType: ModelFieldType(ModelFieldTypeEnum.collection, + ofModelName: describeEnum(ModelFieldTypeEnum.string)))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'awsIPAddressValue', + isRequired: false, + ofType: ModelFieldType(ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( + fieldName: 'listOfAWSIPAddressValue', + isRequired: false, + isArray: true, + ofType: ModelFieldType(ModelFieldTypeEnum.collection, + ofModelName: describeEnum(ModelFieldTypeEnum.string)))); + modelSchemaDefinition.addField(ModelFieldDefinition.customTypeField( fieldName: 'enumValue', isRequired: false, diff --git a/packages/amplify_datastore/example/lib/models/ModelProvider.dart b/packages/amplify_datastore/example/lib/models/ModelProvider.dart index 3c7d1884e6..c677a5e4ab 100644 --- a/packages/amplify_datastore/example/lib/models/ModelProvider.dart +++ b/packages/amplify_datastore/example/lib/models/ModelProvider.dart @@ -75,7 +75,7 @@ export 'Tag.dart'; class ModelProvider implements ModelProviderInterface { @override - String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + String version = "f80fece878bf91a76f44577fe599b120"; @override List modelSchemas = [ BelongsToChildExplicit.schema, diff --git a/packages/amplify_datastore/example/lib/models/ModelWithAppsyncScalarTypes.dart b/packages/amplify_datastore/example/lib/models/ModelWithAppsyncScalarTypes.dart index 774540f2af..0176d75e54 100644 --- a/packages/amplify_datastore/example/lib/models/ModelWithAppsyncScalarTypes.dart +++ b/packages/amplify_datastore/example/lib/models/ModelWithAppsyncScalarTypes.dart @@ -39,7 +39,7 @@ class ModelWithAppsyncScalarTypes extends Model { final bool? _booleanValue; final List? _listOfBooleanValue; final TemporalDate? _awsDateValue; - final List? _listOfAWSDataValue; + final List? _listOfAWSDateValue; final TemporalTime? _awsTimeValue; final List? _listOfAWSTimeValue; final TemporalDateTime? _awsDateTimeValue; @@ -111,8 +111,8 @@ class ModelWithAppsyncScalarTypes extends Model { return _awsDateValue; } - List? get listOfAWSDataValue { - return _listOfAWSDataValue; + List? get listOfAWSDateValue { + return _listOfAWSDateValue; } TemporalTime? get awsTimeValue { @@ -200,7 +200,7 @@ class ModelWithAppsyncScalarTypes extends Model { booleanValue, listOfBooleanValue, awsDateValue, - listOfAWSDataValue, + listOfAWSDateValue, awsTimeValue, listOfAWSTimeValue, awsDateTimeValue, @@ -230,7 +230,7 @@ class ModelWithAppsyncScalarTypes extends Model { _booleanValue = booleanValue, _listOfBooleanValue = listOfBooleanValue, _awsDateValue = awsDateValue, - _listOfAWSDataValue = listOfAWSDataValue, + _listOfAWSDateValue = listOfAWSDateValue, _awsTimeValue = awsTimeValue, _listOfAWSTimeValue = listOfAWSTimeValue, _awsDateTimeValue = awsDateTimeValue, @@ -263,7 +263,7 @@ class ModelWithAppsyncScalarTypes extends Model { bool? booleanValue, List? listOfBooleanValue, TemporalDate? awsDateValue, - List? listOfAWSDataValue, + List? listOfAWSDateValue, TemporalTime? awsTimeValue, List? listOfAWSTimeValue, TemporalDateTime? awsDateTimeValue, @@ -301,9 +301,9 @@ class ModelWithAppsyncScalarTypes extends Model { ? List.unmodifiable(listOfBooleanValue) : listOfBooleanValue, awsDateValue: awsDateValue, - listOfAWSDataValue: listOfAWSDataValue != null - ? List.unmodifiable(listOfAWSDataValue) - : listOfAWSDataValue, + listOfAWSDateValue: listOfAWSDateValue != null + ? List.unmodifiable(listOfAWSDateValue) + : listOfAWSDateValue, awsTimeValue: awsTimeValue, listOfAWSTimeValue: listOfAWSTimeValue != null ? List.unmodifiable(listOfAWSTimeValue) @@ -363,7 +363,7 @@ class ModelWithAppsyncScalarTypes extends Model { .equals(_listOfBooleanValue, other._listOfBooleanValue) && _awsDateValue == other._awsDateValue && DeepCollectionEquality() - .equals(_listOfAWSDataValue, other._listOfAWSDataValue) && + .equals(_listOfAWSDateValue, other._listOfAWSDateValue) && _awsTimeValue == other._awsTimeValue && DeepCollectionEquality() .equals(_listOfAWSTimeValue, other._listOfAWSTimeValue) && @@ -430,9 +430,9 @@ class ModelWithAppsyncScalarTypes extends Model { buffer.write("awsDateValue=" + (_awsDateValue != null ? _awsDateValue!.format() : "null") + ", "); - buffer.write("listOfAWSDataValue=" + - (_listOfAWSDataValue != null - ? _listOfAWSDataValue!.toString() + buffer.write("listOfAWSDateValue=" + + (_listOfAWSDateValue != null + ? _listOfAWSDateValue!.toString() : "null") + ", "); buffer.write("awsTimeValue=" + @@ -510,7 +510,7 @@ class ModelWithAppsyncScalarTypes extends Model { bool? booleanValue, List? listOfBooleanValue, TemporalDate? awsDateValue, - List? listOfAWSDataValue, + List? listOfAWSDateValue, TemporalTime? awsTimeValue, List? listOfAWSTimeValue, TemporalDateTime? awsDateTimeValue, @@ -540,7 +540,7 @@ class ModelWithAppsyncScalarTypes extends Model { booleanValue: booleanValue ?? this.booleanValue, listOfBooleanValue: listOfBooleanValue ?? this.listOfBooleanValue, awsDateValue: awsDateValue ?? this.awsDateValue, - listOfAWSDataValue: listOfAWSDataValue ?? this.listOfAWSDataValue, + listOfAWSDateValue: listOfAWSDateValue ?? this.listOfAWSDateValue, awsTimeValue: awsTimeValue ?? this.awsTimeValue, listOfAWSTimeValue: listOfAWSTimeValue ?? this.listOfAWSTimeValue, awsDateTimeValue: awsDateTimeValue ?? this.awsDateTimeValue, @@ -581,7 +581,7 @@ class ModelWithAppsyncScalarTypes extends Model { _awsDateValue = json['awsDateValue'] != null ? TemporalDate.fromString(json['awsDateValue']) : null, - _listOfAWSDataValue = (json['listOfAWSDataValue'] as List?) + _listOfAWSDateValue = (json['listOfAWSDateValue'] as List?) ?.map((e) => TemporalDate.fromString(e)) .toList(), _awsTimeValue = json['awsTimeValue'] != null @@ -633,8 +633,8 @@ class ModelWithAppsyncScalarTypes extends Model { 'booleanValue': _booleanValue, 'listOfBooleanValue': _listOfBooleanValue, 'awsDateValue': _awsDateValue?.format(), - 'listOfAWSDataValue': - _listOfAWSDataValue?.map((e) => e.format()).toList(), + 'listOfAWSDateValue': + _listOfAWSDateValue?.map((e) => e.format()).toList(), 'awsTimeValue': _awsTimeValue?.format(), 'listOfAWSTimeValue': _listOfAWSTimeValue?.map((e) => e.format()).toList(), @@ -676,8 +676,8 @@ class ModelWithAppsyncScalarTypes extends Model { static final QueryField LISTOFBOOLEANVALUE = QueryField(fieldName: "listOfBooleanValue"); static final QueryField AWSDATEVALUE = QueryField(fieldName: "awsDateValue"); - static final QueryField LISTOFAWSDATAVALUE = - QueryField(fieldName: "listOfAWSDataValue"); + static final QueryField LISTOFAWSDATEVALUE = + QueryField(fieldName: "listOfAWSDateValue"); static final QueryField AWSTIMEVALUE = QueryField(fieldName: "awsTimeValue"); static final QueryField LISTOFAWSTIMEVALUE = QueryField(fieldName: "listOfAWSTimeValue"); @@ -778,7 +778,7 @@ class ModelWithAppsyncScalarTypes extends Model { ofType: ModelFieldType(ModelFieldTypeEnum.date))); modelSchemaDefinition.addField(ModelFieldDefinition.field( - key: ModelWithAppsyncScalarTypes.LISTOFAWSDATAVALUE, + key: ModelWithAppsyncScalarTypes.LISTOFAWSDATEVALUE, isRequired: false, isArray: true, ofType: ModelFieldType(ModelFieldTypeEnum.collection, diff --git a/packages/amplify_datastore/example/tool/schema.graphql b/packages/amplify_datastore/example/tool/schema.graphql index 32dd0aa29d..458138b560 100644 --- a/packages/amplify_datastore/example/tool/schema.graphql +++ b/packages/amplify_datastore/example/tool/schema.graphql @@ -44,7 +44,7 @@ type ModelWithAppsyncScalarTypes @model { booleanValue: Boolean listOfBooleanValue: [Boolean] awsDateValue: AWSDate - listOfAWSDataValue: [AWSDate] + listOfAWSDateValue: [AWSDate] awsTimeValue: AWSTime listOfAWSTimeValue: [AWSTime] awsDateTimeValue: AWSDateTime @@ -87,7 +87,7 @@ type CustomTypeWithAppsyncScalarTypes { listOfIntValue: [Int] floatValue: Float listOfFloatValue: [Float] - boolValue: Boolean + booleanValue: Boolean listOfBooleanValue: [Boolean] awsDateValue: AWSDate listOfAWSDateValue: [AWSDate] @@ -97,8 +97,16 @@ type CustomTypeWithAppsyncScalarTypes { listOfAWSTimeValue: [AWSTime] awsTimestampValue: AWSTimestamp listOfAWSTimestampValue: [AWSTimestamp] + awsEmailValue: AWSEmail + listOfAWSEmailValue: [AWSEmail] awsJsonValue: AWSJSON listOfAWSJsonValue: [AWSJSON] + awsPhoneValue: AWSPhone + listOfAWSPhoneValue: [AWSPhone] + awsURLValue: AWSURL + listOfAWSURLValue: [AWSURL] + awsIPAddressValue: AWSIPAddress + listOfAWSIPAddressValue: [AWSIPAddress] enumValue: EnumField listOfEnumValue: [EnumField] customTypeValue: SimpleCustomType From 32c97cd1672073fdb516f70daa287593aac75db5 Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Wed, 9 Feb 2022 17:07:07 -0800 Subject: [PATCH 3/3] chore(datastore): enable cloud sync in integration tests - Add reusable util for testing cloud sync opeartion - Make integration tests configurable with - `--device-id` or `-d` to specify running device ID - `--enable-cloud-sync` or `-ec` to enable cloud sync in tests --- build-support/integ_test_android.sh | 89 ++ build-support/integ_test_ios.sh | 82 +- melos.yaml | 4 +- packages/amplify_datastore/example/.gitignore | 21 + .../example/integration_test/main_test.dart | 4 - .../query_test/pagination_test.dart | 2 - .../aws_date_query_predicate_test.dart | 1 - .../aws_date_time_query_predicate_test.dart | 1 - .../aws_time_query_predicate_test.dart | 1 - .../aws_timestamp_query_predicate_test.dart | 1 - .../query_test/sort_order_test.dart | 1 - .../standard_query_operations_test.dart | 2 - .../relationship_type_test.dart | 1302 ----------------- .../basic_model_operation_test.dart | 115 ++ .../belongs_to_explicit_parent_test.dart | 59 + .../belongs_to_implicit_parent_test.dart | 58 + .../has_many_bidirectional_explicit_test.dart | 64 + .../has_many_bidirectional_implicit_test.dart | 63 + .../has_many_explicit_parent_test.dart | 60 + .../has_many_implicit_parent_test.dart | 57 + .../has_one_explicit_child_test.dart | 56 + .../has_one_implicit_child_test.dart | 59 + .../many_to_many_test.dart | 247 ++++ .../basic_model_operation/ModelProvider.dart | 70 + .../models/belongs_to/ModelProvider.dart | 60 + .../models/has_many/ModelProvider.dart | 60 + .../has_many_bidirectional/ModelProvider.dart | 65 + .../models/has_one/ModelProvider.dart | 54 + .../models/many_to_many/ModelProvider.dart | 68 + .../multi_relationship/ModelProvider.dart | 61 + .../multi_relationship_test.dart | 276 ++++ .../integration_test/utils/setup_utils.dart | 66 +- .../utils/sort_order_utils.dart | 1 - .../test_cloud_synced_model_operation.dart | 308 ++++ .../wait_for_expected_event_from_hub.dart | 50 + .../amplify_datastore/example/pubspec.yaml | 1 + .../example/tool/add_api_request.json | 7 +- .../example/tool/schema.graphql | 2 +- .../lib/method_channel_datastore.dart | 1 - 39 files changed, 2169 insertions(+), 1330 deletions(-) create mode 100755 build-support/integ_test_android.sh delete mode 100644 packages/amplify_datastore/example/integration_test/relationship_type_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart create mode 100644 packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart create mode 100644 packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart create mode 100644 packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart diff --git a/build-support/integ_test_android.sh b/build-support/integ_test_android.sh new file mode 100755 index 0000000000..56589961fd --- /dev/null +++ b/build-support/integ_test_android.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +if [ ! -d android ]; then + echo "No Android project to test" >&2 + exit +fi + +DEFAULT_DEVICE_ID="sdk" +DEFAULT_ENABLE_CLOUD_SYNC="true" + +while [ $# -gt 0 ]; do + case "$1" in + -d|--device-id) + deviceId="$2" + ;; + -ec|--enable-cloud-sync) + case "$2" in + true|false) + enableCloudSync="$2" + ;; + *) + echo "Invalid value for $1" + exit 1 + esac + ;; + *) + echo "Invalid arguments" + exit 1 + esac + shift + shift +done + +deviceId=${deviceId:-$DEFAULT_DEVICE_ID} +enableCloudSync=${enableCloudSync:-$DEFAULT_ENABLE_CLOUD_SYNC} + +declare -a testsList +declare -a resultsList + +TARGET=integration_test/main_test.dart +if [ ! -e $TARGET ]; then + echo "$TARGET file not found" >&2 + exit +fi + +testsList+=("$TARGET") +if flutter test \ + --no-pub \ + -d $deviceId \ + $TARGET; then + resultsList+=(0) +else + resultsList+=(1) +fi + +TEST_ENTRIES="integration_test/separate_integration_tests/*.dart" +for ENTRY in $TEST_ENTRIES; do + if [ ! -f "${ENTRY}" ]; then + continue + fi + testsList+=("$ENTRY") + if [ $enableCloudSync == "true" ]; then + echo "Run $ENTRY WITH API Sync" + else + echo "Run $ENTRY WITHOUT API Sync" + fi + + if flutter test \ + --no-pub \ + --dart-define ENABLE_CLOUD_SYNC=$enableCloudSync \ + -d $deviceId \ + $ENTRY; then + resultsList+=(0) + else + resultsList+=(1) + fi +done + +testFailure=0 +for i in "${!testsList[@]}"; do + if [ "${resultsList[i]}" == 0 ]; then + echo "βœ… ${testsList[i]}" + else + testFailure=1 + echo "❌ ${testsList[i]}" + fi +done + +exit $testFailure diff --git a/build-support/integ_test_ios.sh b/build-support/integ_test_ios.sh index 609fa3d16d..c7a6f59707 100755 --- a/build-support/integ_test_ios.sh +++ b/build-support/integ_test_ios.sh @@ -7,25 +7,99 @@ if [ ! -d ios ]; then exit fi +DEFAULT_DEVICE_ID="iPhone" +DEFAULT_ENABLE_CLOUD_SYNC="true" + +while [ $# -gt 0 ]; do + case "$1" in + -d|--device-id) + deviceId="$2" + ;; + -ec|--enable-cloud-sync) + case "$2" in + true|false) + enableCloudSync="$2" + ;; + *) + echo "Invalid value for $1" + exit 1 + esac + ;; + *) + echo "Invalid arguments" + exit 1 + esac + shift + shift +done + +deviceId=${deviceId:-$DEFAULT_DEVICE_ID} +enableCloudSync=${enableCloudSync:-$DEFAULT_ENABLE_CLOUD_SYNC} + +declare -a testsList +declare -a resultsList + TARGET=integration_test/main_test.dart if [ ! -e $TARGET ]; then echo "$TARGET file not found" >&2 exit fi + + # Use xcodebuild if 'RunnerTests' scheme exists, else `flutter test` if xcodebuild -workspace ios/Runner.xcworkspace -list -json | jq -e '.workspace.schemes | index("RunnerTests")' >/dev/null; then # Build app for testing flutter build ios --no-pub --config-only --simulator --target=$TARGET - + xcodebuild \ -workspace ios/Runner.xcworkspace \ -scheme RunnerTests \ -destination "platform=iOS Simulator,name=iPhone 12 Pro Max" \ test else - flutter test \ + testsList+=("$TARGET") + if flutter test \ --no-pub \ - -d iPhone \ - $TARGET + -d $deviceId \ + $TARGET; then + resultsList+=(0) + else + resultsList+=(1) + fi fi + +TEST_ENTRIES="integration_test/separate_integration_tests/*.dart" +for ENTRY in $TEST_ENTRIES; do + if [ ! -f "${ENTRY}" ]; then + continue + fi + testsList+=("$ENTRY") + if [ $enableCloudSync == "true" ]; then + echo "Run $ENTRY WITH API Sync" + else + echo "Run $ENTRY WITHOUT API Sync" + fi + + if flutter test \ + --no-pub \ + --dart-define ENABLE_CLOUD_SYNC=$enableCloudSync \ + -d $deviceId \ + $ENTRY; then + resultsList+=(0) + else + resultsList+=(1) + fi +done + +testFailure=0 +for i in "${!testsList[@]}"; do + if [ "${resultsList[i]}" == 0 ]; then + echo "βœ… ${testsList[i]}" + else + testFailure=1 + echo "❌ ${testsList[i]}" + fi +done + +exit $testFailure diff --git a/melos.yaml b/melos.yaml index 49b37733ab..3ef4639b3e 100644 --- a/melos.yaml +++ b/melos.yaml @@ -228,7 +228,7 @@ scripts: - Requires running Android and iOS simulators. test:integration:android: - run: melos exec -c 1 "flutter test --no-pub integration_test/main_test.dart -d sdk" + run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_android.sh" $@ description: Run integration tests for a single package on an Android emulator. - Run with `--no-select` to run for all applicable packages. @@ -239,7 +239,7 @@ scripts: scope: "*example*" test:integration:ios: - run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_ios.sh" + run: melos exec -c 1 -- "$MELOS_ROOT_PATH/build-support/integ_test_ios.sh" $@ description: Run integration tests for a single package on an iOS simulator. - Run with `--no-select` to run for all applicable packages. - Requires launching an iOS simulator prior to execution. diff --git a/packages/amplify_datastore/example/.gitignore b/packages/amplify_datastore/example/.gitignore index 5976fa0dfd..5162ae8e82 100644 --- a/packages/amplify_datastore/example/.gitignore +++ b/packages/amplify_datastore/example/.gitignore @@ -43,3 +43,24 @@ app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +#amplify-do-not-edit-begin +amplify/\#current-cloud-backend +amplify/.config/local-* +amplify/logs +amplify/mock-data +amplify/backend/amplify-meta.json +amplify/backend/.temp +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplifyconfiguration.dart +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig +.secret-* +**.sample +#amplify-do-not-edit-end diff --git a/packages/amplify_datastore/example/integration_test/main_test.dart b/packages/amplify_datastore/example/integration_test/main_test.dart index abe835b896..654692fbfd 100644 --- a/packages/amplify_datastore/example/integration_test/main_test.dart +++ b/packages/amplify_datastore/example/integration_test/main_test.dart @@ -16,10 +16,8 @@ import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'save_test.dart' as save_tests; import 'query_test/query_test.dart' as query_test; import 'model_type_test.dart' as model_type_tests; -import 'relationship_type_test.dart' as relationship_type_tests; import 'observe_test.dart' as observe_tests; import 'observe_query_test.dart' as observe_query_tests; import 'clear_test.dart' as clear_tests; @@ -34,10 +32,8 @@ void main() async { await configureDataStore(); }); - save_tests.main(); query_test.main(); model_type_tests.main(); - relationship_type_tests.main(); observe_tests.main(); observe_query_tests.main(); clear_tests.main(); diff --git a/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart b/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart index e15d4728fd..3eb3a33504 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/pagination_test.dart @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; - import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart index e47bfe26de..2e7905ad16 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart index 0adc135f0f..8dd2fde87d 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_date_time_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart index 366497fda8..74ee60a4da 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_time_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart index ce4c03e8d3..80f985e23f 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/query_predicate_test/aws_timestamp_query_predicate_test.dart @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart b/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart index bf18413d66..bdb520dd83 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/sort_order_test.dart @@ -15,7 +15,6 @@ import 'dart:math'; -import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart b/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart index b5784b4ae4..f3817bcafd 100644 --- a/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart +++ b/packages/amplify_datastore/example/integration_test/query_test/standard_query_operations_test.dart @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -import 'package:amplify_datastore/amplify_datastore.dart'; - import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/relationship_type_test.dart b/packages/amplify_datastore/example/integration_test/relationship_type_test.dart deleted file mode 100644 index 113539c9a3..0000000000 --- a/packages/amplify_datastore/example/integration_test/relationship_type_test.dart +++ /dev/null @@ -1,1302 +0,0 @@ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import 'package:amplify_datastore/amplify_datastore.dart'; - -import 'package:amplify_datastore_example/models/ModelProvider.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; - -import 'utils/setup_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - group('relationship type', () { - group('HasOne (parent refers to child with implicit connection field)', () { - // schema - // type HasOneParent @model { - // id: ID! - // name: String - // implicitChild: HasOneChild @hasOne - // } - // type HasOneChild @model { - // id: ID! - // name: String - // } - var child = HasOneChild(name: 'child'); - // Curretly with @hasOne, parent -> child relationship is created - // by assign child.id to the connection field of the parent - // the connection field is automatically generated when the child - // is implicitly referred in the schema - var parent = HasOneParent( - name: 'HasOne (implicit)', hasOneParentImplicitChildId: child.id); - late Future> childEvent; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvent = Amplify.DataStore.observe(HasOneChild.classType).first; - parentEvent = Amplify.DataStore.observe(HasOneParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasOneChild.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasOneParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isNotEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasOneParent.classType); - var queriedParent = parents.single; - // hasOne relationships do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - expect(queriedParent.hasOneParentImplicitChildId, child.id); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = await Amplify.DataStore.query(HasOneChild.classType); - var queriedChild = children.single; - expect(queriedChild, child); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // hasOne relationships in iOS do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent.id, parent.id); - expect(observedParent.name, parent.name); - expect(observedParent.hasOneParentImplicitChildId, child.id); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - }); - - testWidgets( - 'delete parent', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isEmpty); - }, - // unskip when https://github.com/aws-amplify/amplify-flutter/issues/818 is resolved - skip: true, - ); - - testWidgets('delete child', (WidgetTester tester) async { - await Amplify.DataStore.delete(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isEmpty); - }); - }); - - group('HasOne (parent refers to child with explicit connection field)', () { - // schema - // type HasOneParent @model { - // id: ID! - // name: String - // explicitChildID: ID - // explicitChild: HasOneChild @hasOne(fields: ["explicitChildID"]) - // } - // type HasOneChild @model { - // id: ID! - // name: String - // } - var child = HasOneChild(name: 'child'); - // Curretly with @hasOne, parent -> child relationship is created - // by assign child.id to the connection field of the parent - var parent = - HasOneParent(name: 'HasOne (explicit)', explicitChildID: child.id); - late Future> childEvent; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvent = Amplify.DataStore.observe(HasOneChild.classType).first; - parentEvent = Amplify.DataStore.observe(HasOneParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasOneChild.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasOneParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isNotEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasOneParent.classType); - var queriedParent = parents.single; - // hasOne relationships do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - expect(queriedParent.explicitChildID, child.id); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = await Amplify.DataStore.query(HasOneChild.classType); - var queriedChild = children.single; - expect(queriedChild, child); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // hasOne relationships in iOS do not return the child, so an exact match cannot be performed - // to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent.id, parent.id); - expect(observedParent.name, parent.name); - expect(observedParent.explicitChildID, child.id); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - }); - - testWidgets( - 'delete parent', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasOneParent.classType); - expect(parents, isEmpty); - }, - // unskip when https://github.com/aws-amplify/amplify-flutter/issues/818 is resolved - skip: true, - ); - - testWidgets('delete child', (WidgetTester tester) async { - await Amplify.DataStore.delete(child); - var children = await Amplify.DataStore.query(HasOneChild.classType); - expect(children, isEmpty); - }); - }); - - group('BelongsTo (child refers to parent with implicit connection field)', - () { - // schema - // type BelongsToParent @model { - // id: ID! - // name: String - // } - // type BelongsToChildImplicit @model { - // id: ID! - // name: String - // belongsToParent: BelongsToParent @belongsTo - // } - var parent = BelongsToParent(name: 'belongs to parent'); - var child = BelongsToChildImplicit( - name: 'belongs to child (implicit)', belongsToParent: parent); - late Future> parentEvent; - late Future> childEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - parentEvent = - Amplify.DataStore.observe(BelongsToParent.classType).first; - childEvent = - Amplify.DataStore.observe(BelongsToChildImplicit.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(children, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - var queriedChild = children.single; - expect(queriedChild, child); - expect(queriedChild.belongsToParent, parent); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - expect(observedParent, parent); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - expect(observedChild.belongsToParent, parent); - }); - - testWidgets('delete parent (cascade delete child)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isEmpty); - var children = - await Amplify.DataStore.query(BelongsToChildImplicit.classType); - expect(children, isEmpty); - }); - }); - - group('BelongsTo (child refers to parent with explicit connection field)', - () { - // schema - // type BelongsToParent @model { - // id: ID! - // name: String - // } - // type BelongsToChildExplicit @model { - // id: ID! - // name: String - // belongsToParentID: ID - // belongsToParent: BelongsToParent @belongsTo(fields: ["belongsToParentID"]) - // } - var parent = BelongsToParent(name: 'belongs to parent'); - var child = BelongsToChildExplicit( - name: 'belongs to child (explicit)', belongsToParent: parent); - late Future> parentEvent; - late Future> childEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - parentEvent = - Amplify.DataStore.observe(BelongsToParent.classType).first; - childEvent = - Amplify.DataStore.observe(BelongsToChildExplicit.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save child', (WidgetTester tester) async { - await Amplify.DataStore.save(child); - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(children, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - }); - - testWidgets('query child', (WidgetTester tester) async { - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - var queriedChild = children.single; - expect(queriedChild, child); - expect(queriedChild.belongsToParent, parent); - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - expect(observedParent, parent); - }); - - testWidgets('observe child', (WidgetTester tester) async { - var event = await childEvent; - var observedChild = event.item; - expect(observedChild, child); - expect(observedChild.belongsToParent, parent); - }); - - testWidgets('delete parent (cascade delete child)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - - var parents = await Amplify.DataStore.query(BelongsToParent.classType); - expect(parents, isEmpty); - var children = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(children, isEmpty); - }); - }); - - group('HasMany (parent refers to children with implicit connection field)', - () { - // schema - // type HasManyParent @model { - // id: ID! - // name: String - // implicitChildren: [HasManyChild] @hasMany - // } - // type HasManyChildImplicit @model { - // id: ID! - // name: String - // } - var parent = HasManyParent(name: 'has many parent (implicit)'); - var children = List.generate( - 5, - (i) => HasManyChildImplicit( - name: 'has many child $i (implicit)', - hasManyParentImplicitChildrenId: parent.id)); - late Future>> childEvents; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe(HasManyChildImplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe(HasManyParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasManyParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasManyParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParentImplicitChildrenId, - actualChild.hasManyParentImplicitChildrenId); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParentImplicitChildrenId, - actualChild.hasManyParentImplicitChildrenId); - } - }); - - testWidgets('delete parent', (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isEmpty); - - // cascade delete won't happen in this test case as there is no - // connection field generated in the child model - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isNotEmpty); - // skip this test until https://github.com/aws-amplify/amplify-android/pull/1614 - // is integrated into amplify-flutter - }, skip: true); - - testWidgets('delete children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.delete(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildImplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group( - 'HasMany (parent refers to children with explicit connection field and indexName)', - () { - // schema - // type HasManyParent @model { - // id: ID! - // name: String - // explicitChildren: [HasManyChildExplicit] - // @hasMany(indexName: "byHasManyParent", fields: ["id"]) - // } - // type HasManyChildExplicit @model { - // id: ID! - // name: String - // hasManyParentID: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) - // } - var parent = HasManyParent(name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildExplicit( - name: 'has many child $i (explicit)', - hasManyParentID: parent.id)); - late Future>> childEvents; - late Future> parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe(HasManyChildExplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe(HasManyParent.classType).first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(HasManyParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query(HasManyParent.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParentID, actualChild.hasManyParentID); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParentID, actualChild.hasManyParentID); - } - }); - - testWidgets('delete parent', (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query(HasManyParent.classType); - expect(parents, isEmpty); - - // cascade delete won't happen in this test case as there is no - // connection field generated in the child model - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isNotEmpty); - // skip this test until https://github.com/aws-amplify/amplify-android/pull/1614 - // is integrated into amplify-flutter - }, skip: true); - - testWidgets('delete children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.delete(child); - } - var queriedChildren = - await Amplify.DataStore.query(HasManyChildExplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('HasMany (bi-directional with implicit connection field)', () { - // schema - // type HasManyParentBiDirectionalImplicit @model { - // id: ID! - // name: String - // biDirectionalImplicitChildren: [HasManyChildBiDirectionalImplicit] @hasMany - // } - - // type HasManyChildBiDirectionalImplicit @model { - // id: ID! - // name: String - // hasManyParent: HasManyParentBiDirectionalImplicit @belongsTo - // } - var parent = HasManyParentBiDirectionalImplicit( - name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildBiDirectionalImplicit( - name: 'has many child $i (explicit)', hasManyParent: parent)); - late Future>> - childEvents; - late Future> - parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe( - HasManyChildBiDirectionalImplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe( - HasManyParentBiDirectionalImplicit.classType) - .first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('delete parent (cascade delete associated children)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalImplicit.classType); - expect(parents, isEmpty); - - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalImplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('HasMany (bi-directional with explicit connection field)', () { - // schema - // type HasManyParentBiDirectionalExplicit @model { - // id: ID! - // name: String - // biDirectionalExplicitChildren: [HasManyChildBiDirectionalExplicit] - // @hasMany(indexName: "byHasManyParent", fields: ["id"]) - // } - - // type HasManyChildBiDirectionalExplicit @model { - // id: ID! - // name: String - // hasManyParentId: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) - // hasManyParent: HasManyParentBiDirectionalExplicit - // @belongsTo(fields: ["hasManyParentId"]) - // } - var parent = HasManyParentBiDirectionalExplicit( - name: 'has many parent (explicit)'); - var children = List.generate( - 5, - (i) => HasManyChildBiDirectionalExplicit( - name: 'has many child $i (explicit)', hasManyParent: parent)); - late Future>> - childEvents; - late Future> - parentEvent; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - childEvents = Amplify.DataStore.observe( - HasManyChildBiDirectionalExplicit.classType) - .take(children.length) - .toList(); - - parentEvent = Amplify.DataStore.observe( - HasManyParentBiDirectionalExplicit.classType) - .first; - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save parent', (WidgetTester tester) async { - await Amplify.DataStore.save(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(parents, isNotEmpty); - }); - - testWidgets('save children', (WidgetTester tester) async { - for (var child in children) { - await Amplify.DataStore.save(child); - } - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isNotEmpty); - }); - - testWidgets('query parent', (WidgetTester tester) async { - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - var queriedParent = parents.single; - expect(queriedParent, parent); - expect(queriedParent.id, parent.id); - expect(queriedParent.name, parent.name); - }); - - testWidgets('query children', (WidgetTester tester) async { - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - for (var i = 0; i < children.length; i++) { - var queriedChild = queriedChildren[i]; - var actualChild = children[i]; - expect(queriedChild, actualChild); - expect(queriedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('observe parent', (WidgetTester tester) async { - var event = await parentEvent; - var observedParent = event.item; - // full equality check can be performed since the parent has null children - // and queries return null for nested hasMany data - // this may need to be updated if/when https://github.com/aws-amplify/amplify-flutter/issues/642 is fully resolved - expect(observedParent, parent); - }); - - testWidgets('observe children', (WidgetTester tester) async { - var events = await childEvents; - for (var i = 0; i < children.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedChild = event.item; - var actualChild = children[i]; - expect(eventType, EventType.create); - expect(observedChild, actualChild); - expect(observedChild.hasManyParent, actualChild.hasManyParent); - } - }); - - testWidgets('delete parent (cascade delete associated children)', - (WidgetTester tester) async { - await Amplify.DataStore.delete(parent); - var parents = await Amplify.DataStore.query( - HasManyParentBiDirectionalExplicit.classType); - expect(parents, isEmpty); - - var queriedChildren = await Amplify.DataStore.query( - HasManyChildBiDirectionalExplicit.classType); - expect(queriedChildren, isEmpty); - }); - }); - - group('Many-to-many', () { - // schema - // type Post @model { - // id: ID! - // title: String! - // rating: Int! - // tags: [Tag] @manyToMany(relationName: "PostTags") - // } - // type Tag @model { - // id: ID! - // label: String! - // posts: [Post] @manyToMany(relationName: "PostTags") - // } - var posts = [ - Post(title: 'many to many post 1', rating: 10), - Post(title: 'many to many post 2', rating: 5), - ]; - var tags = [ - Tag(label: 'many to many tag 1'), - Tag(label: 'many to maby tag 2') - ]; - var postTags = [ - PostTags(post: posts[0], tag: tags[0]), - PostTags(post: posts[0], tag: tags[0]), - PostTags(post: posts[1], tag: tags[1]), - PostTags(post: posts[1], tag: tags[1]) - ]; - late Future>> postEvents; - late Future>> tagEvents; - late Future>> postTagsEvents; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - postEvents = Amplify.DataStore.observe(Post.classType) - .take(posts.length) - .toList(); - tagEvents = - Amplify.DataStore.observe(Tag.classType).take(tags.length).toList(); - postTagsEvents = Amplify.DataStore.observe(PostTags.classType) - .take(postTags.length) - .toList(); - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedChildren = - await Amplify.DataStore.query(BelongsToChildExplicit.classType); - expect(queriedChildren, isEmpty); - var queriedParents = - await Amplify.DataStore.query(BelongsToParent.classType); - expect(queriedParents, isEmpty); - }); - - testWidgets('save post', (WidgetTester tester) async { - for (var post in posts) { - await Amplify.DataStore.save(post); - } - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts, isNotEmpty); - }); - - testWidgets('save tags', (WidgetTester tester) async { - for (var tag in tags) { - await Amplify.DataStore.save(tag); - } - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags, isNotEmpty); - }); - - testWidgets('save postTags', (WidgetTester tester) async { - for (var postTag in postTags) { - await Amplify.DataStore.save(postTag); - } - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isNotEmpty); - }); - - testWidgets('query posts', (WidgetTester tester) async { - var queriedPosts = await Amplify.DataStore.query(Post.classType); - for (var post in queriedPosts) { - expect(posts.contains(post), isTrue); - } - }); - - testWidgets('query tags', (WidgetTester tester) async { - var queriedTags = await Amplify.DataStore.query(Tag.classType); - for (var tag in queriedTags) { - expect(tags.contains(tag), isTrue); - } - }); - - testWidgets('query postTags', (WidgetTester tester) async { - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - for (var postTag in queriedPostTags) { - expect( - postTags.indexWhere( - (e) => e.post == postTag.post && e.tag == postTag.tag) > - -1, - isTrue); - } - }); - - testWidgets('observe posts', (WidgetTester tester) async { - var events = await postEvents; - for (var i = 0; i < posts.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedPost = event.item; - var expectedPost = posts[i]; - expect(eventType, EventType.create); - expect(observedPost, expectedPost); - } - }); - - testWidgets('observe tags', (WidgetTester tester) async { - var events = await tagEvents; - for (var i = 0; i < tags.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedTag = event.item; - var expectedTag = tags[i]; - expect(eventType, EventType.create); - expect(observedTag, expectedTag); - } - }); - - testWidgets('observe postTags', (WidgetTester tester) async { - var events = await postTagsEvents; - for (var i = 0; i < tags.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedPostTag = event.item; - var expectedPostTag = postTags[i]; - expect(eventType, EventType.create); - expect(observedPostTag, expectedPostTag); - } - }); - - testWidgets('delete post (cascade delete associated postTag)', - (WidgetTester tester) async { - var deletedPost = posts[0]; - await Amplify.DataStore.delete(deletedPost); - - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts.length, posts.length - 1); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect( - queriedPostTags - .indexWhere((postTag) => postTag.post == deletedPost), - -1); - }); - - testWidgets('delete tag (cascade delete associated postTag)', - (WidgetTester tester) async { - var deletedTag = tags[0]; - await Amplify.DataStore.delete(deletedTag); - - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags.length, tags.length - 1); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect( - queriedPostTags.indexWhere((postTag) => postTag.tag == deletedTag), - -1); - }); - - testWidgets('delete remaining post', (WidgetTester tester) async { - await Amplify.DataStore.delete(posts[1]); - - var queriedPosts = await Amplify.DataStore.query(Post.classType); - expect(queriedPosts, isEmpty); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isEmpty); - }); - - testWidgets('delete remaining tag', (WidgetTester tester) async { - await Amplify.DataStore.delete(tags[1]); - - var queriedTags = await Amplify.DataStore.query(Tag.classType); - expect(queriedTags, isEmpty); - - var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); - expect(queriedPostTags, isEmpty); - }); - }); - - group('Model with multiple relationships', () { - // schema - // type MultiRelatedMeeting @model { - // id: ID! @primaryKey - // title: String! - // attendees: [MultiRelatedRegistration] - // @hasMany(indexName: "byMeeting", fields: ["id"]) - // } - - // type MultiRelatedAttendee @model { - // id: ID! @primaryKey - // meetings: [MultiRelatedRegistration] - // @hasMany(indexName: "byAttendee", fields: ["id"]) - // } - - // type MultiRelatedRegistration @model { - // id: ID! @primaryKey - // meetingId: ID @index(name: "byMeeting", sortKeyFields: ["attendeeId"]) - // meeting: MultiRelatedMeeting! @belongsTo(fields: ["meetingId"]) - // attendeeId: ID @index(name: "byAttendee", sortKeyFields: ["meetingId"]) - // attendee: MultiRelatedAttendee! @belongsTo(fields: ["attendeeId"]) - // } - var meetings = [ - MultiRelatedMeeting(title: 'test meeting 1'), - MultiRelatedMeeting(title: 'test meeting 2'), - ]; - var attendees = [ - MultiRelatedAttendee(), - MultiRelatedAttendee(), - ]; - var registrations = [ - MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[0]), - MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[1]), - MultiRelatedRegistration(attendee: attendees[1], meeting: meetings[1]), - ]; - - late Future>> meetingEvents; - late Future>> attendeeEvents; - late Future>> - registrationEvents; - - setUpAll(() async { - await configureDataStore(); - await clearDataStore(); - - meetingEvents = Amplify.DataStore.observe(MultiRelatedMeeting.classType) - .take(meetings.length) - .toList(); - attendeeEvents = - Amplify.DataStore.observe(MultiRelatedAttendee.classType) - .take(attendees.length) - .toList(); - registrationEvents = - Amplify.DataStore.observe(MultiRelatedRegistration.classType) - .take(registrations.length) - .toList(); - }); - - testWidgets('precondition', (WidgetTester tester) async { - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isEmpty); - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees, isEmpty); - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - - testWidgets('save meetings', (WidgetTester tester) async { - for (var meeting in meetings) { - await Amplify.DataStore.save(meeting); - } - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isNotEmpty); - }); - - testWidgets('save attendees', (WidgetTester tester) async { - for (var attendee in attendees) { - await Amplify.DataStore.save(attendee); - } - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees, isNotEmpty); - }); - - testWidgets('save registrations', (WidgetTester tester) async { - for (var registration in registrations) { - await Amplify.DataStore.save(registration); - } - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isNotEmpty); - }); - - testWidgets('query meetings', (WidgetTester tester) async { - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - for (var meeting in queriedMeetings) { - expect(meetings.contains(meeting), isTrue); - } - }); - - testWidgets('query attendees', (WidgetTester tester) async { - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - for (var attendee in queriedAttendees) { - expect(attendees.contains(attendee), isTrue); - } - }); - - testWidgets('query registraions', (WidgetTester tester) async { - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - for (var registration in queriedRegistrations) { - expect( - registrations.indexWhere((e) => - e.meeting == registration.meeting && - e.attendee == registration.attendee) > - -1, - isTrue); - } - }); - - testWidgets('observe meetings', (WidgetTester tester) async { - var events = await meetingEvents; - for (var i = 0; i < meetings.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedMeeting = event.item; - var expectedMeeting = meetings[i]; - expect(eventType, EventType.create); - expect(observedMeeting, expectedMeeting); - } - }); - - testWidgets('observe attendees', (WidgetTester tester) async { - var events = await attendeeEvents; - for (var i = 0; i < attendees.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedAttendee = event.item; - var expectedAttendee = attendees[i]; - expect(eventType, EventType.create); - expect(observedAttendee, expectedAttendee); - } - }); - - testWidgets('observe resgistrations', (WidgetTester tester) async { - var events = await registrationEvents; - for (var i = 0; i < registrations.length; i++) { - var event = events[i]; - var eventType = event.eventType; - var observedRegistration = event.item; - var expectedRegistration = registrations[i]; - expect(eventType, EventType.create); - expect(observedRegistration, expectedRegistration); - } - }); - - testWidgets('delete meeting (cascade delete associated registration)', - (WidgetTester tester) async { - var deletedMeeting = meetings[0]; - await Amplify.DataStore.delete(deletedMeeting); - - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings.length, meetings.length - 1); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect( - queriedRegistrations.indexWhere( - (registration) => registration.meeting == deletedMeeting), - -1); - }); - - testWidgets('delete attendee (cascade delete associated registration)', - (WidgetTester tester) async { - var deletedAttendee = attendees[0]; - await Amplify.DataStore.delete(deletedAttendee); - - var queriedAttendees = - await Amplify.DataStore.query(MultiRelatedAttendee.classType); - expect(queriedAttendees.length, attendees.length - 1); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect( - queriedRegistrations.indexWhere( - (registration) => registration.attendee == deletedAttendee), - -1); - }); - - testWidgets('delete remaining meeting', (WidgetTester tester) async { - await Amplify.DataStore.delete(meetings[1]); - - var queriedMeetings = - await Amplify.DataStore.query(MultiRelatedMeeting.classType); - expect(queriedMeetings, isEmpty); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - - testWidgets('delete remaining attendee', (WidgetTester tester) async { - await Amplify.DataStore.delete(attendees[1]); - - var queriedAttendees = await Amplify.DataStore.query(Tag.classType); - expect(queriedAttendees, isEmpty); - - var queriedRegistrations = - await Amplify.DataStore.query(MultiRelatedRegistration.classType); - expect(queriedRegistrations, isEmpty); - }); - }); - }); -} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart new file mode 100644 index 0000000000..271b3dc329 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/basic_model_operation_test.dart @@ -0,0 +1,115 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/basic_model_operation/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final enableCloudSync = shouldEnableCloudSync(); + group( + 'Basic model operation${enableCloudSync ? ' with API sync 🌩 enabled' : ''} -', + () { + Blog testBlog = Blog(name: 'test blog'); + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + }); + + testWidgets( + 'should save a new model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testBlog], + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(testBlog); + } + + var queriedBlogs = await Amplify.DataStore.query(Blog.classType); + expect(queriedBlogs, contains(testBlog)); + }); + + testWidgets( + 'should update existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + var updatedTestBlog = testBlog.copyWith(name: "updated test blog"); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [updatedTestBlog], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); + }, + ); + } else { + await Amplify.DataStore.save(updatedTestBlog); + } + + var queriedBlogs = await Amplify.DataStore.query( + Blog.classType, + where: Blog.ID.eq(updatedTestBlog.id), + ); + + expect(queriedBlogs, contains(updatedTestBlog)); + }, + ); + + testWidgets( + 'should delete existing model ${enableCloudSync ? 'and sync to cloud' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [testBlog], + expectedRootModelVersion: 3, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: (events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); + }, + ); + } else { + await Amplify.DataStore.delete(testBlog); + } + + var queriedBlogs = await Amplify.DataStore.query(Blog.classType); + + // verify blog was deleted + expect(queriedBlogs, isNot(contains(testBlog))); + }, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart new file mode 100644 index 0000000000..367cd7e23a --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_explicit_parent_test.dart @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/belongs_to/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('BelongsTo (child refers to parent with explicit connection field)', + () { + // schema + // type BelongsToParent @model { + // id: ID! + // name: String + // explicitChild: BelongsToChildExplicit @hasOne + // } + // type BelongsToChildExplicit @model { + // id: ID! + // name: String + // belongsToParentID: ID + // belongsToParent: BelongsToParent @belongsTo(fields: ["belongsToParentID"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [BelongsToParent(name: 'belongs to parent')]; + var associatedModels = [ + BelongsToChildExplicit( + name: 'belongs to child (explicit)', + belongsToParent: rootModels.first, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: BelongsToParent.classType, + rootModels: rootModels, + associatedModelType: BelongsToChildExplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart new file mode 100644 index 0000000000..c707fd182e --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/belongs_to_implicit_parent_test.dart @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/belongs_to/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('BelongsTo (child refers to parent with implicit connection field)', + () { + // schema + // type BelongsToParent @model { + // id: ID! + // name: String + // implicitChild: BelongsToChildImplicit @hasOne + // } + // type BelongsToChildImplicit @model { + // id: ID! + // name: String + // belongsToParent: BelongsToParent @belongsTo + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [BelongsToParent(name: 'belongs to parent')]; + var associatedModels = [ + BelongsToChildImplicit( + name: 'belongs to child (implicit)', + belongsToParent: rootModels.first, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: BelongsToParent.classType, + rootModels: rootModels, + associatedModelType: BelongsToChildImplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart new file mode 100644 index 0000000000..5e791941b5 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_explicit_test.dart @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many_bidirectional/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (bi-directional with implicit connection field', () { + // schema + // type HasManyParentBiDirectionalExplicit @model { + // id: ID! + // name: String + // biDirectionalExplicitChildren: [HasManyChildBiDirectionalExplicit] + // @hasMany(indexName: "byHasManyParent", fields: ["id"]) + // } + + // type HasManyChildBiDirectionalExplicit @model { + // id: ID! + // name: String + // hasManyParentId: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) + // hasManyParent: HasManyParentBiDirectionalExplicit + // @belongsTo(fields: ["hasManyParentId"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [ + HasManyParentBiDirectionalExplicit(name: 'has many parent (explicit)') + ]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildBiDirectionalExplicit( + name: 'has many child $i (explicit)', + hasManyParent: rootModels.first, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParentBiDirectionalExplicit.classType, + rootModels: rootModels, + associatedModelType: HasManyChildBiDirectionalExplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart new file mode 100644 index 0000000000..d62a89b74e --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_bidirectional_implicit_test.dart @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many_bidirectional/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (bi-directional with implicit connection field', () { + // schema + // type HasManyParentBiDirectionalImplicit @model { + // id: ID! + // name: String + // biDirectionalImplicitChildren: [HasManyChildBiDirectionalImplicit] @hasMany + // } + + // type HasManyChildBiDirectionalImplicit @model { + // id: ID! + // name: String + // hasManyParent: HasManyParentBiDirectionalImplicit @belongsTo + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [ + HasManyParentBiDirectionalImplicit( + name: 'has many parent (explicit)', + ) + ]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildBiDirectionalImplicit( + name: 'has many child $i (explicit)', + hasManyParent: rootModels.first, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParentBiDirectionalImplicit.classType, + rootModels: rootModels, + associatedModelType: HasManyChildBiDirectionalImplicit.classType, + associatedModels: associatedModels, + supportCascadeDelete: true, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart new file mode 100644 index 0000000000..a4439c48e1 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_explicit_parent_test.dart @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'HasMany (parent refers to children with explicit connection field and indexName)', + () { + // schema + // type HasManyParent @model { + // id: ID! + // name: String + // explicitChildren: [HasManyChildExplicit] + // @hasMany(indexName: "byHasManyParent", fields: ["id"]) + // } + // type HasManyChildExplicit @model { + // id: ID! + // name: String + // hasManyParentID: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [HasManyParent(name: 'has many parent (explicit)')]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildExplicit( + name: 'has many child $i (explicit)', + hasManyParentID: rootModels.first.id, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParent.classType, + rootModels: rootModels, + associatedModelType: HasManyChildExplicit.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart new file mode 100644 index 0000000000..0e99ecfdf9 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_many_implicit_parent_test.dart @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasMany (parent refers to children with implicit connection field)', + () { + // schema + // type HasManyParent @model { + // id: ID! + // name: String + // implicitChildren: [HasManyChild] @hasMany + // } + // type HasManyChildImplicit @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var rootModels = [HasManyParent(name: 'has many parent (implicit)')]; + var associatedModels = List.generate( + 5, + (i) => HasManyChildImplicit( + name: 'has many child $i (implicit)', + hasManyParentImplicitChildrenId: rootModels.first.id, + ), + ); + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasManyParent.classType, + rootModels: rootModels, + associatedModelType: HasManyChildImplicit.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart new file mode 100644 index 0000000000..06a3fdf416 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_explicit_child_test.dart @@ -0,0 +1,56 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_one/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasOne (parent refers to child with explicit connection field)', () { + // schema + // type HasOneParent @model { + // id: ID! + // name: String + // explicitChildID: ID + // explicitChild: HasOneChild @hasOne(fields: ["explicitChildID"]) + // } + // type HasOneChild @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var associatedModels = [HasOneChild(name: 'child')]; + // Currently with @hasOne, parent -> child relationship is created + // by assign child.id to the connection field of the parent + var rootModels = [ + HasOneParent( + name: 'HasOne (explicit)', explicitChildID: associatedModels.first.id) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasOneParent.classType, + rootModels: rootModels, + associatedModelType: HasOneChild.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart new file mode 100644 index 0000000000..5dc7a1666c --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/has_one_implicit_child_test.dart @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/has_one/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('HasOne (parent refers to child with implicit connection field)', () { + // schema + // type HasOneParent @model { + // id: ID! + // name: String + // implicitChild: HasOneChild @hasOne + // } + // type HasOneChild @model { + // id: ID! + // name: String + // } + final enableCloudSync = shouldEnableCloudSync(); + var associatedModels = [HasOneChild(name: 'child')]; + // Currently with @hasOne, parent -> child relationship is created + // by assign child.id to the connection field of the parent + // the connection field is automatically generated when the child + // is implicitly referred in the schema + var rootModels = [ + HasOneParent( + name: 'HasOne (implicit)', + hasOneParentImplicitChildId: associatedModels.first.id, + ) + ]; + + testRootAndAssociatedModelsRelationship( + modelProvider: ModelProvider.instance, + rootModelType: HasOneParent.classType, + rootModels: rootModels, + associatedModelType: HasOneChild.classType, + associatedModels: associatedModels, + enableCloudSync: enableCloudSync, + ); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart new file mode 100644 index 0000000000..282e5d94de --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/many_to_many_test.dart @@ -0,0 +1,247 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:amplify_datastore/amplify_datastore.dart'; + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:tuple/tuple.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/many_to_many/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Many-to-many', () { + // schema + // type Post @model { + // id: ID! + // title: String! + // rating: Int! + // tags: [Tag] @manyToMany(relationName: "PostTags") + // } + // type Tag @model { + // id: ID! + // label: String! + // posts: [Post] @manyToMany(relationName: "PostTags") + // } + final enableCloudSync = shouldEnableCloudSync(); + var posts = [ + Post(title: 'many to many post 1', rating: 10), + Post(title: 'many to many post 2', rating: 5), + ]; + var tags = [ + Tag(label: 'many to many tag 1'), + Tag(label: 'many to maby tag 2') + ]; + var postTags = [ + PostTags(post: posts[0], tag: tags[0]), + PostTags(post: posts[0], tag: tags[0]), + PostTags(post: posts[1], tag: tags[1]), + PostTags(post: posts[1], tag: tags[1]) + ]; + late Future>> postModelEventsGetter; + late Future>> tagModelEventsGetter; + late Future>> postTagsModelEventsGetter; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + + postModelEventsGetter = createObservedEventsGetter( + Post.classType, + take: posts.length, + eventType: EventType.create, + ); + tagModelEventsGetter = createObservedEventsGetter( + Tag.classType, + take: tags.length, + eventType: EventType.create, + ); + postTagsModelEventsGetter = createObservedEventsGetter( + PostTags.classType, + take: postTags.length, + eventType: EventType.create, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(Post.classType, posts), + Tuple2(Tag.classType, tags), + Tuple2(PostTags.classType, postTags), + ]); + + testWidgets('save post', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: posts, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var post in posts) { + await Amplify.DataStore.save(post); + } + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, containsAll(posts)); + }); + + testWidgets('save tags', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: tags, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var tag in tags) { + await Amplify.DataStore.save(tag); + } + } + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, containsAll(tags)); + }); + + testWidgets('save postTags', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: postTags, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var postTag in postTags) { + await Amplify.DataStore.save(postTag); + } + } + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, containsAll(postTags)); + }); + + testWidgets('observe posts', (WidgetTester tester) async { + var events = await postModelEventsGetter; + expectObservedEventsToMatchModels(events: events, referenceModels: posts); + }); + + testWidgets('observe tags', (WidgetTester tester) async { + var events = await tagModelEventsGetter; + expectObservedEventsToMatchModels(events: events, referenceModels: tags); + }); + + testWidgets('observe postTags', (WidgetTester tester) async { + var events = await postTagsModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: postTags); + }); + + testWidgets('delete post (cascade delete associated postTag)', + (WidgetTester tester) async { + var deletedPost = posts[0]; + var deletedPostTags = postTags.getRange(0, 2).toList(); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedPost], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: deletedPostTags, + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedPost); + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, isNot(contains(deletedPost))); + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, isNot(containsAll(deletedPostTags))); + }); + + testWidgets('delete tag (cascade delete associated postTag)', + (WidgetTester tester) async { + var deletedTag = tags[1]; + var deletedPostTags = postTags.getRange(2, postTags.length).toList(); + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedTag], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: deletedPostTags, + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedTag); + } + + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, isNot(contains(deletedTag))); + + var queriedPostTags = await Amplify.DataStore.query(PostTags.classType); + expect(queriedPostTags, isNot(containsAll(deletedPostTags))); + }); + + testWidgets('delete remaining post', (WidgetTester tester) async { + var deletedPost = posts[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedPost], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedPost); + } + + var queriedPosts = await Amplify.DataStore.query(Post.classType); + expect(queriedPosts, isNot(contains(deletedPost))); + }); + + testWidgets('delete remaining tag', (WidgetTester tester) async { + var deletedTag = tags[0]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedTag], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedTag); + } + + var queriedTags = await Amplify.DataStore.query(Tag.classType); + expect(queriedTags, isNot(contains(deletedTag))); + }); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart new file mode 100644 index 0000000000..e95dcbda0a --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/basic_model_operation/ModelProvider.dart @@ -0,0 +1,70 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/Blog.dart'; +import '../../../../lib/models/Comment.dart'; +import '../../../../lib/models/Post.dart'; +import '../../../../lib/models/PostTags.dart'; +import '../../../../lib/models/Tag.dart'; + +export '../../../../lib/models/Blog.dart'; +export '../../../../lib/models/Comment.dart'; +export '../../../../lib/models/Post.dart'; +export '../../../../lib/models/PostTags.dart'; +export '../../../../lib/models/Tag.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "f80fece878bf91a76f44577fe599b120"; + @override + List modelSchemas = [ + Blog.schema, + Comment.schema, + Post.schema, + PostTags.schema, + Tag.schema + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "Blog": + return Blog.classType; + case "Comment": + return Comment.classType; + case "Post": + return Post.classType; + case "PostTags": + return PostTags.classType; + case "Tag": + return Tag.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart new file mode 100644 index 0000000000..44b85c5984 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/belongs_to/ModelProvider.dart @@ -0,0 +1,60 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/BelongsToChildExplicit.dart'; +import '../../../../lib/models/BelongsToChildImplicit.dart'; +import '../../../../lib/models/BelongsToParent.dart'; + +export '../../../../lib/models/BelongsToChildExplicit.dart'; +export '../../../../lib/models/BelongsToChildImplicit.dart'; +export '../../../../lib/models/BelongsToParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + BelongsToChildExplicit.schema, + BelongsToChildImplicit.schema, + BelongsToParent.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "BelongsToChildExplicit": + return BelongsToChildExplicit.classType; + case "BelongsToChildImplicit": + return BelongsToChildImplicit.classType; + case "BelongsToParent": + return BelongsToParent.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart new file mode 100644 index 0000000000..d9dc02abe4 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many/ModelProvider.dart @@ -0,0 +1,60 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/HasManyChildExplicit.dart'; +import '../../../../lib/models/HasManyChildImplicit.dart'; +import '../../../../lib/models/HasManyParent.dart'; + +export '../../../../lib/models/HasManyChildExplicit.dart'; +export '../../../../lib/models/HasManyChildImplicit.dart'; +export '../../../../lib/models/HasManyParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + HasManyChildExplicit.schema, + HasManyChildImplicit.schema, + HasManyParent.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasManyChildExplicit": + return HasManyChildExplicit.classType; + case "HasManyChildImplicit": + return HasManyChildImplicit.classType; + case "HasManyParent": + return HasManyParent.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart new file mode 100644 index 0000000000..fd16d1540c --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_many_bidirectional/ModelProvider.dart @@ -0,0 +1,65 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/HasManyChildBiDirectionalExplicit.dart'; +import '../../../../lib/models/HasManyChildBiDirectionalImplicit.dart'; +import '../../../../lib/models/HasManyParentBiDirectionalExplicit.dart'; +import '../../../../lib/models/HasManyParentBiDirectionalImplicit.dart'; + +export '../../../../lib/models/HasManyChildBiDirectionalExplicit.dart'; +export '../../../../lib/models/HasManyChildBiDirectionalImplicit.dart'; +export '../../../../lib/models/HasManyParentBiDirectionalExplicit.dart'; +export '../../../../lib/models/HasManyParentBiDirectionalImplicit.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + HasManyChildBiDirectionalExplicit.schema, + HasManyChildBiDirectionalImplicit.schema, + HasManyParentBiDirectionalExplicit.schema, + HasManyParentBiDirectionalImplicit.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasManyChildBiDirectionalExplicit": + return HasManyChildBiDirectionalExplicit.classType; + case "HasManyChildBiDirectionalImplicit": + return HasManyChildBiDirectionalImplicit.classType; + case "HasManyParentBiDirectionalExplicit": + return HasManyParentBiDirectionalExplicit.classType; + case "HasManyParentBiDirectionalImplicit": + return HasManyParentBiDirectionalImplicit.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart new file mode 100644 index 0000000000..db81bf91dc --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/has_one/ModelProvider.dart @@ -0,0 +1,54 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; + +import '../../../../lib/models/HasOneChild.dart'; +import '../../../../lib/models/HasOneParent.dart'; + +export '../../../../lib/models/HasOneChild.dart'; +export '../../../../lib/models/HasOneParent.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [HasOneChild.schema, HasOneParent.schema]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "HasOneChild": + return HasOneChild.classType; + case "HasOneParent": + return HasOneParent.classType; + case "ModelWithAppsyncScalarTypes": + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart new file mode 100644 index 0000000000..c90ce53a37 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/many_to_many/ModelProvider.dart @@ -0,0 +1,68 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import '../../../../lib/models/Post.dart'; +import '../../../../lib/models/PostTags.dart'; +import '../../../../lib/models/Tag.dart'; +import '../../../../lib/models/Blog.dart'; +import '../../../../lib/models/Comment.dart'; + +export '../../../../lib/models/Post.dart'; +export '../../../../lib/models/PostTags.dart'; +export '../../../../lib/models/Tag.dart'; +export '../../../../lib/models/Blog.dart'; +export '../../../../lib/models/Comment.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + Blog.schema, + Comment.schema, + Post.schema, + PostTags.schema, + Tag.schema + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "Blog": + return Blog.classType; + case "Post": + return Post.classType; + case "PostTags": + return PostTags.classType; + case "Tag": + return Tag.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart new file mode 100644 index 0000000000..e37fd360e9 --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/models/multi_relationship/ModelProvider.dart @@ -0,0 +1,61 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; + +import '../../../../lib/models/MultiRelatedAttendee.dart'; +import '../../../../lib/models/MultiRelatedMeeting.dart'; +import '../../../../lib/models/MultiRelatedRegistration.dart'; + +export '../../../../lib/models/MultiRelatedAttendee.dart'; +export '../../../../lib/models/MultiRelatedMeeting.dart'; +export '../../../../lib/models/MultiRelatedRegistration.dart'; + +class ModelProvider implements ModelProviderInterface { + @override + String version = "c4d29b43024b973d2fd3ba65fe7f0a5b"; + @override + List modelSchemas = [ + MultiRelatedAttendee.schema, + MultiRelatedMeeting.schema, + MultiRelatedRegistration.schema, + ]; + static final ModelProvider _instance = ModelProvider(); + @override + List customTypeSchemas = []; + + static ModelProvider get instance => _instance; + + ModelType getModelTypeByModelName(String modelName) { + switch (modelName) { + case "MultiRelatedAttendee": + return MultiRelatedAttendee.classType; + case "MultiRelatedMeeting": + return MultiRelatedMeeting.classType; + case "MultiRelatedRegistration": + return MultiRelatedRegistration.classType; + default: + throw Exception( + "Failed to find model in model provider for model name: " + + modelName); + } + } +} diff --git a/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart b/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart new file mode 100644 index 0000000000..0497d05a2f --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/separate_integration_tests/multi_relationship_test.dart @@ -0,0 +1,276 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:amplify_datastore/amplify_datastore.dart'; + +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:tuple/tuple.dart'; + +import '../utils/setup_utils.dart'; +import '../utils/test_cloud_synced_model_operation.dart'; +import 'models/multi_relationship/ModelProvider.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Model with multiple relationships', () { + // schema + // type MultiRelatedMeeting @model { + // id: ID! @primaryKey + // title: String! + // attendees: [MultiRelatedRegistration] + // @hasMany(indexName: "byMeeting", fields: ["id"]) + // } + + // type MultiRelatedAttendee @model { + // id: ID! @primaryKey + // meetings: [MultiRelatedRegistration] + // @hasMany(indexName: "byAttendee", fields: ["id"]) + // } + + // type MultiRelatedRegistration @model { + // id: ID! @primaryKey + // meetingId: ID @index(name: "byMeeting", sortKeyFields: ["attendeeId"]) + // meeting: MultiRelatedMeeting! @belongsTo(fields: ["meetingId"]) + // attendeeId: ID @index(name: "byAttendee", sortKeyFields: ["meetingId"]) + // attendee: MultiRelatedAttendee! @belongsTo(fields: ["attendeeId"]) + // } + final enableCloudSync = shouldEnableCloudSync(); + var meetings = [ + MultiRelatedMeeting(title: 'test meeting 1'), + MultiRelatedMeeting(title: 'test meeting 2'), + ]; + var attendees = [ + MultiRelatedAttendee(), + MultiRelatedAttendee(), + ]; + var registrations = [ + MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[0]), + MultiRelatedRegistration(attendee: attendees[0], meeting: meetings[1]), + MultiRelatedRegistration(attendee: attendees[1], meeting: meetings[1]), + ]; + + late Future>> + meetingModelEventsGetter; + late Future>> + attendeeModelEventsGetter; + late Future>> + registrationModelEventsGetter; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: ModelProvider.instance); + + meetingModelEventsGetter = createObservedEventsGetter( + MultiRelatedMeeting.classType, + take: meetings.length, + eventType: EventType.create, + ); + attendeeModelEventsGetter = createObservedEventsGetter( + MultiRelatedAttendee.classType, + take: attendees.length, + eventType: EventType.create, + ); + registrationModelEventsGetter = createObservedEventsGetter( + MultiRelatedRegistration.classType, + take: registrations.length, + eventType: EventType.create, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(MultiRelatedMeeting.classType, meetings), + Tuple2(MultiRelatedAttendee.classType, attendees), + Tuple2(MultiRelatedRegistration.classType, registrations), + ]); + + testWidgets('save meetings', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: meetings, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var meeting in meetings) { + await Amplify.DataStore.save(meeting); + } + } + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, containsAll(meetings)); + }); + + testWidgets('save attendees', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: attendees, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var attendee in attendees) { + await Amplify.DataStore.save(attendee); + } + } + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, containsAll(attendees)); + }); + + testWidgets('save registrations', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: registrations, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var registration in registrations) { + await Amplify.DataStore.save(registration); + } + } + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, containsAll(registrations)); + }); + + testWidgets('observe meetings', (WidgetTester tester) async { + var events = await meetingModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: meetings); + }); + + testWidgets('observe attendees', (WidgetTester tester) async { + var events = await attendeeModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: attendees); + }); + + testWidgets('observe registrations', (WidgetTester tester) async { + var events = await registrationModelEventsGetter; + expectObservedEventsToMatchModels( + events: events, referenceModels: registrations); + }); + + testWidgets('delete meeting (cascade delete associated registration)', + (WidgetTester tester) async { + var deletedMeeting = meetings[0]; // cascade delete registration[0] + var deletedRegistration = registrations[0]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedMeeting], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedMeeting); + } + + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, isNot(contains(deletedMeeting))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete attendee (cascade delete associated registration)', + (WidgetTester tester) async { + var deletedAttendee = attendees[0]; + var deletedRegistration = registrations[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedAttendee], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedAttendee); + } + + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, isNot(contains(deletedAttendee))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete remaining meeting', (WidgetTester tester) async { + var deletedMeeting = meetings[1]; + var deletedRegistration = registrations[2]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedMeeting], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: [deletedRegistration], + expectedAssociatedModelVersion: 2, + associatedModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedMeeting); + } + + var queriedMeetings = + await Amplify.DataStore.query(MultiRelatedMeeting.classType); + expect(queriedMeetings, isNot(contains(deletedMeeting))); + + var queriedRegistrations = + await Amplify.DataStore.query(MultiRelatedRegistration.classType); + expect(queriedRegistrations, isNot(contains(deletedRegistration))); + }); + + testWidgets('delete remaining attendee', (WidgetTester tester) async { + var deletedAttendee = attendees[1]; + + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: [deletedAttendee], + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + await Amplify.DataStore.delete(deletedAttendee); + } + + var queriedAttendees = + await Amplify.DataStore.query(MultiRelatedAttendee.classType); + expect(queriedAttendees, isNot(contains(deletedAttendee))); + }); + }); +} diff --git a/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart b/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart index 667065c7d7..1e59334a6b 100644 --- a/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart +++ b/packages/amplify_datastore/example/integration_test/utils/setup_utils.dart @@ -13,17 +13,41 @@ // permissions and limitations under the License. // +import 'dart:async'; + import 'package:amplify_datastore/amplify_datastore.dart'; +import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_datastore_example/amplifyconfiguration.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; -Future configureDataStore() async { +const ENABLE_CLOUD_SYNC = + bool.fromEnvironment('ENABLE_CLOUD_SYNC', defaultValue: false); +const DATASTORE_READY_EVENT_TIMEOUT = const Duration(minutes: 2); +const DELAY_TO_START_DATASTORE = const Duration(milliseconds: 500); +const DELAY_TO_CLEAR_DATASTORE = const Duration(seconds: 2); + +/// Configure [AmplifyDataStore] plugin with given [modelProvider]. +/// When [ENABLE_CLOUD_SYNC] environment variable is set to true, it also +/// configures [AmplifyAPI] and starts DataStore API sync automatically. +Future configureDataStore({ + bool enableCloudSync = false, + ModelProviderInterface? modelProvider, +}) async { if (!Amplify.isConfigured) { - final dataStorePlugin = - AmplifyDataStore(modelProvider: ModelProvider.instance); - await Amplify.addPlugins([dataStorePlugin]); + final dataStorePlugin = AmplifyDataStore( + modelProvider: modelProvider ?? ModelProvider.instance); + List plugins = [dataStorePlugin]; + if (enableCloudSync) { + plugins.add(AmplifyAPI()); + } + await Amplify.addPlugins(plugins); await Amplify.configure(amplifyconfig); + + // Start DataStore API sync after Amplify Configure when cloud sync is enabled + if (enableCloudSync) { + await startDataStore(); + } } } @@ -34,6 +58,38 @@ Future configureDataStore() async { /// /// see: https:///github.com/aws-amplify/amplify-android/issues/1464 Future clearDataStore() async { - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(DELAY_TO_CLEAR_DATASTORE); await Amplify.DataStore.clear(); } + +/// Am async operator that starts DataStore API sync. +/// It returns a Future which complets when [Amplify.Hub] receives the ready +/// event on [HubChannel.DataStore], or completes with an timeout error if +/// the ready event hasn't been emitted with in 2 minutes. +class DataStoreStarter { + final Completer _completer = Completer(); + late StreamSubscription hubSubscription; + + Future startDataStore() { + hubSubscription = Amplify.Hub.listen([HubChannel.DataStore], (event) { + if (event.eventName == 'ready') { + print( + 'πŸŽ‰πŸŽ‰πŸŽ‰DataStore is ready to start running test suites with API sync enabled.πŸŽ‰πŸŽ‰πŸŽ‰'); + hubSubscription.cancel(); + _completer.complete(); + } + }); + + // we are not waiting for DataStore.start to complete + // but an asynchronous DataStore ready event dispatched via the hub + Amplify.DataStore.start(); + return _completer.future.timeout(DATASTORE_READY_EVENT_TIMEOUT); + } +} + +Future startDataStore() async { + await Future.delayed(DELAY_TO_START_DATASTORE); + await DataStoreStarter().startDataStore(); +} + +bool shouldEnableCloudSync() => ENABLE_CLOUD_SYNC; diff --git a/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart b/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart index d89389f3a4..de32b85ea3 100644 --- a/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart +++ b/packages/amplify_datastore/example/integration_test/utils/sort_order_utils.dart @@ -13,7 +13,6 @@ // permissions and limitations under the License. // -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; import 'package:amplify_datastore_example/models/ModelProvider.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart b/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart new file mode 100644 index 0000000000..afa5d900ea --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/utils/test_cloud_synced_model_operation.dart @@ -0,0 +1,308 @@ +import 'package:amplify_datastore/amplify_datastore.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tuple/tuple.dart'; + +import 'setup_utils.dart'; +import 'wait_for_expected_event_from_hub.dart'; + +typedef EventsAssertor = void Function( + List> events); +typedef ModelOperator = Future Function(T model, + {QueryPredicate? where}); + +/// Check each of the [events] is presenting a deleted model. +void modelIsDeletedAssertor( + List> events) { + events.forEach((event) { + expect(event.element.deleted, isTrue); + }); +} + +/// Check each of the [events] is presenting a model that is not deleted. +void modelIsNotDeletedAssertor( + List> events) { + events.forEach((event) { + expect(event.element.deleted, isFalse); + }); +} + +/// A until function asserts cloud synced result of a model operation. +/// +/// It applies [rootModelOperator] to each of the [rootModels] then waits for +/// cloud sync event to be dispatched, and extract events satisfy +/// [expectedRootModelVersion] and then run [rootModelEventsAssertor] on each +/// of the extracted events. +/// +/// [associatedModels] can be provided to verify automated processes such as +/// cascade delete. [expectedAssociatedModelVersion] and +/// [associatedModelEventsAssertor] can be different from the ones set up for +/// the root models. +/// +/// [rootModelOperator] and [associatedModelOperator] can be passed with +/// [rootModelOperationPredicate] and [associatedModelOperationPredicate] +/// respectively. +Future testCloudSyncedModelOperation({ + required List rootModels, + required int expectedRootModelVersion, + required ModelOperator rootModelOperator, + required EventsAssertor rootModelEventsAssertor, + QueryPredicate? rootModelOperationPredicate, + List? associatedModels, + int? expectedAssociatedModelVersion, + ModelOperator? associatedModelOperator, + QueryPredicate? associatedModelOperationPredicate, + EventsAssertor? associatedModelEventsAssertor, +}) async { + final assertingAssociatedModels = associatedModels != null && + associatedModels.isNotEmpty && + expectedAssociatedModelVersion != null && + associatedModelEventsAssertor != null; + + var syncEventsGetters = rootModels + .map( + (rootModel) => getExpectedSubscriptionDataProcessedEvent( + eventMatcher: (event) { + var model = event.element.model; + + if (model is R) { + return model.getId() == rootModel.getId() && + event.element.version == expectedRootModelVersion; + } + + return false; + }, + ), + ) + .toList(); + + if (assertingAssociatedModels) { + var associatedModelsSyncedEventsGetters = associatedModels!.map( + (associatedModel) => getExpectedSubscriptionDataProcessedEvent( + eventMatcher: (event) { + var model = event.element.model; + + if (model is A) { + return model.getId() == associatedModel.getId() && + event.element.version == expectedRootModelVersion; + } + + return false; + }, + ), + ); + + syncEventsGetters.addAll(associatedModelsSyncedEventsGetters); + } + + for (var rootModel in rootModels) { + await rootModelOperator(rootModel); + } + + if (associatedModelOperator != null) { + for (var associatedModel in associatedModels!) { + await associatedModelOperator(associatedModel); + } + } + + var syncedEvents = await Future.wait(syncEventsGetters); + + rootModelEventsAssertor(syncedEvents.sublist(0, rootModels.length)); + + if (associatedModelEventsAssertor != null) { + associatedModelEventsAssertor(syncedEvents.sublist(rootModels.length)); + } +} + +/// Create am async getter to capture [take] of observed events on [modelType] +/// with desired [eventType]. +Future>> createObservedEventsGetter( + ModelType modelType, { + required int take, + required EventType eventType, +}) => + Amplify.DataStore.observe(modelType) + .where((event) => event.eventType == eventType) + .distinct((prev, next) => + prev.eventType == next.eventType && + prev.item.getId() == next.item.getId()) + .take(take) + .toList(); + +/// A util function runs [testWidgets] to validate [modelSpecs] specified +/// models are not in local storage. +void expectModelsNotToBeInLocalStorage( + List>> modelSpecs) { + testWidgets('testing models are not in local storage', + (WidgetTester tester) async { + for (var modelSpec in modelSpecs) { + var modelsInLocalStorage = Amplify.DataStore.query(modelSpec.item1); + expect(modelsInLocalStorage, isNot(containsAll(modelSpec.item2))); + } + }); +} + +/// A util function valid each of the [events] contained model is matching the +/// model contained in [referenceModels]. +void expectObservedEventsToMatchModels({ + required List> events, + required List referenceModels, +}) async { + for (var i = 0; i < referenceModels.length; i++) { + var event = events[i]; + var syncedRootModel = event.item; + var rootModel = referenceModels[i]; + expect(syncedRootModel, rootModel); + } +} + +/// A until function runs [testWidgets] to validate model operations on related +/// [rootModelType] and [associatedModelType]. +/// +/// This function runs save and delete operations on each of the +/// [rootModels]. +/// +/// This function runs save operation on each of the [associatedModels], and +/// delete operation if [supportCascadeDelete] is set as `false`. +/// +/// This function runs tests with cloud sync enabled when [enableCloudSync] is +/// set as `true`. +void testRootAndAssociatedModelsRelationship({ + required ModelProviderInterface modelProvider, + required ModelType rootModelType, + required List rootModels, + required ModelType associatedModelType, + required List associatedModels, + bool enableCloudSync = false, + bool supportCascadeDelete = false, +}) { + late Future>> observedRootModelsEvents; + late Future>> observedAssociatedModelsEvents; + + setUpAll(() async { + await configureDataStore( + enableCloudSync: enableCloudSync, + modelProvider: modelProvider, + ); + + observedRootModelsEvents = createObservedEventsGetter( + rootModelType, + eventType: EventType.create, + take: rootModels.length, + ); + + observedAssociatedModelsEvents = createObservedEventsGetter( + associatedModelType, + eventType: EventType.create, + take: associatedModels.length, + ); + }); + + expectModelsNotToBeInLocalStorage([ + Tuple2(rootModelType, rootModels), + Tuple2(associatedModelType, associatedModels), + ]); + + testWidgets('save root models', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: rootModels, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var rootModel in rootModels) { + await Amplify.DataStore.save(rootModel); + } + } + + var savedRootModels = await Amplify.DataStore.query(rootModelType); + expect(savedRootModels, containsAll(rootModels)); + }); + + testWidgets('save associated models', (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: associatedModels, + expectedRootModelVersion: 1, + rootModelOperator: Amplify.DataStore.save, + rootModelEventsAssertor: modelIsNotDeletedAssertor, + ); + } else { + for (var associatedModel in associatedModels) { + await Amplify.DataStore.save(associatedModel); + } + } + + var savedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(savedAssociatedModels, containsAll(associatedModels)); + }); + + testWidgets('observed root models creation events', + (WidgetTester tester) async { + var events = await observedRootModelsEvents; + expectObservedEventsToMatchModels( + events: events, referenceModels: rootModels); + }); + + testWidgets('observed associated models creation events', + (WidgetTester tester) async { + var events = await observedAssociatedModelsEvents; + expectObservedEventsToMatchModels( + events: events, referenceModels: associatedModels); + }); + + testWidgets( + 'delete root models${supportCascadeDelete ? ' (cascade delete associated models)' : ''}', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: rootModels, + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + associatedModels: supportCascadeDelete ? associatedModels : null, + expectedAssociatedModelVersion: supportCascadeDelete ? 2 : null, + associatedModelEventsAssertor: + supportCascadeDelete ? modelIsDeletedAssertor : null, + ); + } else { + for (var rootModel in rootModels) { + await Amplify.DataStore.delete(rootModel); + } + } + var queriedRootModels = await Amplify.DataStore.query(rootModelType); + expect(queriedRootModels, isNot(containsAll(rootModels))); + + if (supportCascadeDelete) { + var queriedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(queriedAssociatedModels, isNot(containsAll(associatedModels))); + } + }); + + if (!supportCascadeDelete) { + testWidgets( + 'delete associated models separately as cascade delete is not support with this relationship', + (WidgetTester tester) async { + if (enableCloudSync) { + await testCloudSyncedModelOperation( + rootModels: associatedModels, + expectedRootModelVersion: 2, + rootModelOperator: Amplify.DataStore.delete, + rootModelEventsAssertor: modelIsDeletedAssertor, + ); + } else { + for (var associatedModel in associatedModels) { + await Amplify.DataStore.delete(associatedModel); + } + } + + var queriedAssociatedModels = + await Amplify.DataStore.query(associatedModelType); + expect(queriedAssociatedModels, isNot(containsAll(associatedModels))); + }); + } +} diff --git a/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart b/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart new file mode 100644 index 0000000000..444f9357be --- /dev/null +++ b/packages/amplify_datastore/example/integration_test/utils/wait_for_expected_event_from_hub.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; + +const DEFAULT_TIMEOUT = const Duration(seconds: 20); + +class WaitForExpectedEventFromHub { + final Completer _completer = Completer(); + late StreamSubscription hubSubscription; + final Function eventMatcher; + final String eventName; + Duration timeout; + + WaitForExpectedEventFromHub({ + required this.eventMatcher, + required this.eventName, + Duration timeout = DEFAULT_TIMEOUT, + }) : this.timeout = timeout; + + Future start() { + hubSubscription = Amplify.Hub.listen([HubChannel.DataStore], (event) { + if (event.eventName == this.eventName) { + if (this.eventMatcher(event.payload)) { + hubSubscription.cancel(); + _completer.complete(event.payload as T); + } + } + }); + + return _completer.future.timeout(timeout); + } +} + +Future + getExpectedSubscriptionDataProcessedEvent({ + required bool Function(SubscriptionDataProcessedEvent) eventMatcher, +}) async { + var getter = WaitForExpectedEventFromHub( + eventName: 'subscriptionDataProcessed', + eventMatcher: (HubEventPayload eventPayload) { + if (eventPayload is SubscriptionDataProcessedEvent) { + return eventMatcher(eventPayload); + } + + return false; + }, + ); + return getter.start(); +} diff --git a/packages/amplify_datastore/example/pubspec.yaml b/packages/amplify_datastore/example/pubspec.yaml index 625e5cb004..335e67b8e1 100644 --- a/packages/amplify_datastore/example/pubspec.yaml +++ b/packages/amplify_datastore/example/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: path: ../../amplify_test flutter_test: sdk: flutter + tuple: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/amplify_datastore/example/tool/add_api_request.json b/packages/amplify_datastore/example/tool/add_api_request.json index cedcb66c37..bac4c4e153 100644 --- a/packages/amplify_datastore/example/tool/add_api_request.json +++ b/packages/amplify_datastore/example/tool/add_api_request.json @@ -4,9 +4,14 @@ "serviceName": "AppSync", "apiName": "dataStoreIntegrationTestGraphQL", "transformSchema": "", - "defaultAuthType": { + "defaultAuthType": { "mode": "API_KEY", "expirationTime": 365 + }, + "conflictResolution": { + "defaultResolutionStrategy": { + "type": "AUTOMERGE" + } } } } diff --git a/packages/amplify_datastore/example/tool/schema.graphql b/packages/amplify_datastore/example/tool/schema.graphql index 458138b560..ba45f73790 100644 --- a/packages/amplify_datastore/example/tool/schema.graphql +++ b/packages/amplify_datastore/example/tool/schema.graphql @@ -12,7 +12,7 @@ type Post @model { title: String! rating: Int! created: AWSDateTime - blogID: ID! @index(name: "byBlog") + blogID: ID @index(name: "byBlog") blog: Blog @belongsTo(fields: ["blogID"]) comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) tags: [Tag] @manyToMany(relationName: "PostTags") diff --git a/packages/amplify_datastore/lib/method_channel_datastore.dart b/packages/amplify_datastore/lib/method_channel_datastore.dart index 0988d49579..785c7cf1cc 100644 --- a/packages/amplify_datastore/lib/method_channel_datastore.dart +++ b/packages/amplify_datastore/lib/method_channel_datastore.dart @@ -16,7 +16,6 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:flutter/services.dart'; -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; import 'types/observe_query_executor.dart';