diff --git a/Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift b/Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift index c054da45dd..5549df30e4 100644 --- a/Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift +++ b/Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift @@ -11,6 +11,8 @@ public protocol LazyModelMarker { associatedtype Element: Model var element: Element? { get } + + var identifiers: [String: String]? { get } } public struct AnyModelProvider: ModelProvider { @@ -41,6 +43,6 @@ public protocol ModelProvider { } public enum ModelProviderState { - case notLoaded(identifiers: [String: String]) + case notLoaded(identifiers: [String: String]?) case loaded(Element?) } diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift index b9019598f1..bbcb4a5fcf 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift @@ -225,6 +225,15 @@ extension ModelField { } return true } + + public var isBelongsToOrHasOne: Bool { + switch association { + case .belongsTo, .hasOne: + return true + case .hasMany, .none: + return false + } + } /// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used /// directly by host applications. The behavior of this may change without warning. Though it is not used by host diff --git a/Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift b/Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift index 57bf07365e..6418ea4f8b 100644 --- a/Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift +++ b/Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift @@ -10,18 +10,31 @@ import Foundation // MARK: - DefaultModelProvider public struct DefaultModelProvider: ModelProvider { - - let element: Element? + enum LoadedState { + case notLoaded(identifiers: [String: String]?) + case loaded(model: Element?) + } + + var loadedState: LoadedState + public init(element: Element? = nil) { - self.element = element + self.loadedState = .loaded(model: element) + } + + public init(identifiers: [String: String]?) { + self.loadedState = .notLoaded(identifiers: identifiers) } public func load() async throws -> Element? { - return element + return Fatal.preconditionFailure("DefaultModelProvider does not provide loading capabilities") } public func getState() -> ModelProviderState { - return .loaded(element) + switch loadedState { + case .notLoaded(let identifiers): + return .notLoaded(identifiers: identifiers) + case .loaded(let model): + return .loaded(model) + } } - } diff --git a/Amplify/Categories/DataStore/Model/Lazy/LazyModel.swift b/Amplify/Categories/DataStore/Model/Lazy/LazyModel.swift index 726dd0e4df..b312072052 100644 --- a/Amplify/Categories/DataStore/Model/Lazy/LazyModel.swift +++ b/Amplify/Categories/DataStore/Model/Lazy/LazyModel.swift @@ -12,7 +12,7 @@ public class LazyModel: Codable, LazyModelMarker { /// Represents the data state of the `LazyModel`. enum LoadedState { - case notLoaded(identifiers: [String: String]) + case notLoaded(identifiers: [String: String]?) case loaded(Element?) } var loadedState: LoadedState @@ -42,6 +42,17 @@ public class LazyModel: Codable, LazyModelMarker { } } + public var identifiers: [String: String]? { + get { + switch loadedState { + case .notLoaded(let identifiers): + return identifiers + case .loaded: + return nil + } + } + } + public init(modelProvider: AnyModelProvider) { self.modelProvider = modelProvider switch self.modelProvider.getState() { @@ -52,11 +63,16 @@ public class LazyModel: Codable, LazyModelMarker { } } - public convenience init(element: Element? = nil) { + public convenience init(element: Element?) { let modelProvider = DefaultModelProvider(element: element).eraseToAnyModelProvider() self.init(modelProvider: modelProvider) } + public convenience init(identifiers: [String: String]?) { + let modelProvider = DefaultModelProvider(identifiers: identifiers).eraseToAnyModelProvider() + self.init(modelProvider: modelProvider) + } + required convenience public init(from decoder: Decoder) throws { for modelDecoder in ModelProviderRegistry.decoders.get() { if modelDecoder.shouldDecode(modelType: Element.self, decoder: decoder) { @@ -70,7 +86,7 @@ public class LazyModel: Codable, LazyModelMarker { let element = try Element(from: decoder) self.init(element: element) } else { - self.init() + self.init(identifiers: nil) } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift index 65cae84403..3a42420f98 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift @@ -14,9 +14,11 @@ import Amplify public struct ModelDecorator: ModelBasedGraphQLDocumentDecorator { private let model: Model - - public init(model: Model) { + private let mutationType: GraphQLMutationType + + public init(model: Model, mutationType: GraphQLMutationType) { self.model = model + self.mutationType = mutationType } public func decorate(_ document: SingleDirectiveGraphQLDocument, @@ -27,7 +29,7 @@ public struct ModelDecorator: ModelBasedGraphQLDocumentDecorator { public func decorate(_ document: SingleDirectiveGraphQLDocument, modelSchema: ModelSchema) -> SingleDirectiveGraphQLDocument { var inputs = document.inputs - var graphQLInput = model.graphQLInputForMutation(modelSchema) + var graphQLInput = model.graphQLInputForMutation(modelSchema, mutationType: mutationType) if !modelSchema.authRules.isEmpty { modelSchema.authRules.forEach { authRule in diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift index e983dc184f..86698e9803 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift @@ -245,7 +245,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: modelSchema.name, operationType: .mutation) documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) - documentBuilder.add(decorator: ModelDecorator(model: model)) + documentBuilder.add(decorator: ModelDecorator(model: model, mutationType: type)) if let filter = filter { documentBuilder.add(decorator: FilterDecorator(filter: filter)) } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift index c916046a7c..3d6b294a71 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift @@ -165,7 +165,7 @@ extension GraphQLRequest: ModelGraphQLRequestFactory { switch type { case .create: - documentBuilder.add(decorator: ModelDecorator(model: model)) + documentBuilder.add(decorator: ModelDecorator(model: model, mutationType: type)) case .delete: documentBuilder.add(decorator: ModelIdDecorator(model: model, schema: modelSchema)) @@ -173,7 +173,7 @@ extension GraphQLRequest: ModelGraphQLRequestFactory { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelSchema))) } case .update: - documentBuilder.add(decorator: ModelDecorator(model: model)) + documentBuilder.add(decorator: ModelDecorator(model: model, mutationType: type)) if let predicate = predicate { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelSchema))) } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift index 61c4ec47a7..02deafa08c 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift @@ -31,7 +31,7 @@ extension Model { /// Returns the input used for mutations /// - Parameter modelSchema: model's schema /// - Returns: A key-value map of the GraphQL mutation input - func graphQLInputForMutation(_ modelSchema: ModelSchema) -> GraphQLInput { + func graphQLInputForMutation(_ modelSchema: ModelSchema, mutationType: GraphQLMutationType) -> GraphQLInput { var input: GraphQLInput = [:] // filter existing non-readonly fields @@ -43,7 +43,7 @@ extension Model { guard let value = modelFieldValue else { // Special case for associated models when the value is `nil`, by setting all of the associated // model's primary key fields (targetNames) to `nil`. - if case .model = modelField.type { + if case .model = modelField.type { // add it for "belongs-to" let fieldNames = getFieldNameForAssociatedModels(modelField: modelField) for fieldName in fieldNames { // Only set to `nil` if it has not been set already. For hasOne relationships, where the @@ -59,6 +59,8 @@ extension Model { input.updateValue(nil, forKey: fieldName) } } + } else if case .collection = modelField.type { // skip all "has-many" + continue } else { input.updateValue(nil, forKey: name) } @@ -82,9 +84,10 @@ extension Model { // get the associated model target names and their values let associatedModelIds = associatedModelIdentifierFields(fromModelValue: value, field: modelField, - associatedModelName: associateModelName) + associatedModelName: associateModelName, + mutationType: mutationType) for (fieldName, fieldValue) in associatedModelIds { - input[fieldName] = fieldValue + input.updateValue(fieldValue, forKey: fieldName) } case .embedded, .embeddedCollection: if let encodable = value as? Encodable { @@ -152,22 +155,28 @@ extension Model { /// and `value` its value in the associated model private func associatedModelIdentifierFields(fromModelValue value: Any, field: ModelField, - associatedModelName: String) -> [(String, Persistable)] { + associatedModelName: String, + mutationType: GraphQLMutationType) -> [(String, Persistable?)] { guard let associateModelSchema = ModelRegistry.modelSchema(from: associatedModelName) else { preconditionFailure("Associated model \(associatedModelName) not found.") } let fieldNames = getFieldNameForAssociatedModels(modelField: field) - let values = getModelIdentifierValues(from: value, modelSchema: associateModelSchema) + var values = getModelIdentifierValues(from: value, modelSchema: associateModelSchema) - // if the field is required, the associated field keys and values should match - if fieldNames.count != values.count, field.isRequired { - preconditionFailure( + if fieldNames.count != values.count { + // if the field is required, the associated field keys and values should match + if field.isRequired { + preconditionFailure( """ Associated model target names and values for field \(field.name) of model \(modelName) mismatch. There is a possibility that is an issue with the generated models. """ - ) + ) + } else if mutationType == .update { + // otherwise, pad the values with `nil` to account for removals of associations on updates. + values = [Persistable?](repeating: nil, count: fieldNames.count) + } } return Array(zip(fieldNames, values)) @@ -179,7 +188,7 @@ extension Model { /// - value: model value /// - modelSchema: model's schema /// - Returns: array of values of its primary key - private func getModelIdentifierValues(from value: Any, modelSchema: ModelSchema) -> [Persistable] { + private func getModelIdentifierValues(from value: Any, modelSchema: ModelSchema) -> [Persistable?] { if let modelValue = value as? Model { return modelValue.identifier(schema: modelSchema).values } else if let optionalModel = value as? Model?, diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift index 38cf7f44a2..9f39a859dd 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift @@ -49,7 +49,7 @@ extension ModelSchema { /// The list of fields formatted for GraphQL usage. var graphQLFields: [ModelField] { sortedFields.filter { field in - !field.hasAssociation || field.isAssociationOwner + !field.hasAssociation || field.isBelongsToOrHasOne } } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift index 3afe5020b9..81e7a72fd0 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift @@ -34,18 +34,25 @@ extension SelectionSet { withModelFields(fields) } - func withModelFields(_ fields: [ModelField]) { + func withModelFields(_ fields: [ModelField], recursive: Bool = true) { fields.forEach { field in if field.isEmbeddedType, let embeddedTypeSchema = field.embeddedTypeSchema { let child = SelectionSet(value: .init(name: field.name, fieldType: .embedded)) child.withEmbeddableFields(embeddedTypeSchema.sortedFields) self.addChild(settingParentOf: child) - } else if field.isAssociationOwner, - let associatedModelName = field.associatedModelName, - let schema = ModelRegistry.modelSchema(from: associatedModelName) { - let child = SelectionSet(value: .init(name: field.name, fieldType: .model)) - child.withModelFields(schema.graphQLFields) - self.addChild(settingParentOf: child) + } else if field.isBelongsToOrHasOne, + let associatedModelName = field.associatedModelName, + let schema = ModelRegistry.modelSchema(from: associatedModelName) { + if recursive { + var recursive = recursive + if field.isBelongsToOrHasOne { + recursive = false + } + + let child = SelectionSet(value: .init(name: field.name, fieldType: .model)) + child.withModelFields(schema.graphQLFields, recursive: recursive) + self.addChild(settingParentOf: child) + } } else { self.addChild(settingParentOf: .init(value: .init(name: field.graphQLName, fieldType: .value))) } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelDecoder.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelDecoder.swift index d7cdbf8fe6..a3ed674cd4 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelDecoder.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelDecoder.swift @@ -54,5 +54,5 @@ public struct DataStoreModelDecoder: ModelProviderDecoder { /// Metadata that contains the primary keys and values of a model public struct DataStoreModelIdentifierMetadata: Codable { - let identifier: String + let identifier: String? } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelProvider.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelProvider.swift index 7769279f01..09a870b6ff 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelProvider.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreModelProvider.swift @@ -12,22 +12,26 @@ import Combine public class DataStoreModelProvider: ModelProvider { enum LoadedState { - case notLoaded(identifiers: [String: String]) + case notLoaded(identifiers: [String: String]?) case loaded(model: ModelType?) } var loadedState: LoadedState convenience init(metadata: DataStoreModelIdentifierMetadata) { + if let identifier = metadata.identifier { + self.init(identifiers: [ModelType.schema.primaryKey.sqlName: identifier]) + } else { + self.init(identifiers: nil) + } - self.init(identifiers: [ModelType.schema.primaryKey.sqlName: metadata.identifier]) } init(model: ModelType?) { self.loadedState = .loaded(model: model) } - init(identifiers: [String: String]) { + init(identifiers: [String: String]?) { self.loadedState = .notLoaded(identifiers: identifiers) } @@ -36,7 +40,7 @@ public class DataStoreModelProvider: ModelProvider { public func load() async throws -> ModelType? { switch loadedState { case .notLoaded(let identifiers): - guard let identifier = identifiers.first else { + guard let identifiers = identifiers, let identifier = identifiers.first else { return nil } let queryPredicate: QueryPredicate = field(identifier.key).eq(identifier.value) diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Model+SQLite.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Model+SQLite.swift index 53a5cd886f..16c3f848a5 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Model+SQLite.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Model+SQLite.swift @@ -113,7 +113,9 @@ extension Model { if let associatedModelValue = value as? Model { return associatedModelValue.identifier } else if let associatedLazyModel = value as? (any LazyModelMarker) { - return associatedLazyModel.element?.identifier + // The identifier (sometimes the FK), comes from the loaded model's identifier or + // from the not loaded identifier's first and only value + return associatedLazyModel.element?.identifier ?? associatedLazyModel.identifiers?.first?.value } else if let associatedModelJSON = value as? [String: JSONValue] { return associatedPrimaryKeyValue(fromJSON: associatedModelJSON, associatedModelSchema: associatedModelSchema) diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Statement+Model.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Statement+Model.swift index 60f7baf00e..76271d3688 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Statement+Model.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/Statement+Model.swift @@ -102,7 +102,6 @@ extension Statement: StatementModelConvertible { from: value, fieldType: field.type ) - // Check if the value for the primary key is `nil`. This is when an associated model does not exist. // To create a decodable `modelDictionary` that can be decoded to the Model types, the entire // object at this particular key should be set to `nil`. The following code does that by dropping the last @@ -135,7 +134,10 @@ extension Statement: StatementModelConvertible { // For example, when the value is the `id` of the Blog, then the field.isPrimaryKey is satisfied. // Every association of the Blog, such as the has-many Post is populated with the List with // associatedId == blog's id. This way, the list of post can be lazily loaded later using the associated id. - if let id = modelValue as? String, field.isPrimaryKey { + if let id = modelValue as? String, + (field.name == ModelIdentifierFormat.Custom.sqlColumnName || // this is the `@@primaryKey` (CPK) + (schema.primaryKey.fields.count == 1 // or there's only one primary key (not composite key) + && schema.primaryKey.indexOfField(named: field.name) != nil)) { // and this field is the primary key let associations = schema.fields.values.filter { $0.isArray && $0.hasAssociation } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index ac9308a492..fa8310af4f 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -355,7 +355,8 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { data: outboxMutationProcessedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payload) } catch { - log.error("\(#function) Couldn't decode local model as \(mutationEvent.modelName)") + log.error("\(#function) Couldn't decode local model as \(mutationEvent.modelName) \(error)") + log.error("\(#function) Couldn't decode from \(mutationEvent.json)") return } } @@ -370,7 +371,8 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { data: outboxMutationEnqueuedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payload) } catch { - log.error("\(#function) Couldn't decode local model as \(mutationEvent.modelName)") + log.error("\(#function) Couldn't decode local model as \(mutationEvent.modelName) \(error)") + log.error("\(#function) Couldn't decode from \(mutationEvent.json)") return } } diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift new file mode 100644 index 0000000000..aa59441cf5 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/AWSDataStoreLazyLoadPostComment4V2Tests.swift @@ -0,0 +1,184 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +class AWSDataStoreLazyLoadPostComment4V2Tests: AWSDataStoreLazyLoadBaseTest { + + func testStart() async throws { + await setup(withModels: PostComment4V2Models(), eagerLoad: false, clearOnTearDown: false) + try await startAndWaitForReady() + printDBPath() + } + + func testLazyLoad() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await assertComment(savedComment, hasEagerLoaded: savedPost) + try await assertPost(savedPost, canLazyLoad: savedComment) + let queriedComment = try await query(for: savedComment) + try await assertComment(queriedComment, canLazyLoad: savedPost) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedComment) + } + + func assertComment(_ comment: Comment, + hasEagerLoaded post: Post) async throws { + assertLazyModel(comment._post, + state: .loaded(model: post)) + + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + + // retrieve loaded model + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the loaded post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertComment(_ comment: Comment, + canLazyLoad post: Post) async throws { + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["id": post.identifier])) + guard let loadedPost = try await comment.post else { + XCTFail("Failed to load the post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + assertLazyModel(comment._post, + state: .loaded(model: post)) + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertPost(_ post: Post, + canLazyLoad comment: Comment) async throws { + guard let comments = post.comments else { + XCTFail("Missing comments on post") + return + } + assertList(comments, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "post")) + + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + guard let comment = comments.first else { + XCTFail("Missing lazy loaded comment from post") + return + } + + // further nested models should not be loaded + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["id": post.identifier])) + } + + func testSaveWithoutPost() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + let comment = Comment(content: "content") + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: nil)) + let post = Post(title: "title") + let savedPost = try await saveAndWaitForSync(post) + queriedComment.setPost(savedPost) + let saveCommentWithPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithPost) + try await assertComment(queriedComment2, canLazyLoad: post) + } + + func testUpdateFromQueriedComment() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + let queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["id": post.identifier])) + let savedQueriedComment = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: savedQueriedComment) + try await assertComment(queriedComment2, canLazyLoad: savedPost) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["id": post.identifier])) + + let newPost = Post(title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedComment.setPost(newPost) + let saveCommentWithNewPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithNewPost) + try await assertComment(queriedComment2, canLazyLoad: newPost) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["id": post.identifier])) + + queriedComment.setPost(nil) + let saveCommentRemovePost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedCommentNoPost = try await query(for: saveCommentRemovePost) + assertLazyModel(queriedCommentNoPost._post, + state: .notLoaded(identifiers: nil)) + } + + func testDelete() async throws { + await setup(withModels: PostComment4V2Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await deleteAndWaitForSync(savedPost) + try await assertModelDoesNotExist(savedComment) + try await assertModelDoesNotExist(savedPost) + } +} + +extension AWSDataStoreLazyLoadPostComment4V2Tests { + typealias Post = Post4V2 + typealias Comment = Comment4V2 + + struct PostComment4V2Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Post4V2.self) + ModelRegistry.register(modelType: Comment4V2.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2+Schema.swift new file mode 100644 index 0000000000..600509e366 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2+Schema.swift @@ -0,0 +1,45 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Comment4V2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case post + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let comment4V2 = Comment4V2.keys + + model.authRules = [ + rule(allow: .public, operations: [.create, .update, .delete, .read]) + ] + + model.pluralName = "Comment4V2s" + + model.attributes( + .index(fields: ["postID", "content"], name: "byPost4"), + .primaryKey(fields: [comment4V2.id]) + ) + + model.fields( + .field(comment4V2.id, is: .required, ofType: .string), + .field(comment4V2.content, is: .required, ofType: .string), + .belongsTo(comment4V2.post, is: .optional, ofType: Post4V2.self, targetNames: ["postID"]), + .field(comment4V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment4V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Comment4V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2.swift new file mode 100644 index 0000000000..fd61e8a54d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Comment4V2.swift @@ -0,0 +1,64 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment4V2: Model { + public let id: String + public var content: String + internal var _post: LazyModel + public var post: Post4V2? { + get async throws { + try await _post.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String, + post: Post4V2? = nil) { + self.init(id: id, + content: content, + post: post, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String, + post: Post4V2? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self._post = LazyModel(element: post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setPost(_ post: Post4V2?) { + self._post = LazyModel(element: post) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + id = try values.decode(String.self, forKey: .id) + content = try values.decode(String.self, forKey: .content) + do { + _post = try values.decode(LazyModel.self, forKey: .post) + } catch { + _post = LazyModel(identifiers: nil) + } + + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(content, forKey: .content) + try container.encode(_post, forKey: .post) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2+Schema.swift new file mode 100644 index 0000000000..e8868bdb5d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2+Schema.swift @@ -0,0 +1,44 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Post4V2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case title + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let post4V2 = Post4V2.keys + + model.authRules = [ + rule(allow: .public, operations: [.create, .update, .delete, .read]) + ] + + model.pluralName = "Post4V2s" + + model.attributes( + .primaryKey(fields: [post4V2.id]) + ) + + model.fields( + .field(post4V2.id, is: .required, ofType: .string), + .field(post4V2.title, is: .required, ofType: .string), + .hasMany(post4V2.comments, is: .optional, ofType: Comment4V2.self, associatedWith: Comment4V2.keys.post), + .field(post4V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post4V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Post4V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2.swift new file mode 100644 index 0000000000..08f3e4b429 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL1/Post4V2.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Post4V2: Model { + public let id: String + public var title: String + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + title: String, + comments: List? = []) { + self.init(id: id, + title: title, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + title: String, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.title = title + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift new file mode 100644 index 0000000000..0bb1b4653e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/AWSDataStoreLazyLoadPostComment7Tests.swift @@ -0,0 +1,175 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +final class AWSDataStoreLazyLoadPostComment7Tests: AWSDataStoreLazyLoadBaseTest { + + func testLazyLoad() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await assertComment(savedComment, hasEagerLoaded: savedPost) + try await assertPost(savedPost, canLazyLoad: savedComment) + let queriedComment = try await query(for: savedComment) + try await assertComment(queriedComment, canLazyLoad: savedPost) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedComment) + } + + func assertComment(_ comment: Comment, + hasEagerLoaded post: Post) async throws { + assertLazyModel(comment._post, + state: .loaded(model: post)) + + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the post from the comment") + return + } + XCTAssertEqual(loadedPost.postId, post.postId) + + // retrieve loaded model + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the loaded post from the comment") + return + } + XCTAssertEqual(loadedPost.postId, post.postId) + + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertComment(_ comment: Comment, + canLazyLoad post: Post) async throws { + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + guard let loadedPost = try await comment.post else { + XCTFail("Failed to load the post from the comment") + return + } + XCTAssertEqual(loadedPost.postId, post.postId) + assertLazyModel(comment._post, + state: .loaded(model: post)) + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertPost(_ post: Post, + canLazyLoad comment: Comment) async throws { + guard let comments = post.comments else { + XCTFail("Missing comments on post") + return + } + assertList(comments, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "post")) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + guard let comment = comments.first else { + XCTFail("Missing lazy loaded comment from post") + return + } + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + } + + func testSaveWithoutPost() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + let comment = Comment(commentId: UUID().uuidString, content: "content") + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: nil)) + let post = Post(postId: UUID().uuidString, title: "title") + let savedPost = try await saveAndWaitForSync(post) + queriedComment.setPost(savedPost) + let saveCommentWithPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithPost) + try await assertComment(queriedComment2, canLazyLoad: post) + } + + func testUpdateFromQueriedComment() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + let queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + let savedQueriedComment = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: savedQueriedComment) + try await assertComment(queriedComment2, canLazyLoad: savedPost) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + + let newPost = Post(postId: UUID().uuidString, title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedComment.setPost(newPost) + let saveCommentWithNewPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithNewPost) + try await assertComment(queriedComment2, canLazyLoad: newPost) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + + queriedComment.setPost(nil) + let saveCommentRemovePost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedCommentNoPost = try await query(for: saveCommentRemovePost) + assertLazyModel(queriedCommentNoPost._post, + state: .notLoaded(identifiers: nil)) + } + + func testDelete() async throws { + await setup(withModels: PostComment7Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await deleteAndWaitForSync(savedPost) + try await assertModelDoesNotExist(savedComment) + try await assertModelDoesNotExist(savedPost) + } +} + +extension AWSDataStoreLazyLoadPostComment7Tests { + typealias Post = Post7 + typealias Comment = Comment7 + + struct PostComment7Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Post7.self) + ModelRegistry.register(modelType: Comment7.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7+Schema.swift new file mode 100644 index 0000000000..7f15cca8e7 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7+Schema.swift @@ -0,0 +1,49 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Comment7 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case commentId + case content + case post + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let comment7 = Comment7.keys + + model.pluralName = "Comment7s" + + model.attributes( + .index(fields: ["commentId", "content"], name: nil), + .index(fields: ["postId", "postTitle"], name: "byPost"), + .primaryKey(fields: [comment7.commentId, comment7.content]) + ) + + model.fields( + .field(comment7.commentId, is: .required, ofType: .string), + .field(comment7.content, is: .required, ofType: .string), + .belongsTo(comment7.post, is: .optional, ofType: Post7.self, targetNames: ["postId", "postTitle"]), + .field(comment7.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment7.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Comment7: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Comment7.IdentifierProtocol { + public static func identifier(commentId: String, + content: String) -> Self { + .make(fields:[(name: "commentId", value: commentId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7.swift new file mode 100644 index 0000000000..5dbc7294de --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Comment7.swift @@ -0,0 +1,59 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment7: Model { + public let commentId: String + public let content: String + internal var _post: LazyModel + public var post: Post7? { + get async throws { + try await _post.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(commentId: String, + content: String, + post: Post7? = nil) { + self.init(commentId: commentId, + content: content, + post: post, + createdAt: nil, + updatedAt: nil) + } + internal init(commentId: String, + content: String, + post: Post7? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.commentId = commentId + self.content = content + self._post = LazyModel(element: post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setPost(_ post: Post7?) { + self._post = LazyModel(element: post) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + commentId = try values.decode(String.self, forKey: .commentId) + content = try values.decode(String.self, forKey: .content) + _post = try values.decode(LazyModel.self, forKey: .post) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(commentId, forKey: .commentId) + try container.encode(content, forKey: .content) + try container.encode(_post, forKey: .post) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7+Schema.swift new file mode 100644 index 0000000000..7700f9207a --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Post7 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case postId + case title + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let post7 = Post7.keys + + model.pluralName = "Post7s" + + model.attributes( + .index(fields: ["postId", "title"], name: nil), + .primaryKey(fields: [post7.postId, post7.title]) + ) + + model.fields( + .field(post7.postId, is: .required, ofType: .string), + .field(post7.title, is: .required, ofType: .string), + .hasMany(post7.comments, is: .optional, ofType: Comment7.self, associatedWith: Comment7.keys.post), + .field(post7.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post7.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Post7: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Post7.IdentifierProtocol { + public static func identifier(postId: String, + title: String) -> Self { + .make(fields:[(name: "postId", value: postId), (name: "title", value: title)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7.swift new file mode 100644 index 0000000000..8cb750a96d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL10/Post7.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Post7: Model { + public let postId: String + public let title: String + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(postId: String, + title: String, + comments: List? = []) { + self.init(postId: postId, + title: title, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(postId: String, + title: String, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.postId = postId + self.title = title + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift new file mode 100644 index 0000000000..4773cb9e5b --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/AWSDataStoreLazyLoadPostComment8Tests.swift @@ -0,0 +1,185 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +final class AWSDataStoreLazyLoadPostComment8Tests: AWSDataStoreLazyLoadBaseTest { + + func testLazyLoad() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + postId: post.postId, + postTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + assertComment(savedComment, contains: savedPost) + try await assertPost(savedPost, canLazyLoad: savedComment) + let queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: savedPost) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedComment) + } + + func assertComment(_ comment: Comment, contains post: Post) { + XCTAssertEqual(comment.postId, post.postId) + XCTAssertEqual(comment.postTitle, post.title) + } + + func assertCommentDoesNotContainPost(_ comment: Comment) { + XCTAssertNil(comment.postId) + XCTAssertNil(comment.postTitle) + } + + func assertPost(_ post: Post, + canLazyLoad comment: Comment) async throws { + guard let comments = post.comments else { + XCTFail("Missing comments on post") + return + } + // This is a bit off, the post.identifier is the CPK while the associated field is just "postId", + // Loading the comments by the post identifier should be + // "query all comments where the predicate is field("@@postForeignKey") == "[postId]#[title]" + // List fetching is broken for this use case "uni directional has-many" + assertList(comments, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "postId")) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + guard let comment = comments.first else { + XCTFail("Missing lazy loaded comment from post") + return + } + assertComment(comment, contains: post) + } + + /* + This test fails when the create mutation contains Null values for the foreign keys. On create mutation, we should + be able to detect that the values being set are foreign key fields and remove them from the input + { + "variables" : { + "input" : { + "content" : "content", + "postId" : null, + "commentId" : "532C8869-FD00-4694-A2DE-38223E080206", + "postTitle" : null + } + }, + "query" : "mutation CreateComment8($input: CreateComment8Input!) {\n createComment8(input: $input) {\n commentId\n content\n createdAt\n postId\n postTitle\n updatedAt\n __typename\n _version\n _deleted\n _lastChangedAt\n }\n}" + } + */ + func testSaveWithoutPost() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + let comment = Comment(commentId: UUID().uuidString, content: "content") + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertCommentDoesNotContainPost(queriedComment) + let post = Post(postId: UUID().uuidString, title: "title") + let savedPost = try await saveAndWaitForSync(post) + queriedComment.postId = savedPost.postId + queriedComment.postTitle = savedPost.title + let saveCommentWithPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithPost) + assertComment(queriedComment2, contains: post) + } + + func testUpdateFromQueriedComment() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + postId: post.postId, + postTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + let queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + let savedQueriedComment = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: savedQueriedComment) + assertComment(queriedComment2, contains: savedPost) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + postId: post.postId, + postTitle: post.title) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + let newPost = Post(postId: UUID().uuidString, title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedComment.postId = newPost.postId + queriedComment.postTitle = newPost.title + let saveCommentWithNewPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithNewPost) + assertComment(queriedComment2, contains: newPost) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + postId: post.postId, + postTitle: post.title) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + + queriedComment.postId = nil + queriedComment.postTitle = nil + + let saveCommentRemovePost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedCommentNoPost = try await query(for: saveCommentRemovePost) + assertCommentDoesNotContainPost(queriedCommentNoPost) + } + + func testDelete() async throws { + await setup(withModels: PostComment8Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + postId: post.postId, + postTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await deleteAndWaitForSync(savedPost) + + // The expected behavior when deleting a post should be that the + // child models are deleted (comment) followed by the parent model (post). + try await assertModelDoesNotExist(savedPost) + // Is there a way to delete the children models in uni directional relationships? + try await assertModelExists(savedComment) + } +} + +extension AWSDataStoreLazyLoadPostComment8Tests { + typealias Post = Post8 + typealias Comment = Comment8 + + struct PostComment8Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Post8.self) + ModelRegistry.register(modelType: Comment8.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8+Schema.swift new file mode 100644 index 0000000000..7bec3d9989 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8+Schema.swift @@ -0,0 +1,51 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Comment8 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case commentId + case content + case postId + case postTitle + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let comment8 = Comment8.keys + + model.pluralName = "Comment8s" + + model.attributes( + .index(fields: ["commentId", "content"], name: nil), + .index(fields: ["postId", "postTitle"], name: "byPost"), + .primaryKey(fields: [comment8.commentId, comment8.content]) + ) + + model.fields( + .field(comment8.commentId, is: .required, ofType: .string), + .field(comment8.content, is: .required, ofType: .string), + .field(comment8.postId, is: .optional, ofType: .string), + .field(comment8.postTitle, is: .optional, ofType: .string), + .field(comment8.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment8.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Comment8: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Comment8.IdentifierProtocol { + public static func identifier(commentId: String, + content: String) -> Self { + .make(fields:[(name: "commentId", value: commentId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8.swift new file mode 100644 index 0000000000..22fcd26a4b --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Comment8.swift @@ -0,0 +1,37 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment8: Model { + public let commentId: String + public let content: String + public var postId: String? + public var postTitle: String? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(commentId: String, + content: String, + postId: String? = nil, + postTitle: String? = nil) { + self.init(commentId: commentId, + content: content, + postId: postId, + postTitle: postTitle, + createdAt: nil, + updatedAt: nil) + } + internal init(commentId: String, + content: String, + postId: String? = nil, + postTitle: String? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.commentId = commentId + self.content = content + self.postId = postId + self.postTitle = postTitle + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8+Schema.swift new file mode 100644 index 0000000000..520236fa1e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Post8 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case postId + case title + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let post8 = Post8.keys + + model.pluralName = "Post8s" + + model.attributes( + .index(fields: ["postId", "title"], name: nil), + .primaryKey(fields: [post8.postId, post8.title]) + ) + + model.fields( + .field(post8.postId, is: .required, ofType: .string), + .field(post8.title, is: .required, ofType: .string), + .hasMany(post8.comments, is: .optional, ofType: Comment8.self, associatedWith: Comment8.keys.postId), + .field(post8.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post8.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Post8: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Post8.IdentifierProtocol { + public static func identifier(postId: String, + title: String) -> Self { + .make(fields:[(name: "postId", value: postId), (name: "title", value: title)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8.swift new file mode 100644 index 0000000000..440cd2b9d7 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL11/Post8.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Post8: Model { + public let postId: String + public let title: String + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(postId: String, + title: String, + comments: List? = []) { + self.init(postId: postId, + title: title, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(postId: String, + title: String, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.postId = postId + self.title = title + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo+Schema.swift new file mode 100644 index 0000000000..53befe5219 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo+Schema.swift @@ -0,0 +1,51 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension ChildSansBelongsTo { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case childId + case content + case compositePKParentChildrenSansBelongsToCustomId + case compositePKParentChildrenSansBelongsToContent + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let childSansBelongsTo = ChildSansBelongsTo.keys + + model.pluralName = "ChildSansBelongsTos" + + model.attributes( + .index(fields: ["childId", "content"], name: nil), + .index(fields: ["compositePKParentChildrenSansBelongsToCustomId", "compositePKParentChildrenSansBelongsToContent"], name: "byParent"), + .primaryKey(fields: [childSansBelongsTo.childId, childSansBelongsTo.content]) + ) + + model.fields( + .field(childSansBelongsTo.childId, is: .required, ofType: .string), + .field(childSansBelongsTo.content, is: .required, ofType: .string), + .field(childSansBelongsTo.compositePKParentChildrenSansBelongsToCustomId, is: .required, ofType: .string), + .field(childSansBelongsTo.compositePKParentChildrenSansBelongsToContent, is: .optional, ofType: .string), + .field(childSansBelongsTo.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(childSansBelongsTo.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension ChildSansBelongsTo: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension ChildSansBelongsTo.IdentifierProtocol { + public static func identifier(childId: String, + content: String) -> Self { + .make(fields:[(name: "childId", value: childId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo.swift new file mode 100644 index 0000000000..83d55435d1 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ChildSansBelongsTo.swift @@ -0,0 +1,37 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct ChildSansBelongsTo: Model { + public let childId: String + public let content: String + public var compositePKParentChildrenSansBelongsToCustomId: String + public var compositePKParentChildrenSansBelongsToContent: String? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(childId: String, + content: String, + compositePKParentChildrenSansBelongsToCustomId: String, + compositePKParentChildrenSansBelongsToContent: String? = nil) { + self.init(childId: childId, + content: content, + compositePKParentChildrenSansBelongsToCustomId: compositePKParentChildrenSansBelongsToCustomId, + compositePKParentChildrenSansBelongsToContent: compositePKParentChildrenSansBelongsToContent, + createdAt: nil, + updatedAt: nil) + } + internal init(childId: String, + content: String, + compositePKParentChildrenSansBelongsToCustomId: String, + compositePKParentChildrenSansBelongsToContent: String? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.childId = childId + self.content = content + self.compositePKParentChildrenSansBelongsToCustomId = compositePKParentChildrenSansBelongsToCustomId + self.compositePKParentChildrenSansBelongsToContent = compositePKParentChildrenSansBelongsToContent + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild+Schema.swift new file mode 100644 index 0000000000..ebef1cc4f4 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild+Schema.swift @@ -0,0 +1,49 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension CompositePKChild { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case childId + case content + case parent + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let compositePKChild = CompositePKChild.keys + + model.pluralName = "CompositePKChildren" + + model.attributes( + .index(fields: ["childId", "content"], name: nil), + .index(fields: ["parentId", "parentTitle"], name: "byParent"), + .primaryKey(fields: [compositePKChild.childId, compositePKChild.content]) + ) + + model.fields( + .field(compositePKChild.childId, is: .required, ofType: .string), + .field(compositePKChild.content, is: .required, ofType: .string), + .belongsTo(compositePKChild.parent, is: .optional, ofType: CompositePKParent.self, targetNames: ["parentId", "parentTitle"]), + .field(compositePKChild.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(compositePKChild.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension CompositePKChild: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension CompositePKChild.IdentifierProtocol { + public static func identifier(childId: String, + content: String) -> Self { + .make(fields:[(name: "childId", value: childId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild.swift new file mode 100644 index 0000000000..7e4d7a335e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKChild.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct CompositePKChild: Model { + public let childId: String + public let content: String + public var parent: CompositePKParent? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(childId: String, + content: String, + parent: CompositePKParent? = nil) { + self.init(childId: childId, + content: content, + parent: parent, + createdAt: nil, + updatedAt: nil) + } + internal init(childId: String, + content: String, + parent: CompositePKParent? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.childId = childId + self.content = content + self.parent = parent + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent+Schema.swift new file mode 100644 index 0000000000..238062f901 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent+Schema.swift @@ -0,0 +1,54 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension CompositePKParent { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case customId + case content + case children + case implicitChildren + case strangeChildren + case childrenSansBelongsTo + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let compositePKParent = CompositePKParent.keys + + model.pluralName = "CompositePKParents" + + model.attributes( + .index(fields: ["customId", "content"], name: nil), + .primaryKey(fields: [compositePKParent.customId, compositePKParent.content]) + ) + + model.fields( + .field(compositePKParent.customId, is: .required, ofType: .string), + .field(compositePKParent.content, is: .required, ofType: .string), + .hasMany(compositePKParent.children, is: .optional, ofType: CompositePKChild.self, associatedWith: CompositePKChild.keys.parent), + .hasMany(compositePKParent.implicitChildren, is: .optional, ofType: ImplicitChild.self, associatedWith: ImplicitChild.keys.parent), + .hasMany(compositePKParent.strangeChildren, is: .optional, ofType: StrangeExplicitChild.self, associatedWith: StrangeExplicitChild.keys.parent), + .hasMany(compositePKParent.childrenSansBelongsTo, is: .optional, ofType: ChildSansBelongsTo.self, associatedWith: ChildSansBelongsTo.keys.compositePKParentChildrenSansBelongsToCustomId), + .field(compositePKParent.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(compositePKParent.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension CompositePKParent: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension CompositePKParent.IdentifierProtocol { + public static func identifier(customId: String, + content: String) -> Self { + .make(fields:[(name: "customId", value: customId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent.swift new file mode 100644 index 0000000000..1893042a74 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/CompositePKParent.swift @@ -0,0 +1,47 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct CompositePKParent: Model { + public let customId: String + public let content: String + public var children: List? + public var implicitChildren: List? + public var strangeChildren: List? + public var childrenSansBelongsTo: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(customId: String, + content: String, + children: List? = [], + implicitChildren: List? = [], + strangeChildren: List? = [], + childrenSansBelongsTo: List? = []) { + self.init(customId: customId, + content: content, + children: children, + implicitChildren: implicitChildren, + strangeChildren: strangeChildren, + childrenSansBelongsTo: childrenSansBelongsTo, + createdAt: nil, + updatedAt: nil) + } + internal init(customId: String, + content: String, + children: List? = [], + implicitChildren: List? = [], + strangeChildren: List? = [], + childrenSansBelongsTo: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.customId = customId + self.content = content + self.children = children + self.implicitChildren = implicitChildren + self.strangeChildren = strangeChildren + self.childrenSansBelongsTo = childrenSansBelongsTo + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild+Schema.swift new file mode 100644 index 0000000000..e42b9b9f53 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild+Schema.swift @@ -0,0 +1,41 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension DefaultPKChild { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case parent + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let defaultPKChild = DefaultPKChild.keys + + model.pluralName = "DefaultPKChildren" + + model.attributes( + .index(fields: ["id"], name: nil), + .primaryKey(fields: [defaultPKChild.id]) + ) + + model.fields( + .field(defaultPKChild.id, is: .required, ofType: .string), + .field(defaultPKChild.content, is: .optional, ofType: .string), + .belongsTo(defaultPKChild.parent, is: .optional, ofType: DefaultPKParent.self, targetNames: ["defaultPKParentChildrenId"]), + .field(defaultPKChild.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(defaultPKChild.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension DefaultPKChild: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild.swift new file mode 100644 index 0000000000..6175242adf --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKChild.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct DefaultPKChild: Model { + public let id: String + public var content: String? + public var parent: DefaultPKParent? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String? = nil, + parent: DefaultPKParent? = nil) { + self.init(id: id, + content: content, + parent: parent, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String? = nil, + parent: DefaultPKParent? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self.parent = parent + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent+Schema.swift new file mode 100644 index 0000000000..b5e0fd9884 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent+Schema.swift @@ -0,0 +1,41 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension DefaultPKParent { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case children + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let defaultPKParent = DefaultPKParent.keys + + model.pluralName = "DefaultPKParents" + + model.attributes( + .index(fields: ["id"], name: nil), + .primaryKey(fields: [defaultPKParent.id]) + ) + + model.fields( + .field(defaultPKParent.id, is: .required, ofType: .string), + .field(defaultPKParent.content, is: .optional, ofType: .string), + .hasMany(defaultPKParent.children, is: .optional, ofType: DefaultPKChild.self, associatedWith: DefaultPKChild.keys.parent), + .field(defaultPKParent.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(defaultPKParent.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension DefaultPKParent: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent.swift new file mode 100644 index 0000000000..532bbb01ff --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/DefaultPKParent.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct DefaultPKParent: Model { + public let id: String + public var content: String? + public var children: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String? = nil, + children: List? = []) { + self.init(id: id, + content: content, + children: children, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String? = nil, + children: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self.children = children + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild+Schema.swift new file mode 100644 index 0000000000..00fa241c55 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild+Schema.swift @@ -0,0 +1,39 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension HasOneChild { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let hasOneChild = HasOneChild.keys + + model.pluralName = "HasOneChildren" + + model.attributes( + .index(fields: ["id"], name: nil), + .primaryKey(fields: [hasOneChild.id]) + ) + + model.fields( + .field(hasOneChild.id, is: .required, ofType: .string), + .field(hasOneChild.content, is: .optional, ofType: .string), + .field(hasOneChild.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(hasOneChild.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension HasOneChild: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild.swift new file mode 100644 index 0000000000..c915be5997 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneChild.swift @@ -0,0 +1,27 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct HasOneChild: Model { + public let id: String + public var content: String? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String? = nil) { + self.init(id: id, + content: content, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent+Schema.swift new file mode 100644 index 0000000000..b9bff0c853 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent+Schema.swift @@ -0,0 +1,41 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension HasOneParent { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case child + case createdAt + case updatedAt + case hasOneParentChildId + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let hasOneParent = HasOneParent.keys + + model.pluralName = "HasOneParents" + + model.attributes( + .index(fields: ["id"], name: nil), + .primaryKey(fields: [hasOneParent.id]) + ) + + model.fields( + .field(hasOneParent.id, is: .required, ofType: .string), + .hasOne(hasOneParent.child, is: .optional, ofType: HasOneChild.self, associatedWith: HasOneChild.keys.id, targetNames: ["hasOneParentChildId"]), + .field(hasOneParent.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(hasOneParent.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(hasOneParent.hasOneParentChildId, is: .optional, ofType: .string) + ) + } +} + +extension HasOneParent: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent.swift new file mode 100644 index 0000000000..d7a724b08f --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/HasOneParent.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct HasOneParent: Model { + public let id: String + public var child: HasOneChild? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + public var hasOneParentChildId: String? + + public init(id: String = UUID().uuidString, + child: HasOneChild? = nil, + hasOneParentChildId: String? = nil) { + self.init(id: id, + child: child, + createdAt: nil, + updatedAt: nil, + hasOneParentChildId: hasOneParentChildId) + } + internal init(id: String = UUID().uuidString, + child: HasOneChild? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil, + hasOneParentChildId: String? = nil) { + self.id = id + self.child = child + self.createdAt = createdAt + self.updatedAt = updatedAt + self.hasOneParentChildId = hasOneParentChildId + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild+Schema.swift new file mode 100644 index 0000000000..641193c69a --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension ImplicitChild { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case childId + case content + case parent + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let implicitChild = ImplicitChild.keys + + model.pluralName = "ImplicitChildren" + + model.attributes( + .index(fields: ["childId", "content"], name: nil), + .primaryKey(fields: [implicitChild.childId, implicitChild.content]) + ) + + model.fields( + .field(implicitChild.childId, is: .required, ofType: .string), + .field(implicitChild.content, is: .required, ofType: .string), + .belongsTo(implicitChild.parent, is: .required, ofType: CompositePKParent.self, targetNames: ["compositePKParentImplicitChildrenCustomId", "compositePKParentImplicitChildrenContent"]), + .field(implicitChild.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(implicitChild.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension ImplicitChild: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension ImplicitChild.IdentifierProtocol { + public static func identifier(childId: String, + content: String) -> Self { + .make(fields:[(name: "childId", value: childId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild.swift new file mode 100644 index 0000000000..e9a416b68e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/ImplicitChild.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct ImplicitChild: Model { + public let childId: String + public let content: String + public var parent: CompositePKParent + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(childId: String, + content: String, + parent: CompositePKParent) { + self.init(childId: childId, + content: content, + parent: parent, + createdAt: nil, + updatedAt: nil) + } + internal init(childId: String, + content: String, + parent: CompositePKParent, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.childId = childId + self.content = content + self.parent = parent + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild+Schema.swift new file mode 100644 index 0000000000..edb39234c2 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild+Schema.swift @@ -0,0 +1,49 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension StrangeExplicitChild { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case strangeId + case content + case parent + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let strangeExplicitChild = StrangeExplicitChild.keys + + model.pluralName = "StrangeExplicitChildren" + + model.attributes( + .index(fields: ["strangeId", "content"], name: nil), + .index(fields: ["strangeParentId", "strangeParentTitle"], name: "byCompositePKParentX"), + .primaryKey(fields: [strangeExplicitChild.strangeId, strangeExplicitChild.content]) + ) + + model.fields( + .field(strangeExplicitChild.strangeId, is: .required, ofType: .string), + .field(strangeExplicitChild.content, is: .required, ofType: .string), + .belongsTo(strangeExplicitChild.parent, is: .required, ofType: CompositePKParent.self, targetNames: ["strangeParentId", "strangeParentTitle"]), + .field(strangeExplicitChild.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(strangeExplicitChild.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension StrangeExplicitChild: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension StrangeExplicitChild.IdentifierProtocol { + public static func identifier(strangeId: String, + content: String) -> Self { + .make(fields:[(name: "strangeId", value: strangeId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild.swift new file mode 100644 index 0000000000..492bf9916d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL12/StrangeExplicitChild.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct StrangeExplicitChild: Model { + public let strangeId: String + public let content: String + public var parent: CompositePKParent + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(strangeId: String, + content: String, + parent: CompositePKParent) { + self.init(strangeId: strangeId, + content: content, + parent: parent, + createdAt: nil, + updatedAt: nil) + } + internal init(strangeId: String, + content: String, + parent: CompositePKParent, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.strangeId = strangeId + self.content = content + self.parent = parent + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2+Schema.swift new file mode 100644 index 0000000000..caf4854d1e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2+Schema.swift @@ -0,0 +1,44 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Blog8V2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case name + case customs + case notes + case posts + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let blog8V2 = Blog8V2.keys + + model.pluralName = "Blog8V2s" + + model.attributes( + .primaryKey(fields: [blog8V2.id]) + ) + + model.fields( + .field(blog8V2.id, is: .required, ofType: .string), + .field(blog8V2.name, is: .required, ofType: .string), + .field(blog8V2.customs, is: .optional, ofType: .embeddedCollection(of: MyCustomModel8.self)), + .field(blog8V2.notes, is: .optional, ofType: .embeddedCollection(of: String.self)), + .hasMany(blog8V2.posts, is: .optional, ofType: Post8V2.self, associatedWith: Post8V2.keys.blog), + .field(blog8V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(blog8V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Blog8V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2.swift new file mode 100644 index 0000000000..d5209799e0 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Blog8V2.swift @@ -0,0 +1,42 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Blog8V2: Model { + public let id: String + public var name: String + public var customs: [MyCustomModel8?]? + public var notes: [String?]? + public var posts: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + name: String, + customs: [MyCustomModel8?]? = nil, + notes: [String?]? = nil, + posts: List? = []) { + self.init(id: id, + name: name, + customs: customs, + notes: notes, + posts: posts, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + name: String, + customs: [MyCustomModel8?]? = nil, + notes: [String?]? = nil, + posts: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.name = name + self.customs = customs + self.notes = notes + self.posts = posts + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2+Schema.swift new file mode 100644 index 0000000000..ef1b3620a3 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2+Schema.swift @@ -0,0 +1,41 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Comment8V2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case post + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let comment8V2 = Comment8V2.keys + + model.pluralName = "Comment8V2s" + + model.attributes( + .index(fields: ["postId"], name: "commentByPost"), + .primaryKey(fields: [comment8V2.id]) + ) + + model.fields( + .field(comment8V2.id, is: .required, ofType: .string), + .field(comment8V2.content, is: .optional, ofType: .string), + .belongsTo(comment8V2.post, is: .optional, ofType: Post8V2.self, targetNames: ["postId"]), + .field(comment8V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment8V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Comment8V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2.swift new file mode 100644 index 0000000000..1407b12b0f --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Comment8V2.swift @@ -0,0 +1,59 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment8V2: Model { + public let id: String + public var content: String? + internal var _post: LazyModel + public var post: Post8V2? { + get async throws { + try await _post.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String? = nil, + post: Post8V2? = nil) { + self.init(id: id, + content: content, + post: post, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String? = nil, + post: Post8V2? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self._post = LazyModel(element: post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setPost(_ post: Post8V2) { + self._post = LazyModel(element: post) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + id = try values.decode(String.self, forKey: .id) + content = try values.decode(String.self, forKey: .content) + _post = try values.decode(LazyModel.self, forKey: .post) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(content, forKey: .content) + try container.encode(_post, forKey: .post) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8+Schema.swift new file mode 100644 index 0000000000..2c926d8ab8 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8+Schema.swift @@ -0,0 +1,29 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension MyCustomModel8 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case name + case desc + case children + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let myCustomModel8 = MyCustomModel8.keys + + model.pluralName = "MyCustomModel8s" + + model.fields( + .field(myCustomModel8.id, is: .required, ofType: .string), + .field(myCustomModel8.name, is: .required, ofType: .string), + .field(myCustomModel8.desc, is: .optional, ofType: .string), + .field(myCustomModel8.children, is: .optional, ofType: .embeddedCollection(of: MyNestedModel8.self)) + ) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8.swift new file mode 100644 index 0000000000..6fb7a2b02d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyCustomModel8.swift @@ -0,0 +1,10 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct MyCustomModel8: Embeddable { + var id: String + var name: String + var desc: String? + var children: [MyNestedModel8?]? +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8+Schema.swift new file mode 100644 index 0000000000..da4f5f1040 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8+Schema.swift @@ -0,0 +1,27 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension MyNestedModel8 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case nestedName + case notes + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let myNestedModel8 = MyNestedModel8.keys + + model.pluralName = "MyNestedModel8s" + + model.fields( + .field(myNestedModel8.id, is: .required, ofType: .string), + .field(myNestedModel8.nestedName, is: .required, ofType: .string), + .field(myNestedModel8.notes, is: .optional, ofType: .embeddedCollection(of: String.self)) + ) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8.swift new file mode 100644 index 0000000000..9884b8a197 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/MyNestedModel8.swift @@ -0,0 +1,9 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct MyNestedModel8: Embeddable { + var id: String + var nestedName: String + var notes: [String?]? +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2+Schema.swift new file mode 100644 index 0000000000..f703575013 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2+Schema.swift @@ -0,0 +1,46 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Post8V2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case name + case randomId + case blog + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let post8V2 = Post8V2.keys + + model.pluralName = "Post8V2s" + + model.attributes( + .index(fields: ["blogId"], name: "postByBlog"), + .index(fields: ["randomId"], name: "byRandom"), + .primaryKey(fields: [post8V2.id]) + ) + + model.fields( + .field(post8V2.id, is: .required, ofType: .string), + .field(post8V2.name, is: .required, ofType: .string), + .field(post8V2.randomId, is: .optional, ofType: .string), + .belongsTo(post8V2.blog, is: .optional, ofType: Blog8V2.self, targetNames: ["blogId"]), + .hasMany(post8V2.comments, is: .optional, ofType: Comment8V2.self, associatedWith: Comment8V2.keys.post), + .field(post8V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post8V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Post8V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2.swift new file mode 100644 index 0000000000..81f834dd4e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL2/Post8V2.swift @@ -0,0 +1,74 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Post8V2: Model { + public let id: String + public var name: String + public var randomId: String? + internal var _blog: LazyModel + public var blog: Blog8V2? { + get async throws { + try await _blog.get() + } + } + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + name: String, + randomId: String? = nil, + blog: Blog8V2? = nil, + comments: List? = []) { + self.init(id: id, + name: name, + randomId: randomId, + blog: blog, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + name: String, + randomId: String? = nil, + blog: Blog8V2? = nil, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.name = name + self.randomId = randomId + self._blog = LazyModel(element: blog) + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setBlog(_ blog: Blog8V2) { + self._blog = LazyModel(element: blog) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + id = try values.decode(String.self, forKey: .id) + name = try values.decode(String.self, forKey: .name) + randomId = try values.decode(String.self, forKey: .randomId) + _blog = try values.decode(LazyModel.self, forKey: .blog) + comments = try values.decode(List?.self, forKey: .comments) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(randomId, forKey: .randomId) + try container.encode(_blog, forKey: .blog) + try container.encode(comments, forKey: .comments) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } + +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift new file mode 100644 index 0000000000..ebe9c06537 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift @@ -0,0 +1,175 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +final class AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests: AWSDataStoreLazyLoadBaseTest { + + func testLazyLoad() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await assertComment(savedComment, hasEagerLoaded: savedPost) + try await assertPost(savedPost, canLazyLoad: savedComment) + let queriedComment = try await query(for: savedComment) + try await assertComment(queriedComment, canLazyLoad: savedPost) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedComment) + } + + func assertComment(_ comment: Comment, + hasEagerLoaded post: Post) async throws { + assertLazyModel(comment._post, + state: .loaded(model: post)) + + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + + // retrieve loaded model + guard let loadedPost = try await comment.post else { + XCTFail("Failed to retrieve the loaded post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertComment(_ comment: Comment, + canLazyLoad post: Post) async throws { + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + guard let loadedPost = try await comment.post else { + XCTFail("Failed to load the post from the comment") + return + } + XCTAssertEqual(loadedPost.id, post.id) + assertLazyModel(comment._post, + state: .loaded(model: post)) + try await assertPost(loadedPost, canLazyLoad: comment) + } + + func assertPost(_ post: Post, + canLazyLoad comment: Comment) async throws { + guard let comments = post.comments else { + XCTFail("Missing comments on post") + return + } + assertList(comments, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "post")) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + guard let comment = comments.first else { + XCTFail("Missing lazy loaded comment from post") + return + } + assertLazyModel(comment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + } + + func testSaveWithoutPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + let comment = Comment(content: "content") + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: nil)) + let post = Post(title: "title") + let savedPost = try await saveAndWaitForSync(post) + queriedComment.setPost(savedPost) + let saveCommentWithPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithPost) + try await assertComment(queriedComment2, canLazyLoad: post) + } + + func testUpdateFromQueriedComment() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + let queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + let savedQueriedComment = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: savedQueriedComment) + try await assertComment(queriedComment2, canLazyLoad: savedPost) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + + let newPost = Post(title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedComment.setPost(newPost) + let saveCommentWithNewPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithNewPost) + try await assertComment(queriedComment2, canLazyLoad: newPost) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertLazyModel(queriedComment._post, + state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + + queriedComment.setPost(nil) + let saveCommentRemovePost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedCommentNoPost = try await query(for: saveCommentRemovePost) + assertLazyModel(queriedCommentNoPost._post, + state: .notLoaded(identifiers: nil)) + } + + func testDelete() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels(), logLevel: .verbose, eagerLoad: false) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await deleteAndWaitForSync(savedPost) + try await assertModelDoesNotExist(savedComment) + try await assertModelDoesNotExist(savedPost) + } +} + +extension AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests { + typealias Post = PostWithCompositeKey + typealias Comment = CommentWithCompositeKey + + struct PostCommentWithCompositeKeyModels: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: PostWithCompositeKey.self) + ModelRegistry.register(modelType: CommentWithCompositeKey.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..e267c86f93 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension CommentWithCompositeKey { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case content + case post + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let commentWithCompositeKey = CommentWithCompositeKey.keys + + model.pluralName = "CommentWithCompositeKeys" + + model.attributes( + .index(fields: ["id", "content"], name: nil), + .primaryKey(fields: [commentWithCompositeKey.id, commentWithCompositeKey.content]) + ) + + model.fields( + .field(commentWithCompositeKey.id, is: .required, ofType: .string), + .field(commentWithCompositeKey.content, is: .required, ofType: .string), + .belongsTo(commentWithCompositeKey.post, is: .optional, ofType: PostWithCompositeKey.self, targetNames: ["postWithCompositeKeyCommentsId", "postWithCompositeKeyCommentsTitle"]), + .field(commentWithCompositeKey.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(commentWithCompositeKey.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension CommentWithCompositeKey: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension CommentWithCompositeKey.IdentifierProtocol { + public static func identifier(id: String, + content: String) -> Self { + .make(fields:[(name: "id", value: id), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey.swift new file mode 100644 index 0000000000..21d686d868 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/CommentWithCompositeKey.swift @@ -0,0 +1,64 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct CommentWithCompositeKey: Model { + public let id: String + public let content: String + internal var _post: LazyModel + public var post: PostWithCompositeKey? { + get async throws { + try await _post.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + content: String, + post: PostWithCompositeKey? = nil) { + self.init(id: id, + content: content, + post: post, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + content: String, + post: PostWithCompositeKey? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.content = content + self._post = LazyModel(element: post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setPost(_ post: PostWithCompositeKey?) { + self._post = LazyModel(element: post) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + id = try values.decode(String.self, forKey: .id) + content = try values.decode(String.self, forKey: .content) + do { + _post = try values.decode(LazyModel.self, forKey: .post) + } catch { + _post = LazyModel(identifiers: nil) + } + + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(content, forKey: .content) + try container.encode(_post, forKey: .post) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..e7a9c5e1f4 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension PostWithCompositeKey { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case title + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let postWithCompositeKey = PostWithCompositeKey.keys + + model.pluralName = "PostWithCompositeKeys" + + model.attributes( + .index(fields: ["id", "title"], name: nil), + .primaryKey(fields: [postWithCompositeKey.id, postWithCompositeKey.title]) + ) + + model.fields( + .field(postWithCompositeKey.id, is: .required, ofType: .string), + .field(postWithCompositeKey.title, is: .required, ofType: .string), + .hasMany(postWithCompositeKey.comments, is: .optional, ofType: CommentWithCompositeKey.self, associatedWith: CommentWithCompositeKey.keys.post), + .field(postWithCompositeKey.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(postWithCompositeKey.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension PostWithCompositeKey: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension PostWithCompositeKey.IdentifierProtocol { + public static func identifier(id: String, + title: String) -> Self { + .make(fields:[(name: "id", value: id), (name: "title", value: title)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey.swift new file mode 100644 index 0000000000..e9de7de986 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL3/PostWithCompositeKey.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct PostWithCompositeKey: Model { + public let id: String + public let title: String + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + title: String, + comments: List? = []) { + self.init(id: id, + title: title, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + title: String, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.title = title + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift new file mode 100644 index 0000000000..86f3e7dd0e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/AWSDataStoreLazyLoadPostTagTests.swift @@ -0,0 +1,186 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +final class AWSDataStoreLazyLoadPostTagTests: AWSDataStoreLazyLoadBaseTest { + + func testLazyLoad() async throws { + await setup(withModels: PostTagModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let tag = Tag(name: "name") + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) + let savedPost = try await saveAndWaitForSync(post) + let savedTag = try await saveAndWaitForSync(tag) + let savedPostTag = try await saveAndWaitForSync(postTag) + + try await assertPost(savedPost, canLazyLoad: savedPostTag) + try await assertTag(savedTag, canLazyLoad: savedPostTag) + assertLazyModel(savedPostTag._postWithTagsCompositeKey, state: .loaded(model: savedPost)) + assertLazyModel(savedPostTag._tagWithCompositeKey, state: .loaded(model: savedTag)) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedPostTag) + let queriedTag = try await query(for: savedTag) + try await assertTag(queriedTag, canLazyLoad: savedPostTag) + let queriedPostTag = try await query(for: savedPostTag) + try await assertPostTag(queriedPostTag, canLazyLoadTag: savedTag, canLazyLoadPost: savedPost) + } + + func assertPost(_ post: Post, + canLazyLoad postTag: PostTag) async throws { + guard let postTags = post.tags else { + XCTFail("Missing postTags on post") + return + } + assertList(postTags, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "postWithTagsCompositeKey")) + try await postTags.fetch() + assertList(postTags, state: .isLoaded(count: 1)) + } + + func assertTag(_ tag: Tag, + canLazyLoad postTag: PostTag) async throws { + guard let postTags = tag.posts else { + XCTFail("Missing postTags on post") + return + } + assertList(postTags, state: .isNotLoaded(associatedId: tag.identifier, + associatedField: "tagWithCompositeKey")) + try await postTags.fetch() + assertList(postTags, state: .isLoaded(count: 1)) + } + + func assertPostTag(_ postTag: PostTag, canLazyLoadTag tag: Tag, canLazyLoadPost post: Post) async throws { + assertLazyModel(postTag._tagWithCompositeKey, state: .notLoaded(identifiers: ["@@primaryKey": tag.identifier])) + assertLazyModel(postTag._postWithTagsCompositeKey, state: .notLoaded(identifiers: ["@@primaryKey": post.identifier])) + let loadedTag = try await postTag.tagWithCompositeKey + assertLazyModel(postTag._tagWithCompositeKey, state: .loaded(model: loadedTag)) + try await assertTag(loadedTag, canLazyLoad: postTag) + let loadedPost = try await postTag.postWithTagsCompositeKey + assertLazyModel(postTag._postWithTagsCompositeKey, state: .loaded(model: loadedPost)) + try await assertPost(loadedPost, canLazyLoad: postTag) + } + + func testUpdate() async throws { + await setup(withModels: PostTagModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let tag = Tag(name: "name") + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) + let savedPost = try await saveAndWaitForSync(post) + let savedTag = try await saveAndWaitForSync(tag) + let savedPostTag = try await saveAndWaitForSync(postTag) + + // update the post tag with a new post + var queriedPostTag = try await query(for: savedPostTag) + let newPost = Post(postId: UUID().uuidString, title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedPostTag.setPostWithTagsCompositeKey(newPost) + let savedPostTagWithNewPost = try await saveAndWaitForSync(queriedPostTag, assertVersion: 2) + assertLazyModel(savedPostTagWithNewPost._postWithTagsCompositeKey, state: .loaded(model: newPost)) + let queriedPreviousPost = try await query(for: savedPost) + try await assertPostWithNoPostTag(queriedPreviousPost) + + // update the post tag with a new tag + var queriedPostTagWithNewPost = try await query(for: savedPostTagWithNewPost) + let newTag = Tag(name: "name") + _ = try await saveAndWaitForSync(newTag) + queriedPostTagWithNewPost.setTagWithCompositeKey(newTag) + let savedPostTagWithNewTag = try await saveAndWaitForSync(queriedPostTagWithNewPost, assertVersion: 3) + assertLazyModel(savedPostTagWithNewTag._tagWithCompositeKey, state: .loaded(model: newTag)) + let queriedPreviousTag = try await query(for: savedTag) + try await assertTagWithNoPostTag(queriedPreviousTag) + } + + func assertPostWithNoPostTag(_ post: Post) async throws { + guard let postTags = post.tags else { + XCTFail("Missing postTags on post") + return + } + assertList(postTags, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "postWithTagsCompositeKey")) + try await postTags.fetch() + assertList(postTags, state: .isLoaded(count: 0)) + } + + func assertTagWithNoPostTag(_ tag: Tag) async throws { + guard let postTags = tag.posts else { + XCTFail("Missing postTags on post") + return + } + assertList(postTags, state: .isNotLoaded(associatedId: tag.identifier, + associatedField: "tagWithCompositeKey")) + try await postTags.fetch() + assertList(postTags, state: .isLoaded(count: 0)) + } + + func testDeletePost() async throws { + await setup(withModels: PostTagModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let tag = Tag(name: "name") + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) + let savedPost = try await saveAndWaitForSync(post) + let savedTag = try await saveAndWaitForSync(tag) + let savedPostTag = try await saveAndWaitForSync(postTag) + + try await deleteAndWaitForSync(savedPost) + + try await assertModelDoesNotExist(savedPost) + try await assertModelExists(savedTag) + try await assertModelDoesNotExist(savedPostTag) + } + + func testDeleteTag() async throws { + await setup(withModels: PostTagModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let tag = Tag(name: "name") + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) + let savedPost = try await saveAndWaitForSync(post) + let savedTag = try await saveAndWaitForSync(tag) + let savedPostTag = try await saveAndWaitForSync(postTag) + + try await deleteAndWaitForSync(savedTag) + + try await assertModelExists(savedPost) + try await assertModelDoesNotExist(savedTag) + try await assertModelDoesNotExist(savedPostTag) + } + + func testDeletePostTag() async throws { + await setup(withModels: PostTagModels(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let tag = Tag(name: "name") + let postTag = PostTag(postWithTagsCompositeKey: post, tagWithCompositeKey: tag) + let savedPost = try await saveAndWaitForSync(post) + let savedTag = try await saveAndWaitForSync(tag) + let savedPostTag = try await saveAndWaitForSync(postTag) + + try await deleteAndWaitForSync(savedPostTag) + + try await assertModelExists(savedPost) + try await assertModelExists(savedTag) + try await assertModelDoesNotExist(savedPostTag) + } +} +extension AWSDataStoreLazyLoadPostTagTests { + typealias Post = PostWithTagsCompositeKey + typealias Tag = TagWithCompositeKey + typealias PostTag = PostTagsWithCompositeKey + + struct PostTagModels: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: PostTagsWithCompositeKey.self) + ModelRegistry.register(modelType: PostWithTagsCompositeKey.self) + ModelRegistry.register(modelType: TagWithCompositeKey.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..172936f4e8 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey+Schema.swift @@ -0,0 +1,42 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension PostTagsWithCompositeKey { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case postWithTagsCompositeKey + case tagWithCompositeKey + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let postTagsWithCompositeKey = PostTagsWithCompositeKey.keys + + model.pluralName = "PostTagsWithCompositeKeys" + + model.attributes( + .index(fields: ["postWithTagsCompositeKeyPostId", "postWithTagsCompositeKeytitle"], name: "byPostWithTagsCompositeKey"), + .index(fields: ["tagWithCompositeKeyId", "tagWithCompositeKeyname"], name: "byTagWithCompositeKey"), + .primaryKey(fields: [postTagsWithCompositeKey.id]) + ) + + model.fields( + .field(postTagsWithCompositeKey.id, is: .required, ofType: .string), + .belongsTo(postTagsWithCompositeKey.postWithTagsCompositeKey, is: .required, ofType: PostWithTagsCompositeKey.self, targetNames: ["postWithTagsCompositeKeyPostId", "postWithTagsCompositeKeytitle"]), + .belongsTo(postTagsWithCompositeKey.tagWithCompositeKey, is: .required, ofType: TagWithCompositeKey.self, targetNames: ["tagWithCompositeKeyId", "tagWithCompositeKeyname"]), + .field(postTagsWithCompositeKey.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(postTagsWithCompositeKey.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension PostTagsWithCompositeKey: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey.swift new file mode 100644 index 0000000000..b4288ea812 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostTagsWithCompositeKey.swift @@ -0,0 +1,68 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct PostTagsWithCompositeKey: Model { + public let id: String + internal var _postWithTagsCompositeKey: LazyModel + public var postWithTagsCompositeKey: PostWithTagsCompositeKey { + get async throws { + try await _postWithTagsCompositeKey.require() + } + } + internal var _tagWithCompositeKey: LazyModel + public var tagWithCompositeKey: TagWithCompositeKey { + get async throws { + try await _tagWithCompositeKey.require() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + postWithTagsCompositeKey: PostWithTagsCompositeKey, + tagWithCompositeKey: TagWithCompositeKey) { + self.init(id: id, + postWithTagsCompositeKey: postWithTagsCompositeKey, + tagWithCompositeKey: tagWithCompositeKey, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + postWithTagsCompositeKey: PostWithTagsCompositeKey, + tagWithCompositeKey: TagWithCompositeKey, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self._postWithTagsCompositeKey = LazyModel(element: postWithTagsCompositeKey) + self._tagWithCompositeKey = LazyModel(element: tagWithCompositeKey) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setPostWithTagsCompositeKey(_ postWithTagsCompositeKey: PostWithTagsCompositeKey) { + self._postWithTagsCompositeKey = LazyModel(element: postWithTagsCompositeKey) + } + + public mutating func setTagWithCompositeKey(_ tagWithCompositeKey: TagWithCompositeKey) { + self._tagWithCompositeKey = LazyModel(element: tagWithCompositeKey) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + id = try values.decode(String.self, forKey: .id) + _postWithTagsCompositeKey = try values.decode(LazyModel.self, forKey: .postWithTagsCompositeKey) + _tagWithCompositeKey = try values.decode(LazyModel.self, forKey: .tagWithCompositeKey) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(_postWithTagsCompositeKey, forKey: .postWithTagsCompositeKey) + try container.encode(_tagWithCompositeKey, forKey: .tagWithCompositeKey) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey+Schema.swift new file mode 100644 index 0000000000..2c0cd997e6 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension PostWithTagsCompositeKey { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case postId + case title + case tags + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let postWithTagsCompositeKey = PostWithTagsCompositeKey.keys + + model.pluralName = "PostWithTagsCompositeKeys" + + model.attributes( + .index(fields: ["postId", "title"], name: nil), + .primaryKey(fields: [postWithTagsCompositeKey.postId, postWithTagsCompositeKey.title]) + ) + + model.fields( + .field(postWithTagsCompositeKey.postId, is: .required, ofType: .string), + .field(postWithTagsCompositeKey.title, is: .required, ofType: .string), + .hasMany(postWithTagsCompositeKey.tags, is: .optional, ofType: PostTagsWithCompositeKey.self, associatedWith: PostTagsWithCompositeKey.keys.postWithTagsCompositeKey), + .field(postWithTagsCompositeKey.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(postWithTagsCompositeKey.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension PostWithTagsCompositeKey: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension PostWithTagsCompositeKey.IdentifierProtocol { + public static func identifier(postId: String, + title: String) -> Self { + .make(fields:[(name: "postId", value: postId), (name: "title", value: title)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey.swift new file mode 100644 index 0000000000..c739bc9c22 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/PostWithTagsCompositeKey.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct PostWithTagsCompositeKey: Model { + public let postId: String + public let title: String + public var tags: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(postId: String, + title: String, + tags: List? = []) { + self.init(postId: postId, + title: title, + tags: tags, + createdAt: nil, + updatedAt: nil) + } + internal init(postId: String, + title: String, + tags: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.postId = postId + self.title = title + self.tags = tags + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..dd535f584d --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension TagWithCompositeKey { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case name + case posts + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let tagWithCompositeKey = TagWithCompositeKey.keys + + model.pluralName = "TagWithCompositeKeys" + + model.attributes( + .index(fields: ["id", "name"], name: nil), + .primaryKey(fields: [tagWithCompositeKey.id, tagWithCompositeKey.name]) + ) + + model.fields( + .field(tagWithCompositeKey.id, is: .required, ofType: .string), + .field(tagWithCompositeKey.name, is: .required, ofType: .string), + .hasMany(tagWithCompositeKey.posts, is: .optional, ofType: PostTagsWithCompositeKey.self, associatedWith: PostTagsWithCompositeKey.keys.tagWithCompositeKey), + .field(tagWithCompositeKey.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(tagWithCompositeKey.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension TagWithCompositeKey: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension TagWithCompositeKey.IdentifierProtocol { + public static func identifier(id: String, + name: String) -> Self { + .make(fields:[(name: "id", value: id), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey.swift new file mode 100644 index 0000000000..8d8bbf9f26 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL4/TagWithCompositeKey.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct TagWithCompositeKey: Model { + public let id: String + public let name: String + public var posts: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + name: String, + posts: List? = []) { + self.init(id: id, + name: name, + posts: posts, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + name: String, + posts: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.name = name + self.posts = posts + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift new file mode 100644 index 0000000000..2cba5311af --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/AWSDataStoreLazyLoadProjectTeam1Tests.swift @@ -0,0 +1,318 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +@testable import AWSPluginsCore + +class AWSDataStoreLazyLoadProjectTeam1Tests: AWSDataStoreLazyLoadBaseTest { + + func testStart() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false, clearOnTearDown: false) + try await startAndWaitForReady() + printDBPath() + } + + func testAPISyncQuery() async throws { + await setupAPIOnly(withModels: ProjectTeam1Models()) + + // The selection set of project should include "hasOne" team, and no further + let projectRequest = GraphQLRequest.syncQuery(modelType: Project.self) + let projectDocument = """ + query SyncProject1s($limit: Int) { + syncProject1s(limit: $limit) { + items { + projectId + name + createdAt + project1TeamName + project1TeamTeamId + updatedAt + team { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(projectRequest.document, projectDocument) + + // The selection set of team should include "belongsTo" project, and no further. + let teamRequest = GraphQLRequest.syncQuery(modelType: Team.self) + let teamDocument = """ + query SyncTeam1s($limit: Int) { + syncTeam1s(limit: $limit) { + items { + teamId + name + createdAt + updatedAt + project { + projectId + name + createdAt + project1TeamName + project1TeamTeamId + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(teamRequest.document, teamDocument) + // Making the actual requests and ensuring they can decode to the Model types. + _ = try await Amplify.API.query(request: projectRequest) + _ = try await Amplify.API.query(request: teamRequest) + } + + func testSaveTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + } + + func testSaveProject() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, + name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + assertProjectDoesNotContainTeam(savedProject) + } + + func testSaveProjectWithTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + // Project initializer variation #1 (pass both team reference and fields in) + let project = Project(projectId: UUID().uuidString, + name: "name", + team: team, + project1TeamTeamId: team.teamId, + project1TeamName: team.name) + let savedProject = try await saveAndWaitForSync(project) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + + // Project initializer variation #2 (pass only team reference) + let project2 = Project(projectId: UUID().uuidString, + name: "name", + team: team) + let savedProject2 = try await saveAndWaitForSync(project2) + let queriedProject2 = try await query(for: savedProject2) + assertProjectDoesNotContainTeam(queriedProject2) + + // Project initializer variation #3 (pass fields in) + let project3 = Project(projectId: UUID().uuidString, + name: "name", + project1TeamTeamId: team.teamId, + project1TeamName: team.name) + let savedProject3 = try await saveAndWaitForSync(project3) + let queriedProject3 = try await query(for: savedProject3) + assertProject(queriedProject3, hasTeam: savedTeam) + } + + func testSaveProjectWithTeamThenAccessProjectFromTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false, clearOnTearDown: false) + + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(savedTeam) + let savedProject = try await saveAndWaitForSync(project) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + var queriedTeam = try await query(for: savedTeam) + + // The team has a FK referencing the Project. Updating the project with team + // does not reflect the team, which is why the queried team does not have the project + // to load. + // Project (Parent) hasOne Team + // Team (Child) belongsTo Project + // Team has the FK of Project. + switch queriedTeam._project.modelProvider.getState() { + case .notLoaded(let identifiers): + print("NOT LOADED \(identifiers)") + case .loaded(let model): + print("LOADED \(model)") + } + + queriedTeam.setProject(queriedProject) + let savedTeamWithProject = try await saveAndWaitForSync(queriedTeam, assertVersion: 2) + // Now the FK in the Team should be populated with Project's PK + switch savedTeamWithProject._project.modelProvider.getState() { + case .notLoaded(let identifiers): + print("NOT LOADED \(identifiers)") + case .loaded(let model): + print("LOADED \(model)") + } + var queriedTeamWithProject = try await query(for: savedTeamWithProject) + switch savedTeamWithProject._project.modelProvider.getState() { + case .notLoaded(let identifiers): + print("NOT LOADED \(identifiers)") + case .loaded(let model): + print("LOADED \(model)") + } + printDBPath() + } + + // One-to-One relationships do not create a foreign key for the Team or Project table + // So the LazyModel does not have the FK value to be instantiated as metadata for lazy loading. + // We only assert the FK fields on the Project exist and are equal to the Team's PK. + func assertProject(_ project: Project, hasTeam team: Team) { + XCTAssertEqual(project.project1TeamTeamId, team.teamId) + XCTAssertEqual(project.project1TeamName, team.name) + } + + func assertProjectDoesNotContainTeam(_ project: Project) { + XCTAssertNil(project.project1TeamTeamId) + XCTAssertNil(project.project1TeamName) + } + + func testSaveProjectWithTeamThenUpdate() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + assertProject(savedProject, hasTeam: savedTeam) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + let updatedProject = try await saveAndWaitForSync(project, assertVersion: 2) + assertProject(updatedProject, hasTeam: savedTeam) + } + + func testSaveProjectWithoutTeamUpdateProjectWithTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + assertProjectDoesNotContainTeam(savedProject) + + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + var queriedProject = try await query(for: savedProject) + queriedProject.project1TeamTeamId = team.teamId + queriedProject.project1TeamName = team.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(savedProjectWithNewTeam, hasTeam: savedTeam) + } + + func testSaveTeamSaveProjectWithTeamUpdateProjectToNoTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.project1TeamTeamId = nil + queriedProject.project1TeamName = nil + let savedProjectWithNoTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProjectDoesNotContainTeam(savedProjectWithNoTeam) + } + + func testSaveProjectWithTeamUpdateProjectToNewTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + let newTeam = Team(teamId: UUID().uuidString, name: "name") + let savedNewTeam = try await saveAndWaitForSync(newTeam) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.project1TeamTeamId = newTeam.teamId + queriedProject.project1TeamName = newTeam.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(queriedProject, hasTeam: savedNewTeam) + } + + func testDeleteTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } + + func testDeleteProject() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + try await deleteAndWaitForSync(savedProject) + try await assertModelDoesNotExist(savedProject) + } + + func testDeleteProjectWithTeam() async throws { + await setup(withModels: ProjectTeam1Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + + try await assertModelExists(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedProject) + + try await assertModelDoesNotExist(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } +} + +extension AWSDataStoreLazyLoadProjectTeam1Tests { + typealias Project = Project1 + typealias Team = Team1 + + struct ProjectTeam1Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Project1.self) + ModelRegistry.register(modelType: Team1.self) + } + } + + func initializeProjectWithTeam(_ team: Team) -> Project { + return Project(projectId: UUID().uuidString, + name: "name", + team: team, + project1TeamTeamId: team.teamId, + project1TeamName: team.name) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1+Schema.swift new file mode 100644 index 0000000000..4e4814df85 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1+Schema.swift @@ -0,0 +1,52 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Project1 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case projectId + case name + case team + case createdAt + case updatedAt + case project1TeamTeamId + case project1TeamName + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let project1 = Project1.keys + + model.pluralName = "Project1s" + + model.attributes( + .index(fields: ["projectId", "name"], name: nil), + .primaryKey(fields: [project1.projectId, project1.name]) + ) + + model.fields( + .field(project1.projectId, is: .required, ofType: .string), + .field(project1.name, is: .required, ofType: .string), + .hasOne(project1.team, is: .optional, ofType: Team1.self, associatedWith: Team1.keys.project, targetNames: ["project1TeamTeamId", "project1TeamName"]), + .field(project1.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project1.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project1.project1TeamTeamId, is: .optional, ofType: .string), + .field(project1.project1TeamName, is: .optional, ofType: .string) + ) + } +} + +extension Project1: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Project1.IdentifierProtocol { + public static func identifier(projectId: String, + name: String) -> Self { + .make(fields:[(name: "projectId", value: projectId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1.swift new file mode 100644 index 0000000000..a80cbb1437 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Project1.swift @@ -0,0 +1,77 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Project1: Model { + public let projectId: String + public let name: String + internal var _team: LazyModel + public var team: Team1? { + get async throws { + try await _team.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + public var project1TeamTeamId: String? + public var project1TeamName: String? + + public init(projectId: String, + name: String, + team: Team1? = nil, + project1TeamTeamId: String? = nil, + project1TeamName: String? = nil) { + self.init(projectId: projectId, + name: name, + team: team, + createdAt: nil, + updatedAt: nil, + project1TeamTeamId: project1TeamTeamId, + project1TeamName: project1TeamName) + } + internal init(projectId: String, + name: String, + team: Team1? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil, + project1TeamTeamId: String? = nil, + project1TeamName: String? = nil) { + self.projectId = projectId + self.name = name + self._team = LazyModel(element: team) + self.createdAt = createdAt + self.updatedAt = updatedAt + self.project1TeamTeamId = project1TeamTeamId + self.project1TeamName = project1TeamName + } + + public mutating func setTeam(_ team: Team1?) { + self._team = LazyModel(element: team) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + projectId = try values.decode(String.self, forKey: .projectId) + name = try values.decode(String.self, forKey: .name) + do { + _team = try values.decode(LazyModel.self, forKey: .team) + } catch { + _team = LazyModel(identifiers: nil) + } + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + project1TeamTeamId = try values.decode(String?.self, forKey: .project1TeamTeamId) + project1TeamName = try values.decode(String?.self, forKey: .project1TeamName) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(projectId, forKey: .projectId) + try container.encode(name, forKey: .name) + try container.encode(_team, forKey: .team) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + try container.encode(project1TeamTeamId, forKey: .project1TeamTeamId) + try container.encode(project1TeamName, forKey: .project1TeamName) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1+Schema.swift new file mode 100644 index 0000000000..3ec96f9969 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Team1 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case teamId + case name + case project + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let team1 = Team1.keys + + model.pluralName = "Team1s" + + model.attributes( + .index(fields: ["teamId", "name"], name: nil), + .primaryKey(fields: [team1.teamId, team1.name]) + ) + + model.fields( + .field(team1.teamId, is: .required, ofType: .string), + .field(team1.name, is: .required, ofType: .string), + .belongsTo(team1.project, is: .optional, ofType: Project1.self, targetNames: ["team1ProjectProjectId", "team1ProjectName"]), + .field(team1.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(team1.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Team1: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Team1.IdentifierProtocol { + public static func identifier(teamId: String, + name: String) -> Self { + .make(fields:[(name: "teamId", value: teamId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1.swift new file mode 100644 index 0000000000..59c6b0edb3 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL5/Team1.swift @@ -0,0 +1,63 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Team1: Model { + public let teamId: String + public let name: String + internal var _project: LazyModel + public var project: Project1? { + get async throws { + try await _project.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(teamId: String, + name: String, + project: Project1? = nil) { + self.init(teamId: teamId, + name: name, + project: project, + createdAt: nil, + updatedAt: nil) + } + internal init(teamId: String, + name: String, + project: Project1? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.teamId = teamId + self.name = name + self._project = LazyModel(element: project) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setProject(_ project: Project1?) { + self._project = LazyModel(element: project) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + teamId = try values.decode(String.self, forKey: .teamId) + name = try values.decode(String.self, forKey: .name) + do { + _project = try values.decode(LazyModel.self, forKey: .project) + } catch { + _project = LazyModel(identifiers: nil) + } + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(teamId, forKey: .teamId) + try container.encode(name, forKey: .name) + try container.encode(_project, forKey: .project) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift new file mode 100644 index 0000000000..06681dbe1c --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/AWSDataStoreLazyLoadProjectTeam2Tests.swift @@ -0,0 +1,266 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +class AWSDataStoreLazyLoadProjectTeam2Tests: AWSDataStoreLazyLoadBaseTest { + + func testStart() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false, clearOnTearDown: false) + try await startAndWaitForReady() + printDBPath() + } + + func testAPISyncQuery() async throws { + await setupAPIOnly(withModels: ProjectTeam2Models()) + + // The selection set of project should include "hasOne" team, and no further + let projectRequest = GraphQLRequest.syncQuery(modelType: Project.self) + let projectDocument = """ + query SyncProject2s($limit: Int) { + syncProject2s(limit: $limit) { + items { + projectId + name + createdAt + project2TeamName + project2TeamTeamId + updatedAt + team { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(projectRequest.document, projectDocument) + + // In this "Implicit Uni-directional Has One", only project hasOne team, team does not reference the project + // So, the selection set of team does not include project + let teamRequest = GraphQLRequest.syncQuery(modelType: Team.self) + let teamDocument = """ + query SyncTeam2s($limit: Int) { + syncTeam2s(limit: $limit) { + items { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(teamRequest.document, teamDocument) + // Making the actual requests and ensuring they can decode to the Model types. + _ = try await Amplify.API.query(request: projectRequest) + _ = try await Amplify.API.query(request: teamRequest) + } + + func testSaveTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + } + + func testSaveProject() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, + name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + assertProjectDoesNotContainTeam(savedProject) + } + + func testSaveProjectWithTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + // Project initializer variation #1 (pass both team reference and fields in) + let project = Project(projectId: UUID().uuidString, + name: "name", + team: team, + project2TeamTeamId: team.teamId, + project2TeamName: team.name) + let savedProject = try await saveAndWaitForSync(project) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + + // Project initializer variation #2 (pass only team reference) + let project2 = Project(projectId: UUID().uuidString, + name: "name", + team: team) + let savedProject2 = try await saveAndWaitForSync(project2) + let queriedProject2 = try await query(for: savedProject2) + assertProjectDoesNotContainTeam(queriedProject2) + + // Project initializer variation #3 (pass fields in) + let project3 = Project(projectId: UUID().uuidString, + name: "name", + project2TeamTeamId: team.teamId, + project2TeamName: team.name) + let savedProject3 = try await saveAndWaitForSync(project3) + let queriedProject3 = try await query(for: savedProject3) + assertProject(queriedProject3, hasTeam: savedTeam) + } + + // One-to-One relationships do not create a foreign key for the Team or Project table + // So the LazyModel does not have the FK value to be instantiated as metadata for lazy loading. + // We only assert the FK fields on the Project exist and are equal to the Team's PK. + func assertProject(_ project: Project, hasTeam team: Team) { + XCTAssertEqual(project.project2TeamTeamId, team.teamId) + XCTAssertEqual(project.project2TeamName, team.name) + } + + func assertProjectDoesNotContainTeam(_ project: Project) { + XCTAssertNil(project.project2TeamTeamId) + XCTAssertNil(project.project2TeamName) + } + + func testSaveProjectWithTeamThenUpdate() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + assertProject(savedProject, hasTeam: savedTeam) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + let updatedProject = try await saveAndWaitForSync(project, assertVersion: 2) + assertProject(updatedProject, hasTeam: savedTeam) + } + + func testSaveProjectWithoutTeamUpdateProjectWithTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + assertProjectDoesNotContainTeam(savedProject) + + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + var queriedProject = try await query(for: savedProject) + queriedProject.project2TeamTeamId = team.teamId + queriedProject.project2TeamName = team.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(savedProjectWithNewTeam, hasTeam: savedTeam) + } + + func testSaveTeamSaveProjectWithTeamUpdateProjectToNoTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.project2TeamTeamId = nil + queriedProject.project2TeamName = nil + let savedProjectWithNoTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProjectDoesNotContainTeam(savedProjectWithNoTeam) + } + + func testSaveProjectWithTeamUpdateProjectToNewTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + let newTeam = Team(teamId: UUID().uuidString, name: "name") + let savedNewTeam = try await saveAndWaitForSync(newTeam) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.project2TeamTeamId = newTeam.teamId + queriedProject.project2TeamName = newTeam.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(queriedProject, hasTeam: savedNewTeam) + } + + func testDeleteTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } + + func testDeleteProject() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + try await deleteAndWaitForSync(savedProject) + try await assertModelDoesNotExist(savedProject) + } + + func testDeleteProjectWithTeam() async throws { + await setup(withModels: ProjectTeam2Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + + try await assertModelExists(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedProject) + + try await assertModelDoesNotExist(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } +} + +extension AWSDataStoreLazyLoadProjectTeam2Tests { + + typealias Project = Project2 + typealias Team = Team2 + + struct ProjectTeam2Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Project2.self) + ModelRegistry.register(modelType: Team2.self) + } + } + + func initializeProjectWithTeam(_ team: Team) -> Project { + return Project(projectId: UUID().uuidString, + name: "name", + team: team, + project2TeamTeamId: team.teamId, + project2TeamName: team.name) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2+Schema.swift new file mode 100644 index 0000000000..c3693a6421 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2+Schema.swift @@ -0,0 +1,52 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Project2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case projectId + case name + case team + case createdAt + case updatedAt + case project2TeamTeamId + case project2TeamName + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let project2 = Project2.keys + + model.pluralName = "Project2s" + + model.attributes( + .index(fields: ["projectId", "name"], name: nil), + .primaryKey(fields: [project2.projectId, project2.name]) + ) + + model.fields( + .field(project2.projectId, is: .required, ofType: .string), + .field(project2.name, is: .required, ofType: .string), + .hasOne(project2.team, is: .optional, ofType: Team2.self, associatedWith: Team2.keys.teamId, targetNames: ["project2TeamTeamId", "project2TeamName"]), + .field(project2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project2.project2TeamTeamId, is: .optional, ofType: .string), + .field(project2.project2TeamName, is: .optional, ofType: .string) + ) + } +} + +extension Project2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Project2.IdentifierProtocol { + public static func identifier(projectId: String, + name: String) -> Self { + .make(fields:[(name: "projectId", value: projectId), (name: "name", value: name)]) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2.swift new file mode 100644 index 0000000000..3cca46d19b --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Project2.swift @@ -0,0 +1,78 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Project2: Model { + public let projectId: String + public let name: String + internal var _team: LazyModel + public var team: Team2? { + get async throws { + try await _team.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + public var project2TeamTeamId: String? + public var project2TeamName: String? + + public init(projectId: String, + name: String, + team: Team2? = nil, + project2TeamTeamId: String? = nil, + project2TeamName: String? = nil) { + self.init(projectId: projectId, + name: name, + team: team, + createdAt: nil, + updatedAt: nil, + project2TeamTeamId: project2TeamTeamId, + project2TeamName: project2TeamName) + } + internal init(projectId: String, + name: String, + team: Team2? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil, + project2TeamTeamId: String? = nil, + project2TeamName: String? = nil) { + self.projectId = projectId + self.name = name + self._team = LazyModel(element: team) + self.createdAt = createdAt + self.updatedAt = updatedAt + self.project2TeamTeamId = project2TeamTeamId + self.project2TeamName = project2TeamName + } + + public mutating func setTeam(_ team: Team2?) { + self._team = LazyModel(element: team) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + projectId = try values.decode(String.self, forKey: .projectId) + name = try values.decode(String.self, forKey: .name) + do { + _team = try values.decode(LazyModel.self, forKey: .team) + } catch { + _team = LazyModel(identifiers: nil) + } + + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + project2TeamTeamId = try values.decode(String?.self, forKey: .project2TeamTeamId) + project2TeamName = try values.decode(String?.self, forKey: .project2TeamName) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(projectId, forKey: .projectId) + try container.encode(name, forKey: .name) + try container.encode(_team, forKey: .team) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + try container.encode(project2TeamTeamId, forKey: .project2TeamTeamId) + try container.encode(project2TeamName, forKey: .project2TeamName) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2+Schema.swift new file mode 100644 index 0000000000..0f7353c4ab --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2+Schema.swift @@ -0,0 +1,46 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Team2 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case teamId + case name + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let team2 = Team2.keys + + model.pluralName = "Team2s" + + model.attributes( + .index(fields: ["teamId", "name"], name: nil), + .primaryKey(fields: [team2.teamId, team2.name]) + ) + + model.fields( + .field(team2.teamId, is: .required, ofType: .string), + .field(team2.name, is: .required, ofType: .string), + .field(team2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(team2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Team2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Team2.IdentifierProtocol { + public static func identifier(teamId: String, + name: String) -> Self { + .make(fields:[(name: "teamId", value: teamId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2.swift new file mode 100644 index 0000000000..90d68caa79 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL6/Team2.swift @@ -0,0 +1,27 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Team2: Model { + public let teamId: String + public let name: String + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(teamId: String, + name: String) { + self.init(teamId: teamId, + name: name, + createdAt: nil, + updatedAt: nil) + } + internal init(teamId: String, + name: String, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.teamId = teamId + self.name = name + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift new file mode 100644 index 0000000000..2b16946342 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/AWSDataStoreLazyLoadPostComment4Tests.swift @@ -0,0 +1,183 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +final class AWSDataStoreLazyLoadPostComment4Tests: AWSDataStoreLazyLoadBaseTest { + + // This is broken for the same reason described in Comment8 tests + func testLazyLoad() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + post4CommentsPostId: post.postId, + post4CommentsTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + assertComment(savedComment, contains: savedPost) + try await assertPost(savedPost, canLazyLoad: savedComment) + let queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: savedPost) + let queriedPost = try await query(for: savedPost) + try await assertPost(queriedPost, canLazyLoad: savedComment) + } + + func assertComment(_ comment: Comment, contains post: Post) { + XCTAssertEqual(comment.post4CommentsPostId, post.postId) + XCTAssertEqual(comment.post4CommentsTitle, post.title) + } + + func assertCommentDoesNotContainPost(_ comment: Comment) { + XCTAssertNil(comment.post4CommentsPostId) + XCTAssertNil(comment.post4CommentsTitle) + } + + func assertPost(_ post: Post, + canLazyLoad comment: Comment) async throws { + guard let comments = post.comments else { + XCTFail("Missing comments on post") + return + } + assertList(comments, state: .isNotLoaded(associatedId: post.identifier, + associatedField: "post")) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + guard let comment = comments.first else { + XCTFail("Missing lazy loaded comment from post") + return + } + assertComment(comment, contains: post) + } + + /* + This test fails when the create mutation contains Null values for the foreign keys. On create mutation, we should + be able to detect that the values being set are foreign key fields and remove them from the input + { + "variables" : { + "input" : { + "post4CommentsPostId" : null, + "post4CommentsTitle" : null, + "commentId" : "A3C577B3-7CAD-4603-947A-FB1EBEC0E8CE", + "content" : "content" + } + }, + "query" : "mutation CreateComment4($input: CreateComment4Input!) {\n createComment4(input: $input) {\n commentId\n content\n createdAt\n post4CommentsPostId\n post4CommentsTitle\n updatedAt\n __typename\n _version\n _deleted\n _lastChangedAt\n }\n}" + } + One or more parameter values were invalid: Type mismatch for Index Key post4CommentsPostId Expected: S Actual: NULL IndexName: gsi-Post4.comments + */ + func testSaveWithoutPost() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + let comment = Comment(commentId: UUID().uuidString, content: "content") + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertCommentDoesNotContainPost(queriedComment) + let post = Post(postId: UUID().uuidString, title: "title") + let savedPost = try await saveAndWaitForSync(post) + queriedComment.post4CommentsPostId = savedPost.postId + queriedComment.post4CommentsTitle = savedPost.title + let saveCommentWithPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithPost) + assertComment(queriedComment2, contains: post) + } + + func testUpdateFromQueriedComment() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + post4CommentsPostId: post.postId, + post4CommentsTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + let queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + let savedQueriedComment = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: savedQueriedComment) + assertComment(queriedComment2, contains: savedPost) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + post4CommentsPostId: post.postId, + post4CommentsTitle: post.title) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + let newPost = Post(postId: UUID().uuidString, title: "title") + _ = try await saveAndWaitForSync(newPost) + queriedComment.post4CommentsPostId = newPost.postId + queriedComment.post4CommentsTitle = newPost.title + let saveCommentWithNewPost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedComment2 = try await query(for: saveCommentWithNewPost) + assertComment(queriedComment2, contains: newPost) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + post4CommentsPostId: post.postId, + post4CommentsTitle: post.title) + _ = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + var queriedComment = try await query(for: savedComment) + assertComment(queriedComment, contains: post) + + queriedComment.post4CommentsPostId = nil + queriedComment.post4CommentsTitle = nil + + let saveCommentRemovePost = try await saveAndWaitForSync(queriedComment, assertVersion: 2) + let queriedCommentNoPost = try await query(for: saveCommentRemovePost) + assertCommentDoesNotContainPost(queriedCommentNoPost) + } + + func testDelete() async throws { + await setup(withModels: PostComment4Models(), logLevel: .verbose, eagerLoad: false) + + let post = Post(postId: UUID().uuidString, title: "title") + let comment = Comment(commentId: UUID().uuidString, + content: "content", + post4CommentsPostId: post.postId, + post4CommentsTitle: post.title) + let savedPost = try await saveAndWaitForSync(post) + let savedComment = try await saveAndWaitForSync(comment) + try await deleteAndWaitForSync(savedPost) + + // The expected behavior when deleting a post should be that the + // child models are deleted (comment) followed by the parent model (post). + try await assertModelDoesNotExist(savedPost) + // Is there a way to delete the children models in uni directional relationships? + try await assertModelExists(savedComment) + } +} + +extension AWSDataStoreLazyLoadPostComment4Tests { + typealias Post = Post4 + typealias Comment = Comment4 + + struct PostComment4Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Post4.self) + ModelRegistry.register(modelType: Comment4.self) + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4+Schema.swift new file mode 100644 index 0000000000..6e27468d61 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4+Schema.swift @@ -0,0 +1,50 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Comment4 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case commentId + case content + case createdAt + case updatedAt + case post4CommentsPostId + case post4CommentsTitle + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let comment4 = Comment4.keys + + model.pluralName = "Comment4s" + + model.attributes( + .index(fields: ["commentId", "content"], name: nil), + .primaryKey(fields: [comment4.commentId, comment4.content]) + ) + + model.fields( + .field(comment4.commentId, is: .required, ofType: .string), + .field(comment4.content, is: .required, ofType: .string), + .field(comment4.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment4.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(comment4.post4CommentsPostId, is: .optional, ofType: .string), + .field(comment4.post4CommentsTitle, is: .optional, ofType: .string) + ) + } +} + +extension Comment4: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Comment4.IdentifierProtocol { + public static func identifier(commentId: String, + content: String) -> Self { + .make(fields:[(name: "commentId", value: commentId), (name: "content", value: content)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4.swift new file mode 100644 index 0000000000..bf249d1211 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Comment4.swift @@ -0,0 +1,37 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment4: Model { + public let commentId: String + public let content: String + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + public var post4CommentsPostId: String? + public var post4CommentsTitle: String? + + public init(commentId: String, + content: String, + post4CommentsPostId: String? = nil, + post4CommentsTitle: String? = nil) { + self.init(commentId: commentId, + content: content, + createdAt: nil, + updatedAt: nil, + post4CommentsPostId: post4CommentsPostId, + post4CommentsTitle: post4CommentsTitle) + } + internal init(commentId: String, + content: String, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil, + post4CommentsPostId: String? = nil, + post4CommentsTitle: String? = nil) { + self.commentId = commentId + self.content = content + self.createdAt = createdAt + self.updatedAt = updatedAt + self.post4CommentsPostId = post4CommentsPostId + self.post4CommentsTitle = post4CommentsTitle + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4+Schema.swift new file mode 100644 index 0000000000..0f9b9f5740 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Post4 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case postId + case title + case comments + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let post4 = Post4.keys + + model.pluralName = "Post4s" + + model.attributes( + .index(fields: ["postId", "title"], name: nil), + .primaryKey(fields: [post4.postId, post4.title]) + ) + + model.fields( + .field(post4.postId, is: .required, ofType: .string), + .field(post4.title, is: .required, ofType: .string), + .hasMany(post4.comments, is: .optional, ofType: Comment4.self, associatedWith: Comment4.keys.post4CommentsPostId), + .field(post4.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post4.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Post4: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Post4.IdentifierProtocol { + public static func identifier(postId: String, + title: String) -> Self { + .make(fields:[(name: "postId", value: postId), (name: "title", value: title)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4.swift new file mode 100644 index 0000000000..a50849704b --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL7/Post4.swift @@ -0,0 +1,32 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Post4: Model { + public let postId: String + public let title: String + public var comments: List? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(postId: String, + title: String, + comments: List? = []) { + self.init(postId: postId, + title: title, + comments: comments, + createdAt: nil, + updatedAt: nil) + } + internal init(postId: String, + title: String, + comments: List? = [], + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.postId = postId + self.title = title + self.comments = comments + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift new file mode 100644 index 0000000000..aa5e066a8a --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/AWSDataStoreLazyLoadProjectTeam5Tests.swift @@ -0,0 +1,281 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +class AWSDataStoreLazyLoadProjectTeam5Tests: AWSDataStoreLazyLoadBaseTest { + + func testStart() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false, clearOnTearDown: false) + try await startAndWaitForReady() + printDBPath() + } + + func testAPISyncQuery() async throws { + await setupAPIOnly(withModels: ProjectTeam5Models()) + + // The selection set of project should include "hasOne" team, and no further + let projectRequest = GraphQLRequest.syncQuery(modelType: Project.self) + let projectDocument = """ + query SyncProject5s($limit: Int) { + syncProject5s(limit: $limit) { + items { + projectId + name + createdAt + teamId + teamName + updatedAt + team { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(projectRequest.document, projectDocument) + + // In this "Explicit bi-directional Has One", team "belongsTo" so it should include the project. + let teamRequest = GraphQLRequest.syncQuery(modelType: Team.self) + let teamDocument = """ + query SyncTeam5s($limit: Int) { + syncTeam5s(limit: $limit) { + items { + teamId + name + createdAt + updatedAt + project { + projectId + name + createdAt + teamId + teamName + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(teamRequest.document, teamDocument) + // Making the actual requests and ensuring they can decode to the Model types. + _ = try await Amplify.API.query(request: projectRequest) + _ = try await Amplify.API.query(request: teamRequest) + } + + func testSaveTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + } + + func testSaveProject() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, + name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + assertProjectDoesNotContainTeam(savedProject) + } + + func testSaveProjectWithTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + // Project initializer variation #1 (pass both team reference and fields in) + let project = Project(projectId: UUID().uuidString, + name: "name", + team: team, + teamId: team.teamId, + teamName: team.name) + let savedProject = try await saveAndWaitForSync(project) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + + // Project initializer variation #2 (pass only team reference) + let project2 = Project(projectId: UUID().uuidString, + name: "name", + team: team) + let savedProject2 = try await saveAndWaitForSync(project2) + let queriedProject2 = try await query(for: savedProject2) + assertProjectDoesNotContainTeam(queriedProject2) + + // Project initializer variation #3 (pass fields in) + let project3 = Project(projectId: UUID().uuidString, + name: "name", + teamId: team.teamId, + teamName: team.name) + let savedProject3 = try await saveAndWaitForSync(project3) + let queriedProject3 = try await query(for: savedProject3) + assertProject(queriedProject3, hasTeam: savedTeam) + } + + // One-to-One relationships do not create a foreign key for the Team or Project table + // So the LazyModel does not have the FK value to be instantiated as metadata for lazy loading. + // We only assert the FK fields on the Project exist and are equal to the Team's PK. + func assertProject(_ project: Project, hasTeam team: Team) { + XCTAssertEqual(project.teamId, team.teamId) + XCTAssertEqual(project.teamName, team.name) + } + + func assertProjectDoesNotContainTeam(_ project: Project) { + XCTAssertNil(project.teamId) + XCTAssertNil(project.teamName) + } + + func testSaveProjectWithTeamThenUpdate() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + assertProject(savedProject, hasTeam: savedTeam) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + let updatedProject = try await saveAndWaitForSync(project, assertVersion: 2) + assertProject(updatedProject, hasTeam: savedTeam) + } + + func testSaveProjectWithoutTeamUpdateProjectWithTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + assertProjectDoesNotContainTeam(savedProject) + + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + var queriedProject = try await query(for: savedProject) + queriedProject.teamId = team.teamId + queriedProject.teamName = team.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(savedProjectWithNewTeam, hasTeam: savedTeam) + } + + func testSaveTeamSaveProjectWithTeamUpdateProjectToNoTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.teamId = nil + queriedProject.teamName = nil + let savedProjectWithNoTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProjectDoesNotContainTeam(savedProjectWithNoTeam) + } + + func testSaveProjectWithTeamUpdateProjectToNewTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + let newTeam = Team(teamId: UUID().uuidString, name: "name") + let savedNewTeam = try await saveAndWaitForSync(newTeam) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.teamId = newTeam.teamId + queriedProject.teamName = newTeam.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(queriedProject, hasTeam: savedNewTeam) + } + + func testDeleteTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } + + func testDeleteProject() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + try await deleteAndWaitForSync(savedProject) + try await assertModelDoesNotExist(savedProject) + } + + func testDeleteProjectWithTeam() async throws { + await setup(withModels: ProjectTeam5Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = Project(projectId: UUID().uuidString, + name: "name", + team: team, + teamId: team.teamId, + teamName: team.name) + let savedProject = try await saveAndWaitForSync(project) + + try await assertModelExists(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedProject) + + try await assertModelDoesNotExist(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } +} + +extension AWSDataStoreLazyLoadProjectTeam5Tests { + + typealias Project = Project5 + typealias Team = Team5 + + struct ProjectTeam5Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Project5.self) + ModelRegistry.register(modelType: Team5.self) + } + } + + func initializeProjectWithTeam(_ team: Team) -> Project { + return Project(projectId: UUID().uuidString, + name: "name", + team: team, + teamId: team.teamId, + teamName: team.name) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5+Schema.swift new file mode 100644 index 0000000000..39a7855901 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5+Schema.swift @@ -0,0 +1,52 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Project5 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case projectId + case name + case team + case teamId + case teamName + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let project5 = Project5.keys + + model.pluralName = "Project5s" + + model.attributes( + .index(fields: ["projectId", "name"], name: nil), + .primaryKey(fields: [project5.projectId, project5.name]) + ) + + model.fields( + .field(project5.projectId, is: .required, ofType: .string), + .field(project5.name, is: .required, ofType: .string), + .hasOne(project5.team, is: .optional, ofType: Team5.self, associatedWith: Team5.keys.project, targetNames: ["teamId", "teamName"]), + .field(project5.teamId, is: .optional, ofType: .string), + .field(project5.teamName, is: .optional, ofType: .string), + .field(project5.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project5.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Project5: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Project5.IdentifierProtocol { + public static func identifier(projectId: String, + name: String) -> Self { + .make(fields:[(name: "projectId", value: projectId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5.swift new file mode 100644 index 0000000000..0d819c3382 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Project5.swift @@ -0,0 +1,77 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Project5: Model { + public let projectId: String + public let name: String + internal var _team: LazyModel + public var team: Team5? { + get async throws { + try await _team.get() + } + } + public var teamId: String? + public var teamName: String? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(projectId: String, + name: String, + team: Team5? = nil, + teamId: String? = nil, + teamName: String? = nil) { + self.init(projectId: projectId, + name: name, + team: team, + teamId: teamId, + teamName: teamName, + createdAt: nil, + updatedAt: nil) + } + internal init(projectId: String, + name: String, + team: Team5? = nil, + teamId: String? = nil, + teamName: String? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.projectId = projectId + self.name = name + self._team = LazyModel(element: team) + self.teamId = teamId + self.teamName = teamName + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setTeam(_ team: Team5) { + self._team = LazyModel(element: team) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + projectId = try values.decode(String.self, forKey: .projectId) + name = try values.decode(String.self, forKey: .name) + do { + _team = try values.decode(LazyModel.self, forKey: .team) + } catch { + _team = LazyModel(identifiers: nil) + } + teamId = try values.decode(String?.self, forKey: .teamId) + teamName = try values.decode(String?.self, forKey: .teamName) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(projectId, forKey: .projectId) + try container.encode(name, forKey: .name) + try container.encode(_team, forKey: .team) + try container.encode(teamId, forKey: .teamId) + try container.encode(teamName, forKey: .teamName) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5+Schema.swift new file mode 100644 index 0000000000..cbe640a92e --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5+Schema.swift @@ -0,0 +1,48 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Team5 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case teamId + case name + case project + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let team5 = Team5.keys + + model.pluralName = "Team5s" + + model.attributes( + .index(fields: ["teamId", "name"], name: nil), + .primaryKey(fields: [team5.teamId, team5.name]) + ) + + model.fields( + .field(team5.teamId, is: .required, ofType: .string), + .field(team5.name, is: .required, ofType: .string), + .belongsTo(team5.project, is: .optional, ofType: Project5.self, targetNames: ["projectId", "projectName"]), + .field(team5.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(team5.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Team5: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Team5.IdentifierProtocol { + public static func identifier(teamId: String, + name: String) -> Self { + .make(fields:[(name: "teamId", value: teamId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5.swift new file mode 100644 index 0000000000..a021677757 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL8/Team5.swift @@ -0,0 +1,59 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Team5: Model { + public let teamId: String + public let name: String + internal var _project: LazyModel + public var project: Project5? { + get async throws { + try await _project.get() + } + } + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(teamId: String, + name: String, + project: Project5? = nil) { + self.init(teamId: teamId, + name: name, + project: project, + createdAt: nil, + updatedAt: nil) + } + internal init(teamId: String, + name: String, + project: Project5? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.teamId = teamId + self.name = name + self._project = LazyModel(element: project) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + teamId = try values.decode(String.self, forKey: .teamId) + name = try values.decode(String.self, forKey: .name) + do { + _project = try values.decode(LazyModel.self, forKey: .project) + } catch { + _project = LazyModel(identifiers: nil) + } + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(teamId, forKey: .teamId) + try container.encode(name, forKey: .name) + try container.encode(_project, forKey: .project) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift new file mode 100644 index 0000000000..33fc3af9e3 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/AWSDataStoreLazyLoadProjectTeam6Tests.swift @@ -0,0 +1,266 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Combine +import XCTest + +@testable import Amplify +import AWSPluginsCore + +class AWSDataStoreLazyLoadProjectTeam6Tests: AWSDataStoreLazyLoadBaseTest { + + func testStart() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false, clearOnTearDown: false) + try await startAndWaitForReady() + printDBPath() + } + + func testAPISyncQuery() async throws { + await setupAPIOnly(withModels: ProjectTeam6Models()) + + // The selection set of project should include "hasOne" team, and no further + let projectRequest = GraphQLRequest.syncQuery(modelType: Project.self) + let projectDocument = """ + query SyncProject6s($limit: Int) { + syncProject6s(limit: $limit) { + items { + projectId + name + createdAt + teamId + teamName + updatedAt + team { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(projectRequest.document, projectDocument) + + // In this "Explicit Uni-directional Has One", only project hasOne team, team does not reference the project + // So, the selection set of team does not include project + let teamRequest = GraphQLRequest.syncQuery(modelType: Team.self) + let teamDocument = """ + query SyncTeam6s($limit: Int) { + syncTeam6s(limit: $limit) { + items { + teamId + name + createdAt + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + nextToken + startedAt + } + } + """ + XCTAssertEqual(teamRequest.document, teamDocument) + // Making the actual requests and ensuring they can decode to the Model types. + _ = try await Amplify.API.query(request: projectRequest) + _ = try await Amplify.API.query(request: teamRequest) + } + + func testSaveTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + } + + func testSaveProject() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, + name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + assertProjectDoesNotContainTeam(savedProject) + } + + func testSaveProjectWithTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + // Project initializer variation #1 (pass both team reference and fields in) + let project = Project(projectId: UUID().uuidString, + name: "name", + team: team, + teamId: team.teamId, + teamName: team.name) + let savedProject = try await saveAndWaitForSync(project) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + + // Project initializer variation #2 (pass only team reference) + let project2 = Project(projectId: UUID().uuidString, + name: "name", + team: team) + let savedProject2 = try await saveAndWaitForSync(project2) + let queriedProject2 = try await query(for: savedProject2) + assertProjectDoesNotContainTeam(queriedProject2) + + // Project initializer variation #3 (pass fields in) + let project3 = Project(projectId: UUID().uuidString, + name: "name", + teamId: team.teamId, + teamName: team.name) + let savedProject3 = try await saveAndWaitForSync(project3) + let queriedProject3 = try await query(for: savedProject3) + assertProject(queriedProject3, hasTeam: savedTeam) + } + + // One-to-One relationships do not create a foreign key for the Team or Project table + // So the LazyModel does not have the FK value to be instantiated as metadata for lazy loading. + // We only assert the FK fields on the Project exist and are equal to the Team's PK. + func assertProject(_ project: Project, hasTeam team: Team) { + XCTAssertEqual(project.teamId, team.teamId) + XCTAssertEqual(project.teamName, team.name) + } + + func assertProjectDoesNotContainTeam(_ project: Project) { + XCTAssertNil(project.teamId) + XCTAssertNil(project.teamName) + } + + func testSaveProjectWithTeamThenUpdate() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + assertProject(savedProject, hasTeam: savedTeam) + let queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + let updatedProject = try await saveAndWaitForSync(project, assertVersion: 2) + assertProject(updatedProject, hasTeam: savedTeam) + } + + func testSaveProjectWithoutTeamUpdateProjectWithTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + assertProjectDoesNotContainTeam(savedProject) + + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + var queriedProject = try await query(for: savedProject) + queriedProject.teamId = team.teamId + queriedProject.teamName = team.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(savedProjectWithNewTeam, hasTeam: savedTeam) + } + + func testSaveTeamSaveProjectWithTeamUpdateProjectToNoTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.teamId = nil + queriedProject.teamName = nil + let savedProjectWithNoTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProjectDoesNotContainTeam(savedProjectWithNoTeam) + } + + func testSaveProjectWithTeamUpdateProjectToNewTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + let newTeam = Team(teamId: UUID().uuidString, name: "name") + let savedNewTeam = try await saveAndWaitForSync(newTeam) + var queriedProject = try await query(for: savedProject) + assertProject(queriedProject, hasTeam: savedTeam) + queriedProject.teamId = newTeam.teamId + queriedProject.teamName = newTeam.name + let savedProjectWithNewTeam = try await saveAndWaitForSync(queriedProject, assertVersion: 2) + assertProject(queriedProject, hasTeam: savedNewTeam) + } + + func testDeleteTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + try await assertModelExists(savedTeam) + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } + + func testDeleteProject() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let project = Project(projectId: UUID().uuidString, name: "name") + let savedProject = try await saveAndWaitForSync(project) + try await assertModelExists(savedProject) + try await deleteAndWaitForSync(savedProject) + try await assertModelDoesNotExist(savedProject) + } + + func testDeleteProjectWithTeam() async throws { + await setup(withModels: ProjectTeam6Models(), eagerLoad: false) + let team = Team(teamId: UUID().uuidString, name: "name") + let savedTeam = try await saveAndWaitForSync(team) + let project = initializeProjectWithTeam(team) + let savedProject = try await saveAndWaitForSync(project) + + try await assertModelExists(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedProject) + + try await assertModelDoesNotExist(savedProject) + try await assertModelExists(savedTeam) + + try await deleteAndWaitForSync(savedTeam) + try await assertModelDoesNotExist(savedTeam) + } +} + +extension AWSDataStoreLazyLoadProjectTeam6Tests { + + typealias Project = Project6 + typealias Team = Team6 + + struct ProjectTeam6Models: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Project6.self) + ModelRegistry.register(modelType: Team6.self) + } + } + + func initializeProjectWithTeam(_ team: Team) -> Project { + return Project(projectId: UUID().uuidString, + name: "name", + team: team, + teamId: team.teamId, + teamName: team.name) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6+Schema.swift new file mode 100644 index 0000000000..6cda02ed54 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6+Schema.swift @@ -0,0 +1,52 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Project6 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case projectId + case name + case team + case teamId + case teamName + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let project6 = Project6.keys + + model.pluralName = "Project6s" + + model.attributes( + .index(fields: ["projectId", "name"], name: nil), + .primaryKey(fields: [project6.projectId, project6.name]) + ) + + model.fields( + .field(project6.projectId, is: .required, ofType: .string), + .field(project6.name, is: .required, ofType: .string), + .hasOne(project6.team, is: .optional, ofType: Team6.self, associatedWith: Team6.keys.teamId, targetNames: ["teamId", "teamName"]), + .field(project6.teamId, is: .optional, ofType: .string), + .field(project6.teamName, is: .optional, ofType: .string), + .field(project6.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(project6.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Project6: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Project6.IdentifierProtocol { + public static func identifier(projectId: String, + name: String) -> Self { + .make(fields:[(name: "projectId", value: projectId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6.swift new file mode 100644 index 0000000000..fe89dcd9e4 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Project6.swift @@ -0,0 +1,77 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Project6: Model { + public let projectId: String + public let name: String + internal var _team: LazyModel + public var team: Team6? { + get async throws { + try await _team.get() + } + } + public var teamId: String? + public var teamName: String? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(projectId: String, + name: String, + team: Team6? = nil, + teamId: String? = nil, + teamName: String? = nil) { + self.init(projectId: projectId, + name: name, + team: team, + teamId: teamId, + teamName: teamName, + createdAt: nil, + updatedAt: nil) + } + internal init(projectId: String, + name: String, + team: Team6? = nil, + teamId: String? = nil, + teamName: String? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.projectId = projectId + self.name = name + self._team = LazyModel(element: team) + self.teamId = teamId + self.teamName = teamName + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public mutating func setTeam(_ team: Team6?) { + self._team = LazyModel(element: team) + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + projectId = try values.decode(String.self, forKey: .projectId) + name = try values.decode(String.self, forKey: .name) + do { + _team = try values.decode(LazyModel.self, forKey: .team) + } catch { + _team = LazyModel(identifiers: nil) + } + teamId = try values.decode(String?.self, forKey: .teamId) + teamName = try values.decode(String?.self, forKey: .teamName) + createdAt = try values.decode(Temporal.DateTime?.self, forKey: .createdAt) + updatedAt = try values.decode(Temporal.DateTime?.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(projectId, forKey: .projectId) + try container.encode(name, forKey: .name) + try container.encode(_team, forKey: .team) + try container.encode(teamId, forKey: .teamId) + try container.encode(teamName, forKey: .teamName) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6+Schema.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6+Schema.swift new file mode 100644 index 0000000000..8556fdcbe1 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6+Schema.swift @@ -0,0 +1,46 @@ +// swiftlint:disable all +import Amplify +import Foundation + +extension Team6 { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case teamId + case name + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let team6 = Team6.keys + + model.pluralName = "Team6s" + + model.attributes( + .index(fields: ["teamId", "name"], name: nil), + .primaryKey(fields: [team6.teamId, team6.name]) + ) + + model.fields( + .field(team6.teamId, is: .required, ofType: .string), + .field(team6.name, is: .required, ofType: .string), + .field(team6.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(team6.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} + +extension Team6: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Custom + public typealias IdentifierProtocol = ModelIdentifier +} + +extension Team6.IdentifierProtocol { + public static func identifier(teamId: String, + name: String) -> Self { + .make(fields:[(name: "teamId", value: teamId), (name: "name", value: name)]) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6.swift new file mode 100644 index 0000000000..88da92235b --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LL9/Team6.swift @@ -0,0 +1,27 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Team6: Model { + public let teamId: String + public let name: String + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(teamId: String, + name: String) { + self.init(teamId: teamId, + name: name, + createdAt: nil, + updatedAt: nil) + } + internal init(teamId: String, + name: String, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.teamId = teamId + self.name = name + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} \ No newline at end of file diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AWSDataStoreLazyLoadBaseTest.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AWSDataStoreLazyLoadBaseTest.swift new file mode 100644 index 0000000000..1bf57c9bd4 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AWSDataStoreLazyLoadBaseTest.swift @@ -0,0 +1,281 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import XCTest +import Combine +@testable import AWSDataStorePlugin +import AWSPluginsCore +import AWSAPIPlugin + +@testable import Amplify + +class AWSDataStoreLazyLoadBaseTest: XCTestCase { + var requests: Set = [] + + var amplifyConfig: AmplifyConfiguration! + + var apiOnly: Bool = false + var clearOnTearDown: Bool = false + + override func setUp() { + continueAfterFailure = false + } + + override func tearDown() async throws { + if !apiOnly && clearOnTearDown { + try await clearDataStore() + } + + requests = [] + await Amplify.reset() + } + + func setupConfig() { + let basePath = "testconfiguration" + let baseFileName = "AWSDataStoreCategoryPluginLazyLoadIntegrationTests" + let configFile = "\(basePath)/\(baseFileName)-amplifyconfiguration" + + do { + amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration(forResource: configFile) + } catch { + XCTFail("Error during setup: \(error)") + } + } + + func apiEndpointName() throws -> String { + guard let apiPlugin = amplifyConfig.api?.plugins["awsAPIPlugin"], + case .object(let value) = apiPlugin else { + throw APIError.invalidConfiguration("API endpoint not found.", "Check the provided configuration") + } + return value.keys.first! + } + + /// Setup DataStore with given models + /// - Parameter models: DataStore models + func setup(withModels models: AmplifyModelRegistration, + logLevel: LogLevel = .verbose, + eagerLoad: Bool = true, + clearOnTearDown: Bool = true) async { + self.clearOnTearDown = clearOnTearDown + do { + setupConfig() + Amplify.Logging.logLevel = logLevel + + try Amplify.add(plugin: AWSDataStorePlugin( + modelRegistration: models, + configuration: .custom( + loadingStrategy: eagerLoad ? .eagerLoad : .lazyLoad))) + try Amplify.add(plugin: AWSAPIPlugin()) + try Amplify.configure(amplifyConfig) + + try await deleteMutationEvents() + } catch { + XCTFail("Error during setup: \(error)") + } + } + + func setupAPIOnly(withModels models: AmplifyModelRegistration, logLevel: LogLevel = .verbose) async { + apiOnly = true + do { + setupConfig() + Amplify.Logging.logLevel = logLevel + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: models)) + try Amplify.configure(amplifyConfig) + } catch { + XCTFail("Error during setup: \(error)") + } + } + + func deleteMutationEvents() async throws { + try await Amplify.DataStore.delete(MutationEvent.self, where: QueryPredicateConstant.all) + } + + func clearDataStore() async throws { + try await Amplify.DataStore.clear() + } + + func startAndWaitForReady() async throws { + let dataStoreReady = asyncExpectation(description: "DataStore `ready` event received") + let dataStoreEvents = HubPayload.EventName.DataStore.self + Amplify + .Hub + .publisher(for: .dataStore) + .sink { event in + if event.eventName == dataStoreEvents.ready { + Task { + await dataStoreReady.fulfill() + } + } + } + .store(in: &requests) + try await startDataStore() + await waitForExpectations([dataStoreReady], timeout: 60) + } + + func startDataStore() async throws { + try await Amplify.DataStore.start() + } + + func printDBPath() { + let dbPath = DataStoreDebugger.dbFilePath + print("DBPath: \(dbPath)") + } + + func saveAndWaitForSync(_ model: M, assertVersion: Int = 1) async throws -> M { + let modelSynced = asyncExpectation(description: "model was synced successfully") + let mutationEvents = Amplify.DataStore.observe(M.self) + Task { + for try await mutationEvent in mutationEvents { + if mutationEvent.version == assertVersion && mutationEvent.modelId == model.identifier { + await modelSynced.fulfill() + } + } + } + let savedModel = try await Amplify.DataStore.save(model) + await waitForExpectations([modelSynced], timeout: 10) + return savedModel + } + + func deleteAndWaitForSync(_ model: M) async throws { + let modelSynced = asyncExpectation(description: "model was synced successfully") + let dataStoreEvents = HubPayload.EventName.DataStore.self + Amplify + .Hub + .publisher(for: .dataStore) + .sink { event in + if event.eventName == dataStoreEvents.outboxMutationProcessed, + let outboxMutationEvent = event.data as? OutboxMutationEvent, + outboxMutationEvent.modelName == model.modelName, + outboxMutationEvent.element.deleted == true { + Task { await modelSynced.fulfill() } + + } + } + .store(in: &requests) + try await Amplify.DataStore.delete(model) + await waitForExpectations([modelSynced], timeout: 10) + } + + enum AssertListState { + case isNotLoaded(associatedId: String, associatedField: String) + case isLoaded(count: Int) + } + + func assertList(_ list: List, state: AssertListState) { + switch state { + case .isNotLoaded(let expectedAssociatedId, let expectedAssociatedField): + if case .notLoaded(let associatedId, let associatedField) = list.listProvider.getState() { + XCTAssertEqual(associatedId, expectedAssociatedId) + XCTAssertEqual(associatedField, expectedAssociatedField) + } else { + XCTFail("It should be not loaded with expected associatedId \(expectedAssociatedId) associatedField \(expectedAssociatedField)") + } + case .isLoaded(let count): + if case .loaded(let loadedList) = list.listProvider.getState() { + XCTAssertEqual(loadedList.count, count) + } else { + XCTFail("It should be loaded with expected count \(count)") + } + } + } + + enum AssertLazyModelState { + case notLoaded(identifiers: [String: String]?) + case loaded(model: M?) + } + + func assertLazyModel(_ lazyModel: LazyModel, + state: AssertLazyModelState) { + switch state { + case .notLoaded(let expectedIdentifiers): + if case .notLoaded(let identifiers) = lazyModel.modelProvider.getState() { + XCTAssertEqual(identifiers, expectedIdentifiers) + } else { + XCTFail("Should be not loaded with identifiers \(expectedIdentifiers)") + } + case .loaded(let expectedModel): + if case .loaded(let model) = lazyModel.modelProvider.getState() { + guard let expectedModel = expectedModel, let model = model else { + XCTAssertNil(model) + return + } + XCTAssertEqual(model.identifier, expectedModel.identifier) + } else { + XCTFail("Should be loaded with model \(String(describing: expectedModel))") + } + } + } + + func assertModelExists(_ model: M) async throws { + let modelExists = try await modelExists(model) + XCTAssertTrue(modelExists) + } + + func assertModelDoesNotExist(_ model: M) async throws { + let modelExists = try await modelExists(model) + XCTAssertFalse(modelExists) + } + + func modelExists(_ model: M) async throws -> Bool { + let identifierName = model.schema.primaryKey.sqlName + let queryPredicate: QueryPredicate = field(identifierName).eq(model.identifier) + + let queriedModels = try await Amplify.DataStore.query(M.self, + where: queryPredicate) + let metadataId = MutationSyncMetadata.identifier(modelName: model.modelName, + modelId: model.identifier) + guard let metadata = try await Amplify.DataStore.query(MutationSyncMetadata.self, + byId: metadataId) else { + XCTFail("Could not retrieve metadata for model \(model)") + throw "Could not retrieve metadata for model \(model)" + } + + return !(metadata.deleted && queriedModels.isEmpty) + } + + func query(for model: M) async throws -> M { + let identifierName = model.schema.primaryKey.sqlName + let queryPredicate: QueryPredicate = field(identifierName).eq(model.identifier) + + let queriedModels = try await Amplify.DataStore.query(M.self, + where: queryPredicate) + if queriedModels.count > 1 { + XCTFail("Expected to find one model, found \(queriedModels.count). \(queriedModels)") + throw "Expected to find one model, found \(queriedModels.count). \(queriedModels)" + } + if let queriedModel = queriedModels.first { + return queriedModel + } else { + throw "Expected to find one model, found none" + } + } +} + +struct DataStoreDebugger { + + static var dbFilePath: URL? { getAdapter()?.dbFilePath } + + static func getAdapter() -> SQLiteStorageEngineAdapter? { + if let dataStorePlugin = tryGetPlugin(), + let storageEngine = dataStorePlugin.storageEngine as? StorageEngine, + let adapter = storageEngine.storageAdapter as? SQLiteStorageEngineAdapter { + return adapter + } + + print("Could not get `SQLiteStorageEngineAdapter` from DataStore") + return nil + } + + static func tryGetPlugin() -> AWSDataStorePlugin? { + do { + return try Amplify.DataStore.getPlugin(for: "awsDataStorePlugin") as? AWSDataStorePlugin + } catch { + return nil + } + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AmplifyModels.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AmplifyModels.swift new file mode 100644 index 0000000000..fa01b38420 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/AmplifyModels.swift @@ -0,0 +1,36 @@ +// swiftlint:disable all +import Amplify +import Foundation + +// Contains the set of classes that conforms to the `Model` protocol. + +final public class AmplifyModels: AmplifyModelRegistration { + public let version: String = "248b0ffa3d44f144554beab3bf489b20" + + public func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: Post4V2.self) + ModelRegistry.register(modelType: Comment4V2.self) + ModelRegistry.register(modelType: Blog8V2.self) + ModelRegistry.register(modelType: Post8V2.self) + ModelRegistry.register(modelType: Comment8V2.self) + ModelRegistry.register(modelType: PostWithCompositeKey.self) + ModelRegistry.register(modelType: CommentWithCompositeKey.self) + ModelRegistry.register(modelType: PostWithTagsCompositeKey.self) + ModelRegistry.register(modelType: TagWithCompositeKey.self) + ModelRegistry.register(modelType: Project1.self) + ModelRegistry.register(modelType: Team1.self) + ModelRegistry.register(modelType: Project2.self) + ModelRegistry.register(modelType: Team2.self) + ModelRegistry.register(modelType: Post4.self) + ModelRegistry.register(modelType: Comment4.self) + ModelRegistry.register(modelType: Project5.self) + ModelRegistry.register(modelType: Team5.self) + ModelRegistry.register(modelType: Project6.self) + ModelRegistry.register(modelType: Team6.self) + ModelRegistry.register(modelType: Post7.self) + ModelRegistry.register(modelType: Comment7.self) + ModelRegistry.register(modelType: Post8.self) + ModelRegistry.register(modelType: Comment8.self) + ModelRegistry.register(modelType: PostTagsWithCompositeKey.self) + } +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/README.md b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/README.md new file mode 100644 index 0000000000..57f3859fe8 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/README.md @@ -0,0 +1,32 @@ +## DataStore Lazy Load Integration Tests + +### Prerequisites +- AWS CLI +- Version used: `amplify -v` => `` + +### Set-up + +1. `amplify init` + +These tests were provisioned with V2 Transform:, and updates to `cli.json` +- "respectprimarykeyattributesonconnectionfield": true +- "TODOlazyLoadiOS": true + +2. `amplify add api` + +- Choose conflict resolution for DataStore +- Use API Key +- Use the `lazyload-schema.graphql` from this test directory. + +3. `amplify push` + +```perl +? Are you sure you want to continue? `Yes` +? Do you want to generate code for your newly created GraphQL API `No` +``` + +4. Copy `amplifyconfiguration.json` to a new file named `AWSDataStoreCategoryPluginLazyLoadIntegrationTests-amplifyconfiguration.json` inside `~/.aws-amplify/amplify-ios/testconfiguration/` + +```perl +cp amplifyconfiguration.json ~/.aws-amplify/amplify-ios/testconfiguration/AWSDataStoreCategoryPluginLazyLoadIntegrationTests-amplifyconfiguration.json +``` diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/TestConfigHelper.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/TestConfigHelper.swift new file mode 100644 index 0000000000..98a66edda4 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/TestConfigHelper.swift @@ -0,0 +1,44 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +@testable import Amplify + +extension String: Error { } + +class TestConfigHelper { + + static func retrieveAmplifyConfiguration(forResource: String) throws -> AmplifyConfiguration { + + let data = try retrieve(forResource: forResource) + return try AmplifyConfiguration.decodeAmplifyConfiguration(from: data) + } + + static func retrieveCredentials(forResource: String) throws -> [String: String] { + let data = try retrieve(forResource: forResource) + + let jsonOptional = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] + guard let json = jsonOptional else { + throw "Could not deserialize `\(forResource)` into JSON object" + } + + return json + } + + static func retrieve(forResource: String) throws -> Data { + guard let path = Bundle(for: self).path(forResource: forResource, ofType: "json") else { + throw "Could not retrieve configuration file: \(forResource)" + } + + let url = URL(fileURLWithPath: path) + return try Data(contentsOf: url) + } +} + +class TestCommonConstants { + static let networkTimeout = TimeInterval(10) +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/lazyload-schema.graphql b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/lazyload-schema.graphql new file mode 100644 index 0000000000..1adc3ccd47 --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginLazyLoadTests/LazyLoadBase/lazyload-schema.graphql @@ -0,0 +1,260 @@ +# This "input" configures a global authorization rule to enable public access to +# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules +input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY! + + +# LL.1. Explicit Bi-Directional Belongs-to Has-many PostComment4V2 +# 11 Explicit Bi-Directional Belongs to Relationship + +type Post4V2 @model @auth(rules: [{allow: public}]) { + id: ID! + title: String! + comments: [Comment4V2] @hasMany(indexName: "byPost4", fields: ["id"]) +} + +type Comment4V2 @model @auth(rules: [{allow: public}]) { + id: ID! + postID: ID @index(name: "byPost4", sortKeyFields: ["content"]) + content: String! + post: Post4V2 @belongsTo(fields: ["postID"]) +} + +# LL.2. BlogPostComment +# 15 +# This is to address optional associations use case (Post can exist without a Blog) +# See issue https://github.com/aws-amplify/amplify-ios/issues/1792 for more details + +type Blog8V2 @model { + id: ID! + name: String! + customs: [MyCustomModel8] + notes: [String] + posts: [Post8V2] @hasMany(indexName: "postByBlog", fields: ["id"]) +} + +type Post8V2 @model { + id: ID! + name: String! + blogId: ID @index(name: "postByBlog") + randomId: String @index(name: "byRandom") + blog: Blog8V2 @belongsTo(fields: ["blogId"]) + comments: [Comment8V2] @hasMany(indexName: "commentByPost", fields: ["id"]) +} + +type Comment8V2 @model { + id: ID! + content: String + postId: ID @index(name: "commentByPost") + post: Post8V2 @belongsTo(fields: ["postId"]) +} + +type MyCustomModel8 { + id: ID! + name: String! + desc: String + children: [MyNestedModel8] +} + +type MyNestedModel8 { + id: ID! + nestedName: String! + notes: [String] +} + +# LL.3. Has-Many/Belongs-To With Composite Key +# iOS.7. A Has-Many/Belongs-To relationship, each with a composite key +# Post with `id` and `title`, Comment with `id` and `content` + +type PostWithCompositeKey @model { + id: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + comments: [CommentWithCompositeKey] @hasMany +} + +type CommentWithCompositeKey @model { + id: ID! @primaryKey(sortKeyFields: ["content"]) + content: String! + post: PostWithCompositeKey @belongsTo +} + +# LL.4. Many-To-Many relationship With Composite Key +# iOS.8. A Many-To-Many relationship, each with a composite key +# Post with `id` and `title`, Tag with `id` and `name` + +type PostWithTagsCompositeKey @model { + postId: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + tags: [TagWithCompositeKey] @manyToMany(relationName: "PostTagsWithCompositeKey") +} + +type TagWithCompositeKey @model { + id: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + posts: [PostWithTagsCompositeKey] @manyToMany(relationName: "PostTagsWithCompositeKey") +} + +# LL.5. Implicit Bi-directional Has One +# CLI.1. Implicit Bi-directional Has One + +type Project1 @model { + projectId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + team: Team1 @hasOne +} +type Team1 @model { + teamId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + project: Project1 @belongsTo +} + +# LL.6. Implicit Uni-directional Has One +# CLI.2. Implicit Uni-directional Has One + +type Project2 @model { + projectId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + team: Team2 @hasOne +} +type Team2 @model { + teamId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! +} + +# LL.7. Implicit Uni-directional Has Many +# CLI.4. Implicit Uni-directional Has Many + +type Post4 @model { + postId: ID! @primaryKey(sortKeyFields:["title"]) + title: String! + comments: [Comment4] @hasMany +} +type Comment4 @model { + commentId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! +} + +# LL.8. Explicit Bi-directional Has One +# CLI.5. Explicit Bi-directional Has One + +type Project5 @model { + projectId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + team: Team5 @hasOne(fields:["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} +type Team5 @model { + teamId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + project: Project5 @belongsTo(fields:["projectId", "projectName"]) + projectId: ID # customized foreign key for parent primary key + projectName: String # customized foreign key for parent sort key +} + +# LL.9. Explicit Uni-directional Has One +# CLI.6. Explicit Uni-directional Has One + +type Project6 @model { + projectId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! + team: Team6 @hasOne(fields:["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} +type Team6 @model { + teamId: ID! @primaryKey(sortKeyFields:["name"]) + name: String! +} + +# LL.10. Explicit Bi-directional Has Many +# CLI.7. Explicit Bi-directional Has Many + +type Post7 @model { + postId: ID! @primaryKey(sortKeyFields:["title"]) + title: String! + comments: [Comment7] @hasMany(indexName:"byPost", fields:["postId", "title"]) +} +type Comment7 @model { + commentId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + post: Post7 @belongsTo(fields:["postId", "postTitle"]) + postId: ID @index(name: "byPost", sortKeyFields:["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} + +# LL.11. Explicit Uni-directional Has Many +# CLI.8. Explicit Uni-directional Has Many + +type Post8 @model { + postId: ID! @primaryKey(sortKeyFields:["title"]) + title: String! + comments: [Comment8] @hasMany(indexName:"byPost", fields:["postId", "title"]) +} +type Comment8 @model { + commentId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + postId: ID @index(name: "byPost", sortKeyFields:["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} + +# LL.12. JS Schema + +type HasOneParent @model { + id: ID! @primaryKey + child: HasOneChild @hasOne +} + +type HasOneChild @model { + id: ID! @primaryKey + content: String +} + +type DefaultPKParent @model { + id: ID! @primaryKey + content: String + children: [DefaultPKChild] @hasMany +} + +type DefaultPKChild @model { + id: ID! @primaryKey + content: String + parent: DefaultPKParent @belongsTo +} + +type CompositePKParent @model { + customId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + children: [CompositePKChild] @hasMany(indexName:"byParent", fields:["customId", "content"]) + implicitChildren: [ImplicitChild] @hasMany + strangeChildren: [StrangeExplicitChild] @hasMany(indexName: "byCompositePKParentX", fields: ["customId", "content"]) + childrenSansBelongsTo: [ChildSansBelongsTo] @hasMany +} + +type CompositePKChild @model { + childId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + parent: CompositePKParent @belongsTo(fields:["parentId", "parentTitle"]) + parentId: ID @index(name: "byParent", sortKeyFields:["parentTitle"]) + parentTitle: String +} + +type ImplicitChild @model { + childId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + parent: CompositePKParent! @belongsTo +} + +type StrangeExplicitChild @model { + strangeId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + parent: CompositePKParent! @belongsTo(fields:["strangeParentId", "strangeParentTitle"]) + strangeParentId: ID @index(name: "byCompositePKParentX", sortKeyFields:["strangeParentTitle"]) + strangeParentTitle: String # customized foreign key for parent sort key +} + +type ChildSansBelongsTo @model { + childId: ID! @primaryKey(sortKeyFields:["content"]) + content: String! + compositePKParentChildrenSansBelongsToCustomId: ID! @index(name: "byParent", sortKeyFields: ["compositePKParentChildrenSansBelongsToContent"]) + compositePKParentChildrenSansBelongsToContent: String +} diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/project.pbxproj index 59c2654616..09e9c58f5c 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/project.pbxproj @@ -153,6 +153,92 @@ 213DBC6128A5808400B30280 /* TodoIAMPrivate+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BBFA8B289BFE4E00B32A39 /* TodoIAMPrivate+Schema.swift */; }; 213DBC6228A5808400B30280 /* TodoIAMPublic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BBFA8D289BFE4E00B32A39 /* TodoIAMPublic.swift */; }; 213DBC6328A5841400B30280 /* HubListenerTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BBFE13289C073100B32A39 /* HubListenerTestUtilities.swift */; }; + 217AA7BD2909C1D00042CD5D /* AWSDataStoreLazyLoadProjectTeam5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217AA7BC2909C1D00042CD5D /* AWSDataStoreLazyLoadProjectTeam5Tests.swift */; }; + 217AA7BF2909C6330042CD5D /* AWSDataStoreLazyLoadProjectTeam6Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217AA7BE2909C6330042CD5D /* AWSDataStoreLazyLoadProjectTeam6Tests.swift */; }; + 217AA7C12909ED420042CD5D /* AWSDataStoreLazyLoadPostComment7Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217AA7C02909ED420042CD5D /* AWSDataStoreLazyLoadPostComment7Tests.swift */; }; + 217AA7C32909EF050042CD5D /* AWSDataStoreLazyLoadPostComment8Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217AA7C22909EF050042CD5D /* AWSDataStoreLazyLoadPostComment8Tests.swift */; }; + 217AA7C52909F6730042CD5D /* AWSDataStoreLazyLoadPostComment4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217AA7C42909F6730042CD5D /* AWSDataStoreLazyLoadPostComment4Tests.swift */; }; + 21801C7D28F9A22900FFA37E /* AWSDataStoreLazyLoadBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801C7C28F9A22900FFA37E /* AWSDataStoreLazyLoadBaseTest.swift */; }; + 21801C8728F9A38000FFA37E /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801C8628F9A37F00FFA37E /* TestConfigHelper.swift */; }; + 21801CD428F9A86800FFA37E /* PostWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA328F9A86600FFA37E /* PostWithCompositeKey.swift */; }; + 21801CD528F9A86800FFA37E /* CommentWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA428F9A86600FFA37E /* CommentWithCompositeKey+Schema.swift */; }; + 21801CD628F9A86800FFA37E /* PostWithTagsCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA528F9A86600FFA37E /* PostWithTagsCompositeKey+Schema.swift */; }; + 21801CD728F9A86800FFA37E /* CommentWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA628F9A86600FFA37E /* CommentWithCompositeKey.swift */; }; + 21801CD828F9A86800FFA37E /* Comment7+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA728F9A86600FFA37E /* Comment7+Schema.swift */; }; + 21801CD928F9A86800FFA37E /* Post8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA828F9A86600FFA37E /* Post8.swift */; }; + 21801CDA28F9A86800FFA37E /* PostWithTagsCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CA928F9A86600FFA37E /* PostWithTagsCompositeKey.swift */; }; + 21801CDB28F9A86800FFA37E /* MyNestedModel8+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAA28F9A86600FFA37E /* MyNestedModel8+Schema.swift */; }; + 21801CDC28F9A86800FFA37E /* PostTagsWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAB28F9A86600FFA37E /* PostTagsWithCompositeKey.swift */; }; + 21801CDD28F9A86800FFA37E /* Comment8V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAC28F9A86600FFA37E /* Comment8V2+Schema.swift */; }; + 21801CDE28F9A86800FFA37E /* Post4+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAD28F9A86600FFA37E /* Post4+Schema.swift */; }; + 21801CDF28F9A86800FFA37E /* MyCustomModel8+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAE28F9A86600FFA37E /* MyCustomModel8+Schema.swift */; }; + 21801CE028F9A86800FFA37E /* Post8V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CAF28F9A86600FFA37E /* Post8V2.swift */; }; + 21801CE328F9A86800FFA37E /* Project6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB228F9A86600FFA37E /* Project6.swift */; }; + 21801CE428F9A86800FFA37E /* Blog8V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB328F9A86700FFA37E /* Blog8V2.swift */; }; + 21801CE528F9A86800FFA37E /* PostTagsWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB428F9A86700FFA37E /* PostTagsWithCompositeKey+Schema.swift */; }; + 21801CE628F9A86800FFA37E /* TagWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB528F9A86700FFA37E /* TagWithCompositeKey+Schema.swift */; }; + 21801CE728F9A86800FFA37E /* Project2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB628F9A86700FFA37E /* Project2.swift */; }; + 21801CE828F9A86800FFA37E /* Comment4V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB728F9A86700FFA37E /* Comment4V2.swift */; }; + 21801CE928F9A86800FFA37E /* Post8+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB828F9A86700FFA37E /* Post8+Schema.swift */; }; + 21801CEA28F9A86800FFA37E /* Comment8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CB928F9A86700FFA37E /* Comment8.swift */; }; + 21801CEB28F9A86800FFA37E /* Post4V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CBA28F9A86700FFA37E /* Post4V2.swift */; }; + 21801CEC28F9A86800FFA37E /* Team6+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CBB28F9A86700FFA37E /* Team6+Schema.swift */; }; + 21801CED28F9A86800FFA37E /* Post4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CBC28F9A86700FFA37E /* Post4.swift */; }; + 21801CEE28F9A86800FFA37E /* Comment4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CBD28F9A86700FFA37E /* Comment4.swift */; }; + 21801CEF28F9A86800FFA37E /* Blog8V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CBE28F9A86700FFA37E /* Blog8V2+Schema.swift */; }; + 21801CF128F9A86800FFA37E /* Project2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC028F9A86700FFA37E /* Project2+Schema.swift */; }; + 21801CF228F9A86800FFA37E /* Post4V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC128F9A86700FFA37E /* Post4V2+Schema.swift */; }; + 21801CF328F9A86800FFA37E /* Project6+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC228F9A86700FFA37E /* Project6+Schema.swift */; }; + 21801CF428F9A86800FFA37E /* Post7.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC328F9A86700FFA37E /* Post7.swift */; }; + 21801CF528F9A86800FFA37E /* Comment7.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC428F9A86700FFA37E /* Comment7.swift */; }; + 21801CF628F9A86800FFA37E /* Comment4+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC528F9A86700FFA37E /* Comment4+Schema.swift */; }; + 21801CF728F9A86800FFA37E /* Post8V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC628F9A86700FFA37E /* Post8V2+Schema.swift */; }; + 21801CF828F9A86800FFA37E /* PostWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC728F9A86800FFA37E /* PostWithCompositeKey+Schema.swift */; }; + 21801CF928F9A86800FFA37E /* Team6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC828F9A86800FFA37E /* Team6.swift */; }; + 21801CFA28F9A86800FFA37E /* TagWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CC928F9A86800FFA37E /* TagWithCompositeKey.swift */; }; + 21801CFB28F9A86800FFA37E /* Team2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CCA28F9A86800FFA37E /* Team2+Schema.swift */; }; + 21801CFC28F9A86800FFA37E /* Comment4V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CCB28F9A86800FFA37E /* Comment4V2+Schema.swift */; }; + 21801CFD28F9A86800FFA37E /* MyCustomModel8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CCC28F9A86800FFA37E /* MyCustomModel8.swift */; }; + 21801CFE28F9A86800FFA37E /* Comment8V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CCD28F9A86800FFA37E /* Comment8V2.swift */; }; + 21801CFF28F9A86800FFA37E /* Team2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CCE28F9A86800FFA37E /* Team2.swift */; }; + 21801D0128F9A86800FFA37E /* AmplifyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CD028F9A86800FFA37E /* AmplifyModels.swift */; }; + 21801D0228F9A86800FFA37E /* Comment8+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CD128F9A86800FFA37E /* Comment8+Schema.swift */; }; + 21801D0328F9A86800FFA37E /* Post7+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CD228F9A86800FFA37E /* Post7+Schema.swift */; }; + 21801D0428F9A86800FFA37E /* MyNestedModel8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801CD328F9A86800FFA37E /* MyNestedModel8.swift */; }; + 21801D0628F9AE9400FFA37E /* AWSDataStoreLazyLoadPostComment4V2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D0528F9AE9400FFA37E /* AWSDataStoreLazyLoadPostComment4V2Tests.swift */; }; + 21801D0728F9B11800FFA37E /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE8028E746C10000C36A /* AsyncTesting.swift */; }; + 21801D0828F9B11B00FFA37E /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE8128E746C10000C36A /* AsyncExpectation.swift */; }; + 21801D0928F9B11E00FFA37E /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE8228E746C10000C36A /* XCTestCase+AsyncTesting.swift */; }; + 21801D2528FDA5E700FFA37E /* AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D2428FDA5E700FFA37E /* AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift */; }; + 21801D2A29006DA300FFA37E /* Project1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D2829006DA100FFA37E /* Project1.swift */; }; + 21801D2B29006DA300FFA37E /* Project1+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D2929006DA300FFA37E /* Project1+Schema.swift */; }; + 21801D2E29006DA900FFA37E /* Team1+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D2C29006DA800FFA37E /* Team1+Schema.swift */; }; + 21801D2F29006DA900FFA37E /* Team1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D2D29006DA800FFA37E /* Team1.swift */; }; + 21801D3229006DB500FFA37E /* Team5+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D3029006DB400FFA37E /* Team5+Schema.swift */; }; + 21801D3329006DB500FFA37E /* Team5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D3129006DB500FFA37E /* Team5.swift */; }; + 21801D3629006DC200FFA37E /* Project5+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D3429006DC000FFA37E /* Project5+Schema.swift */; }; + 21801D3729006DC200FFA37E /* Project5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D3529006DC200FFA37E /* Project5.swift */; }; + 21801D3E2900A10600FFA37E /* AWSDataStoreLazyLoadProjectTeam1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D3D2900A10600FFA37E /* AWSDataStoreLazyLoadProjectTeam1Tests.swift */; }; + 21801D4A2906CF3B00FFA37E /* AWSDataStoreLazyLoadPostTagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D492906CF3B00FFA37E /* AWSDataStoreLazyLoadPostTagTests.swift */; }; + 21801D5E29097D5800FFA37E /* StrangeExplicitChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D4C29097D5400FFA37E /* StrangeExplicitChild.swift */; }; + 21801D5F29097D5800FFA37E /* HasOneChild+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D4D29097D5600FFA37E /* HasOneChild+Schema.swift */; }; + 21801D6029097D5800FFA37E /* ImplicitChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D4E29097D5600FFA37E /* ImplicitChild.swift */; }; + 21801D6129097D5800FFA37E /* HasOneParent+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D4F29097D5600FFA37E /* HasOneParent+Schema.swift */; }; + 21801D6229097D5800FFA37E /* CompositePKChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5029097D5600FFA37E /* CompositePKChild.swift */; }; + 21801D6329097D5800FFA37E /* StrangeExplicitChild+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5129097D5600FFA37E /* StrangeExplicitChild+Schema.swift */; }; + 21801D6429097D5800FFA37E /* CompositePKChild+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5229097D5600FFA37E /* CompositePKChild+Schema.swift */; }; + 21801D6529097D5800FFA37E /* DefaultPKParent+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5329097D5600FFA37E /* DefaultPKParent+Schema.swift */; }; + 21801D6629097D5800FFA37E /* DefaultPKChild+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5429097D5600FFA37E /* DefaultPKChild+Schema.swift */; }; + 21801D6729097D5800FFA37E /* CompositePKParent+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5529097D5600FFA37E /* CompositePKParent+Schema.swift */; }; + 21801D6829097D5800FFA37E /* DefaultPKChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5629097D5600FFA37E /* DefaultPKChild.swift */; }; + 21801D6929097D5800FFA37E /* CompositePKParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5729097D5600FFA37E /* CompositePKParent.swift */; }; + 21801D6A29097D5800FFA37E /* ChildSansBelongsTo+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5829097D5700FFA37E /* ChildSansBelongsTo+Schema.swift */; }; + 21801D6B29097D5800FFA37E /* HasOneParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5929097D5700FFA37E /* HasOneParent.swift */; }; + 21801D6C29097D5800FFA37E /* DefaultPKParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5A29097D5700FFA37E /* DefaultPKParent.swift */; }; + 21801D6D29097D5800FFA37E /* ImplicitChild+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5B29097D5700FFA37E /* ImplicitChild+Schema.swift */; }; + 21801D6E29097D5800FFA37E /* HasOneChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5C29097D5700FFA37E /* HasOneChild.swift */; }; + 21801D6F29097D5800FFA37E /* ChildSansBelongsTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D5D29097D5700FFA37E /* ChildSansBelongsTo.swift */; }; + 21801D7129097E9400FFA37E /* AWSDataStoreLazyLoadProjectTeam2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21801D7029097E9200FFA37E /* AWSDataStoreLazyLoadProjectTeam2Tests.swift */; }; 219253BE28BFE84100820737 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 219253BD28BFE84000820737 /* XCTest.framework */; }; 21977DBF289C171A005B49D6 /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21977DBE289C1719005B49D6 /* TestConfigHelper.swift */; }; 219B518528E3A4B00080EDCC /* DataStoreConnectionOptionalAssociations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BBFA00289BFE3400B32A39 /* DataStoreConnectionOptionalAssociations.swift */; }; @@ -540,6 +626,13 @@ remoteGlobalIDString = 2118212B289BFB4B001B5945; remoteInfo = DataStoreHostApp; }; + 21801C7E28F9A22900FFA37E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 21182124289BFB4B001B5945 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2118212B289BFB4B001B5945; + remoteInfo = DataStoreHostApp; + }; 21BBFB45289BFF6A00B32A39 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 21182124289BFB4B001B5945 /* Project object */; @@ -644,6 +737,92 @@ 213DBC5828A57FFE00B30280 /* TestConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; 213DBC5A28A5800F00B30280 /* TestConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; 213DBC5C28A5801900B30280 /* TestConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; + 217AA7BC2909C1D00042CD5D /* AWSDataStoreLazyLoadProjectTeam5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadProjectTeam5Tests.swift; sourceTree = ""; }; + 217AA7BE2909C6330042CD5D /* AWSDataStoreLazyLoadProjectTeam6Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadProjectTeam6Tests.swift; sourceTree = ""; }; + 217AA7C02909ED420042CD5D /* AWSDataStoreLazyLoadPostComment7Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostComment7Tests.swift; sourceTree = ""; }; + 217AA7C22909EF050042CD5D /* AWSDataStoreLazyLoadPostComment8Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostComment8Tests.swift; sourceTree = ""; }; + 217AA7C42909F6730042CD5D /* AWSDataStoreLazyLoadPostComment4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostComment4Tests.swift; sourceTree = ""; }; + 21801C7A28F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSDataStorePluginLazyLoadTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 21801C7C28F9A22900FFA37E /* AWSDataStoreLazyLoadBaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadBaseTest.swift; sourceTree = ""; }; + 21801C8428F9A2DC00FFA37E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 21801C8528F9A36D00FFA37E /* lazyload-schema.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = "lazyload-schema.graphql"; sourceTree = ""; }; + 21801C8628F9A37F00FFA37E /* TestConfigHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; + 21801CA328F9A86600FFA37E /* PostWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostWithCompositeKey.swift; sourceTree = ""; }; + 21801CA428F9A86600FFA37E /* CommentWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CommentWithCompositeKey+Schema.swift"; sourceTree = ""; }; + 21801CA528F9A86600FFA37E /* PostWithTagsCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PostWithTagsCompositeKey+Schema.swift"; sourceTree = ""; }; + 21801CA628F9A86600FFA37E /* CommentWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentWithCompositeKey.swift; sourceTree = ""; }; + 21801CA728F9A86600FFA37E /* Comment7+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment7+Schema.swift"; sourceTree = ""; }; + 21801CA828F9A86600FFA37E /* Post8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post8.swift; sourceTree = ""; }; + 21801CA928F9A86600FFA37E /* PostWithTagsCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostWithTagsCompositeKey.swift; sourceTree = ""; }; + 21801CAA28F9A86600FFA37E /* MyNestedModel8+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MyNestedModel8+Schema.swift"; sourceTree = ""; }; + 21801CAB28F9A86600FFA37E /* PostTagsWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostTagsWithCompositeKey.swift; sourceTree = ""; }; + 21801CAC28F9A86600FFA37E /* Comment8V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment8V2+Schema.swift"; sourceTree = ""; }; + 21801CAD28F9A86600FFA37E /* Post4+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post4+Schema.swift"; sourceTree = ""; }; + 21801CAE28F9A86600FFA37E /* MyCustomModel8+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MyCustomModel8+Schema.swift"; sourceTree = ""; }; + 21801CAF28F9A86600FFA37E /* Post8V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post8V2.swift; sourceTree = ""; }; + 21801CB228F9A86600FFA37E /* Project6.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project6.swift; sourceTree = ""; }; + 21801CB328F9A86700FFA37E /* Blog8V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blog8V2.swift; sourceTree = ""; }; + 21801CB428F9A86700FFA37E /* PostTagsWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PostTagsWithCompositeKey+Schema.swift"; sourceTree = ""; }; + 21801CB528F9A86700FFA37E /* TagWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TagWithCompositeKey+Schema.swift"; sourceTree = ""; }; + 21801CB628F9A86700FFA37E /* Project2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project2.swift; sourceTree = ""; }; + 21801CB728F9A86700FFA37E /* Comment4V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment4V2.swift; sourceTree = ""; }; + 21801CB828F9A86700FFA37E /* Post8+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post8+Schema.swift"; sourceTree = ""; }; + 21801CB928F9A86700FFA37E /* Comment8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment8.swift; sourceTree = ""; }; + 21801CBA28F9A86700FFA37E /* Post4V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post4V2.swift; sourceTree = ""; }; + 21801CBB28F9A86700FFA37E /* Team6+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Team6+Schema.swift"; sourceTree = ""; }; + 21801CBC28F9A86700FFA37E /* Post4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post4.swift; sourceTree = ""; }; + 21801CBD28F9A86700FFA37E /* Comment4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment4.swift; sourceTree = ""; }; + 21801CBE28F9A86700FFA37E /* Blog8V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Blog8V2+Schema.swift"; sourceTree = ""; }; + 21801CC028F9A86700FFA37E /* Project2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Project2+Schema.swift"; sourceTree = ""; }; + 21801CC128F9A86700FFA37E /* Post4V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post4V2+Schema.swift"; sourceTree = ""; }; + 21801CC228F9A86700FFA37E /* Project6+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Project6+Schema.swift"; sourceTree = ""; }; + 21801CC328F9A86700FFA37E /* Post7.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post7.swift; sourceTree = ""; }; + 21801CC428F9A86700FFA37E /* Comment7.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment7.swift; sourceTree = ""; }; + 21801CC528F9A86700FFA37E /* Comment4+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment4+Schema.swift"; sourceTree = ""; }; + 21801CC628F9A86700FFA37E /* Post8V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post8V2+Schema.swift"; sourceTree = ""; }; + 21801CC728F9A86800FFA37E /* PostWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PostWithCompositeKey+Schema.swift"; sourceTree = ""; }; + 21801CC828F9A86800FFA37E /* Team6.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team6.swift; sourceTree = ""; }; + 21801CC928F9A86800FFA37E /* TagWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagWithCompositeKey.swift; sourceTree = ""; }; + 21801CCA28F9A86800FFA37E /* Team2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Team2+Schema.swift"; sourceTree = ""; }; + 21801CCB28F9A86800FFA37E /* Comment4V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment4V2+Schema.swift"; sourceTree = ""; }; + 21801CCC28F9A86800FFA37E /* MyCustomModel8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCustomModel8.swift; sourceTree = ""; }; + 21801CCD28F9A86800FFA37E /* Comment8V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment8V2.swift; sourceTree = ""; }; + 21801CCE28F9A86800FFA37E /* Team2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team2.swift; sourceTree = ""; }; + 21801CD028F9A86800FFA37E /* AmplifyModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmplifyModels.swift; sourceTree = ""; }; + 21801CD128F9A86800FFA37E /* Comment8+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment8+Schema.swift"; sourceTree = ""; }; + 21801CD228F9A86800FFA37E /* Post7+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post7+Schema.swift"; sourceTree = ""; }; + 21801CD328F9A86800FFA37E /* MyNestedModel8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyNestedModel8.swift; sourceTree = ""; }; + 21801D0528F9AE9400FFA37E /* AWSDataStoreLazyLoadPostComment4V2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostComment4V2Tests.swift; sourceTree = ""; }; + 21801D2428FDA5E700FFA37E /* AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift; sourceTree = ""; }; + 21801D2829006DA100FFA37E /* Project1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project1.swift; sourceTree = ""; }; + 21801D2929006DA300FFA37E /* Project1+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Project1+Schema.swift"; sourceTree = ""; }; + 21801D2C29006DA800FFA37E /* Team1+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Team1+Schema.swift"; sourceTree = ""; }; + 21801D2D29006DA800FFA37E /* Team1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team1.swift; sourceTree = ""; }; + 21801D3029006DB400FFA37E /* Team5+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Team5+Schema.swift"; sourceTree = ""; }; + 21801D3129006DB500FFA37E /* Team5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team5.swift; sourceTree = ""; }; + 21801D3429006DC000FFA37E /* Project5+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Project5+Schema.swift"; sourceTree = ""; }; + 21801D3529006DC200FFA37E /* Project5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project5.swift; sourceTree = ""; }; + 21801D3D2900A10600FFA37E /* AWSDataStoreLazyLoadProjectTeam1Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadProjectTeam1Tests.swift; sourceTree = ""; }; + 21801D492906CF3B00FFA37E /* AWSDataStoreLazyLoadPostTagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadPostTagTests.swift; sourceTree = ""; }; + 21801D4C29097D5400FFA37E /* StrangeExplicitChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrangeExplicitChild.swift; sourceTree = ""; }; + 21801D4D29097D5600FFA37E /* HasOneChild+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HasOneChild+Schema.swift"; sourceTree = ""; }; + 21801D4E29097D5600FFA37E /* ImplicitChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitChild.swift; sourceTree = ""; }; + 21801D4F29097D5600FFA37E /* HasOneParent+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HasOneParent+Schema.swift"; sourceTree = ""; }; + 21801D5029097D5600FFA37E /* CompositePKChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositePKChild.swift; sourceTree = ""; }; + 21801D5129097D5600FFA37E /* StrangeExplicitChild+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StrangeExplicitChild+Schema.swift"; sourceTree = ""; }; + 21801D5229097D5600FFA37E /* CompositePKChild+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CompositePKChild+Schema.swift"; sourceTree = ""; }; + 21801D5329097D5600FFA37E /* DefaultPKParent+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultPKParent+Schema.swift"; sourceTree = ""; }; + 21801D5429097D5600FFA37E /* DefaultPKChild+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultPKChild+Schema.swift"; sourceTree = ""; }; + 21801D5529097D5600FFA37E /* CompositePKParent+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CompositePKParent+Schema.swift"; sourceTree = ""; }; + 21801D5629097D5600FFA37E /* DefaultPKChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultPKChild.swift; sourceTree = ""; }; + 21801D5729097D5600FFA37E /* CompositePKParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositePKParent.swift; sourceTree = ""; }; + 21801D5829097D5700FFA37E /* ChildSansBelongsTo+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChildSansBelongsTo+Schema.swift"; sourceTree = ""; }; + 21801D5929097D5700FFA37E /* HasOneParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HasOneParent.swift; sourceTree = ""; }; + 21801D5A29097D5700FFA37E /* DefaultPKParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultPKParent.swift; sourceTree = ""; }; + 21801D5B29097D5700FFA37E /* ImplicitChild+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImplicitChild+Schema.swift"; sourceTree = ""; }; + 21801D5C29097D5700FFA37E /* HasOneChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HasOneChild.swift; sourceTree = ""; }; + 21801D5D29097D5700FFA37E /* ChildSansBelongsTo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChildSansBelongsTo.swift; sourceTree = ""; }; + 21801D7029097E9200FFA37E /* AWSDataStoreLazyLoadProjectTeam2Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSDataStoreLazyLoadProjectTeam2Tests.swift; sourceTree = ""; }; 219253BD28BFE84000820737 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 21977D84289C1633005B49D6 /* primarykey_schema.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = primarykey_schema.graphql; sourceTree = ""; }; 21977DBE289C1719005B49D6 /* TestConfigHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; @@ -1124,6 +1303,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 21801C7728F9A22900FFA37E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 21BBFB3E289BFF6A00B32A39 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1167,6 +1353,7 @@ 213DBBDB28A5770000B30280 /* AWSDataStorePluginAuthCognitoTests */, 213DBBE828A5771400B30280 /* AWSDataStorePluginAuthIAMTests */, 213DBBF528A5772500B30280 /* AWSDataStorePluginMultiAuthTests */, + 21801C7B28F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests */, 2118212D289BFB4B001B5945 /* Products */, 2118213F289BFBC4001B5945 /* Frameworks */, ); @@ -1183,6 +1370,7 @@ 213DBBDA28A5770000B30280 /* AWSDataStorePluginAuthCognitoTests.xctest */, 213DBBE728A5771400B30280 /* AWSDataStorePluginAuthIAMTests.xctest */, 213DBBF428A5772500B30280 /* AWSDataStorePluginMultiAuthTests.xctest */, + 21801C7A28F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests.xctest */, ); name = Products; sourceTree = ""; @@ -1273,6 +1461,202 @@ path = AWSDataStorePluginMultiAuthTests; sourceTree = ""; }; + 21801C7B28F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests */ = { + isa = PBXGroup; + children = ( + 21801D482902E25A00FFA37E /* LazyLoadBase */, + 21801D3C2900730B00FFA37E /* LL1 */, + 21801D412902DD4900FFA37E /* LL2 */, + 21801D3A29006F8600FFA37E /* LL3 */, + 21801D422902E0CD00FFA37E /* LL4 */, + 21801D3829006F7500FFA37E /* LL5 */, + 21801D432902E0D400FFA37E /* LL6 */, + 21801D442902E0DB00FFA37E /* LL7 */, + 21801D3B2900709200FFA37E /* LL8 */, + 21801D452902E0E100FFA37E /* LL9 */, + 21801D462902E0F600FFA37E /* LL10 */, + 21801D472902E0FD00FFA37E /* LL11 */, + 21801D4B29097CAB00FFA37E /* LL12 */, + ); + path = AWSDataStorePluginLazyLoadTests; + sourceTree = ""; + }; + 21801D3829006F7500FFA37E /* LL5 */ = { + isa = PBXGroup; + children = ( + 21801D3D2900A10600FFA37E /* AWSDataStoreLazyLoadProjectTeam1Tests.swift */, + 21801D2829006DA100FFA37E /* Project1.swift */, + 21801D2929006DA300FFA37E /* Project1+Schema.swift */, + 21801D2D29006DA800FFA37E /* Team1.swift */, + 21801D2C29006DA800FFA37E /* Team1+Schema.swift */, + ); + path = LL5; + sourceTree = ""; + }; + 21801D3A29006F8600FFA37E /* LL3 */ = { + isa = PBXGroup; + children = ( + 21801D2428FDA5E700FFA37E /* AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift */, + 21801CA628F9A86600FFA37E /* CommentWithCompositeKey.swift */, + 21801CA428F9A86600FFA37E /* CommentWithCompositeKey+Schema.swift */, + 21801CA328F9A86600FFA37E /* PostWithCompositeKey.swift */, + 21801CC728F9A86800FFA37E /* PostWithCompositeKey+Schema.swift */, + ); + path = LL3; + sourceTree = ""; + }; + 21801D3B2900709200FFA37E /* LL8 */ = { + isa = PBXGroup; + children = ( + 217AA7BC2909C1D00042CD5D /* AWSDataStoreLazyLoadProjectTeam5Tests.swift */, + 21801D3529006DC200FFA37E /* Project5.swift */, + 21801D3429006DC000FFA37E /* Project5+Schema.swift */, + 21801D3129006DB500FFA37E /* Team5.swift */, + 21801D3029006DB400FFA37E /* Team5+Schema.swift */, + ); + path = LL8; + sourceTree = ""; + }; + 21801D3C2900730B00FFA37E /* LL1 */ = { + isa = PBXGroup; + children = ( + 21801D0528F9AE9400FFA37E /* AWSDataStoreLazyLoadPostComment4V2Tests.swift */, + 21801CB728F9A86700FFA37E /* Comment4V2.swift */, + 21801CCB28F9A86800FFA37E /* Comment4V2+Schema.swift */, + 21801CBA28F9A86700FFA37E /* Post4V2.swift */, + 21801CC128F9A86700FFA37E /* Post4V2+Schema.swift */, + ); + path = LL1; + sourceTree = ""; + }; + 21801D412902DD4900FFA37E /* LL2 */ = { + isa = PBXGroup; + children = ( + 21801CB328F9A86700FFA37E /* Blog8V2.swift */, + 21801CBE28F9A86700FFA37E /* Blog8V2+Schema.swift */, + 21801CCD28F9A86800FFA37E /* Comment8V2.swift */, + 21801CAC28F9A86600FFA37E /* Comment8V2+Schema.swift */, + 21801CCC28F9A86800FFA37E /* MyCustomModel8.swift */, + 21801CAE28F9A86600FFA37E /* MyCustomModel8+Schema.swift */, + 21801CD328F9A86800FFA37E /* MyNestedModel8.swift */, + 21801CAA28F9A86600FFA37E /* MyNestedModel8+Schema.swift */, + 21801CAF28F9A86600FFA37E /* Post8V2.swift */, + 21801CC628F9A86700FFA37E /* Post8V2+Schema.swift */, + ); + path = LL2; + sourceTree = ""; + }; + 21801D422902E0CD00FFA37E /* LL4 */ = { + isa = PBXGroup; + children = ( + 21801D492906CF3B00FFA37E /* AWSDataStoreLazyLoadPostTagTests.swift */, + 21801CAB28F9A86600FFA37E /* PostTagsWithCompositeKey.swift */, + 21801CB428F9A86700FFA37E /* PostTagsWithCompositeKey+Schema.swift */, + 21801CA928F9A86600FFA37E /* PostWithTagsCompositeKey.swift */, + 21801CA528F9A86600FFA37E /* PostWithTagsCompositeKey+Schema.swift */, + 21801CC928F9A86800FFA37E /* TagWithCompositeKey.swift */, + 21801CB528F9A86700FFA37E /* TagWithCompositeKey+Schema.swift */, + ); + path = LL4; + sourceTree = ""; + }; + 21801D432902E0D400FFA37E /* LL6 */ = { + isa = PBXGroup; + children = ( + 21801D7029097E9200FFA37E /* AWSDataStoreLazyLoadProjectTeam2Tests.swift */, + 21801CB628F9A86700FFA37E /* Project2.swift */, + 21801CC028F9A86700FFA37E /* Project2+Schema.swift */, + 21801CCE28F9A86800FFA37E /* Team2.swift */, + 21801CCA28F9A86800FFA37E /* Team2+Schema.swift */, + ); + path = LL6; + sourceTree = ""; + }; + 21801D442902E0DB00FFA37E /* LL7 */ = { + isa = PBXGroup; + children = ( + 217AA7C42909F6730042CD5D /* AWSDataStoreLazyLoadPostComment4Tests.swift */, + 21801CBD28F9A86700FFA37E /* Comment4.swift */, + 21801CC528F9A86700FFA37E /* Comment4+Schema.swift */, + 21801CBC28F9A86700FFA37E /* Post4.swift */, + 21801CAD28F9A86600FFA37E /* Post4+Schema.swift */, + ); + path = LL7; + sourceTree = ""; + }; + 21801D452902E0E100FFA37E /* LL9 */ = { + isa = PBXGroup; + children = ( + 217AA7BE2909C6330042CD5D /* AWSDataStoreLazyLoadProjectTeam6Tests.swift */, + 21801CB228F9A86600FFA37E /* Project6.swift */, + 21801CC228F9A86700FFA37E /* Project6+Schema.swift */, + 21801CC828F9A86800FFA37E /* Team6.swift */, + 21801CBB28F9A86700FFA37E /* Team6+Schema.swift */, + ); + path = LL9; + sourceTree = ""; + }; + 21801D462902E0F600FFA37E /* LL10 */ = { + isa = PBXGroup; + children = ( + 217AA7C02909ED420042CD5D /* AWSDataStoreLazyLoadPostComment7Tests.swift */, + 21801CC428F9A86700FFA37E /* Comment7.swift */, + 21801CA728F9A86600FFA37E /* Comment7+Schema.swift */, + 21801CC328F9A86700FFA37E /* Post7.swift */, + 21801CD228F9A86800FFA37E /* Post7+Schema.swift */, + ); + path = LL10; + sourceTree = ""; + }; + 21801D472902E0FD00FFA37E /* LL11 */ = { + isa = PBXGroup; + children = ( + 217AA7C22909EF050042CD5D /* AWSDataStoreLazyLoadPostComment8Tests.swift */, + 21801CB928F9A86700FFA37E /* Comment8.swift */, + 21801CD128F9A86800FFA37E /* Comment8+Schema.swift */, + 21801CA828F9A86600FFA37E /* Post8.swift */, + 21801CB828F9A86700FFA37E /* Post8+Schema.swift */, + ); + path = LL11; + sourceTree = ""; + }; + 21801D482902E25A00FFA37E /* LazyLoadBase */ = { + isa = PBXGroup; + children = ( + 21801C8528F9A36D00FFA37E /* lazyload-schema.graphql */, + 21801C7C28F9A22900FFA37E /* AWSDataStoreLazyLoadBaseTest.swift */, + 21801C8428F9A2DC00FFA37E /* README.md */, + 21801C8628F9A37F00FFA37E /* TestConfigHelper.swift */, + 21801CD028F9A86800FFA37E /* AmplifyModels.swift */, + ); + path = LazyLoadBase; + sourceTree = ""; + }; + 21801D4B29097CAB00FFA37E /* LL12 */ = { + isa = PBXGroup; + children = ( + 21801D5D29097D5700FFA37E /* ChildSansBelongsTo.swift */, + 21801D5829097D5700FFA37E /* ChildSansBelongsTo+Schema.swift */, + 21801D5029097D5600FFA37E /* CompositePKChild.swift */, + 21801D5229097D5600FFA37E /* CompositePKChild+Schema.swift */, + 21801D5729097D5600FFA37E /* CompositePKParent.swift */, + 21801D5529097D5600FFA37E /* CompositePKParent+Schema.swift */, + 21801D5629097D5600FFA37E /* DefaultPKChild.swift */, + 21801D5429097D5600FFA37E /* DefaultPKChild+Schema.swift */, + 21801D5A29097D5700FFA37E /* DefaultPKParent.swift */, + 21801D5329097D5600FFA37E /* DefaultPKParent+Schema.swift */, + 21801D5C29097D5700FFA37E /* HasOneChild.swift */, + 21801D4D29097D5600FFA37E /* HasOneChild+Schema.swift */, + 21801D5929097D5700FFA37E /* HasOneParent.swift */, + 21801D4F29097D5600FFA37E /* HasOneParent+Schema.swift */, + 21801D4E29097D5600FFA37E /* ImplicitChild.swift */, + 21801D5B29097D5700FFA37E /* ImplicitChild+Schema.swift */, + 21801D4C29097D5400FFA37E /* StrangeExplicitChild.swift */, + 21801D5129097D5600FFA37E /* StrangeExplicitChild+Schema.swift */, + ); + path = LL12; + sourceTree = ""; + }; 21977D75289C161D005B49D6 /* Models */ = { isa = PBXGroup; children = ( @@ -2354,6 +2738,25 @@ productReference = 213DBBF428A5772500B30280 /* AWSDataStorePluginMultiAuthTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 21801C7928F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 21801C8228F9A22900FFA37E /* Build configuration list for PBXNativeTarget "AWSDataStorePluginLazyLoadTests" */; + buildPhases = ( + 21801C7628F9A22900FFA37E /* Sources */, + 21801C7728F9A22900FFA37E /* Frameworks */, + 21801C7828F9A22900FFA37E /* Resources */, + 21801C8328F9A23300FFA37E /* Copy Configuration Files */, + ); + buildRules = ( + ); + dependencies = ( + 21801C7F28F9A22900FFA37E /* PBXTargetDependency */, + ); + name = AWSDataStorePluginLazyLoadTests; + productName = AWSDataStorePluginLazyLoadTests; + productReference = 21801C7A28F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 21BBFB40289BFF6A00B32A39 /* AWSDataStorePluginIntegrationTests */ = { isa = PBXNativeTarget; buildConfigurationList = 21BBFB49289BFF6A00B32A39 /* Build configuration list for PBXNativeTarget "AWSDataStorePluginIntegrationTests" */; @@ -2454,6 +2857,10 @@ CreatedOnToolsVersion = 14.0; TestTargetID = 2118212B289BFB4B001B5945; }; + 21801C7928F9A22900FFA37E = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 2118212B289BFB4B001B5945; + }; 21BBFB40289BFF6A00B32A39 = { CreatedOnToolsVersion = 13.4.1; TestTargetID = 2118212B289BFB4B001B5945; @@ -2493,6 +2900,7 @@ 213DBBD928A5770000B30280 /* AWSDataStorePluginAuthCognitoTests */, 213DBBE628A5771400B30280 /* AWSDataStorePluginAuthIAMTests */, 213DBBF328A5772500B30280 /* AWSDataStorePluginMultiAuthTests */, + 21801C7928F9A22900FFA37E /* AWSDataStorePluginLazyLoadTests */, ); }; /* End PBXProject section */ @@ -2528,6 +2936,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 21801C7828F9A22900FFA37E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 21BBFB3F289BFF6A00B32A39 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2617,6 +3032,24 @@ shellPath = /bin/sh; shellScript = "TEMP_FILE=$HOME/.aws-amplify/amplify-ios/testconfiguration/.\nDEST_PATH=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/testconfiguration/\"\n\nif [[ ! -d $TEMP_FILE ]] ; then\n echo \"${TEMP_FILE} does not exist. Using empty configuration.\"\n exit 0\nfi\n\nif [[ -f $DEST_PATH ]] ; then\n rm $DEST_PATH\nfi\n \ncp -r $TEMP_FILE $DEST_PATH\n"; }; + 21801C8328F9A23300FFA37E /* Copy Configuration Files */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy Configuration Files"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "TEMP_FILE=$HOME/.aws-amplify/amplify-ios/testconfiguration/.\nDEST_PATH=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/testconfiguration/\"\n\nif [[ ! -d $TEMP_FILE ]] ; then\n echo \"${TEMP_FILE} does not exist. Using empty configuration.\"\n exit 0\nfi\n\nif [[ -f $DEST_PATH ]] ; then\n rm $DEST_PATH\nfi\n \ncp -r $TEMP_FILE $DEST_PATH\n"; + }; 21977DBC289C16EB005B49D6 /* Copy Configuration Files */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2798,6 +3231,99 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 21801C7628F9A22900FFA37E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 21801CE328F9A86800FFA37E /* Project6.swift in Sources */, + 21801CE628F9A86800FFA37E /* TagWithCompositeKey+Schema.swift in Sources */, + 21801D2F29006DA900FFA37E /* Team1.swift in Sources */, + 21801D6529097D5800FFA37E /* DefaultPKParent+Schema.swift in Sources */, + 21801CE728F9A86800FFA37E /* Project2.swift in Sources */, + 217AA7C32909EF050042CD5D /* AWSDataStoreLazyLoadPostComment8Tests.swift in Sources */, + 21801D6A29097D5800FFA37E /* ChildSansBelongsTo+Schema.swift in Sources */, + 21801CF728F9A86800FFA37E /* Post8V2+Schema.swift in Sources */, + 21801D0128F9A86800FFA37E /* AmplifyModels.swift in Sources */, + 21801CFB28F9A86800FFA37E /* Team2+Schema.swift in Sources */, + 21801D6F29097D5800FFA37E /* ChildSansBelongsTo.swift in Sources */, + 21801D6B29097D5800FFA37E /* HasOneParent.swift in Sources */, + 21801D0328F9A86800FFA37E /* Post7+Schema.swift in Sources */, + 21801D3329006DB500FFA37E /* Team5.swift in Sources */, + 21801CEF28F9A86800FFA37E /* Blog8V2+Schema.swift in Sources */, + 21801CE428F9A86800FFA37E /* Blog8V2.swift in Sources */, + 21801D3629006DC200FFA37E /* Project5+Schema.swift in Sources */, + 21801D6229097D5800FFA37E /* CompositePKChild.swift in Sources */, + 21801CE028F9A86800FFA37E /* Post8V2.swift in Sources */, + 21801D3E2900A10600FFA37E /* AWSDataStoreLazyLoadProjectTeam1Tests.swift in Sources */, + 21801C7D28F9A22900FFA37E /* AWSDataStoreLazyLoadBaseTest.swift in Sources */, + 21801CFF28F9A86800FFA37E /* Team2.swift in Sources */, + 217AA7BF2909C6330042CD5D /* AWSDataStoreLazyLoadProjectTeam6Tests.swift in Sources */, + 21801D0228F9A86800FFA37E /* Comment8+Schema.swift in Sources */, + 21801D5F29097D5800FFA37E /* HasOneChild+Schema.swift in Sources */, + 21801CFC28F9A86800FFA37E /* Comment4V2+Schema.swift in Sources */, + 21801CEC28F9A86800FFA37E /* Team6+Schema.swift in Sources */, + 21801CEA28F9A86800FFA37E /* Comment8.swift in Sources */, + 21801D6329097D5800FFA37E /* StrangeExplicitChild+Schema.swift in Sources */, + 21801C8728F9A38000FFA37E /* TestConfigHelper.swift in Sources */, + 21801CE928F9A86800FFA37E /* Post8+Schema.swift in Sources */, + 21801D6C29097D5800FFA37E /* DefaultPKParent.swift in Sources */, + 21801CF528F9A86800FFA37E /* Comment7.swift in Sources */, + 21801D5E29097D5800FFA37E /* StrangeExplicitChild.swift in Sources */, + 21801CE828F9A86800FFA37E /* Comment4V2.swift in Sources */, + 21801CD528F9A86800FFA37E /* CommentWithCompositeKey+Schema.swift in Sources */, + 217AA7BD2909C1D00042CD5D /* AWSDataStoreLazyLoadProjectTeam5Tests.swift in Sources */, + 21801CED28F9A86800FFA37E /* Post4.swift in Sources */, + 21801CF228F9A86800FFA37E /* Post4V2+Schema.swift in Sources */, + 21801CFA28F9A86800FFA37E /* TagWithCompositeKey.swift in Sources */, + 21801CF828F9A86800FFA37E /* PostWithCompositeKey+Schema.swift in Sources */, + 21801D2E29006DA900FFA37E /* Team1+Schema.swift in Sources */, + 21801CF328F9A86800FFA37E /* Project6+Schema.swift in Sources */, + 21801CD728F9A86800FFA37E /* CommentWithCompositeKey.swift in Sources */, + 21801CFE28F9A86800FFA37E /* Comment8V2.swift in Sources */, + 21801CD928F9A86800FFA37E /* Post8.swift in Sources */, + 21801CDD28F9A86800FFA37E /* Comment8V2+Schema.swift in Sources */, + 21801CD428F9A86800FFA37E /* PostWithCompositeKey.swift in Sources */, + 21801D6D29097D5800FFA37E /* ImplicitChild+Schema.swift in Sources */, + 21801D6E29097D5800FFA37E /* HasOneChild.swift in Sources */, + 21801CD828F9A86800FFA37E /* Comment7+Schema.swift in Sources */, + 21801D3229006DB500FFA37E /* Team5+Schema.swift in Sources */, + 21801D2B29006DA300FFA37E /* Project1+Schema.swift in Sources */, + 21801D6629097D5800FFA37E /* DefaultPKChild+Schema.swift in Sources */, + 21801D0728F9B11800FFA37E /* AsyncTesting.swift in Sources */, + 21801D2A29006DA300FFA37E /* Project1.swift in Sources */, + 21801CF128F9A86800FFA37E /* Project2+Schema.swift in Sources */, + 21801D0428F9A86800FFA37E /* MyNestedModel8.swift in Sources */, + 21801CF928F9A86800FFA37E /* Team6.swift in Sources */, + 21801CD628F9A86800FFA37E /* PostWithTagsCompositeKey+Schema.swift in Sources */, + 21801CDC28F9A86800FFA37E /* PostTagsWithCompositeKey.swift in Sources */, + 21801D6129097D5800FFA37E /* HasOneParent+Schema.swift in Sources */, + 21801CF628F9A86800FFA37E /* Comment4+Schema.swift in Sources */, + 21801CEB28F9A86800FFA37E /* Post4V2.swift in Sources */, + 217AA7C52909F6730042CD5D /* AWSDataStoreLazyLoadPostComment4Tests.swift in Sources */, + 21801CDF28F9A86800FFA37E /* MyCustomModel8+Schema.swift in Sources */, + 21801D0828F9B11B00FFA37E /* AsyncExpectation.swift in Sources */, + 21801D6429097D5800FFA37E /* CompositePKChild+Schema.swift in Sources */, + 21801D3729006DC200FFA37E /* Project5.swift in Sources */, + 217AA7C12909ED420042CD5D /* AWSDataStoreLazyLoadPostComment7Tests.swift in Sources */, + 21801D6029097D5800FFA37E /* ImplicitChild.swift in Sources */, + 21801CFD28F9A86800FFA37E /* MyCustomModel8.swift in Sources */, + 21801D6729097D5800FFA37E /* CompositePKParent+Schema.swift in Sources */, + 21801CEE28F9A86800FFA37E /* Comment4.swift in Sources */, + 21801D0628F9AE9400FFA37E /* AWSDataStoreLazyLoadPostComment4V2Tests.swift in Sources */, + 21801CDB28F9A86800FFA37E /* MyNestedModel8+Schema.swift in Sources */, + 21801CE528F9A86800FFA37E /* PostTagsWithCompositeKey+Schema.swift in Sources */, + 21801D6929097D5800FFA37E /* CompositePKParent.swift in Sources */, + 21801D2528FDA5E700FFA37E /* AWSDataStoreLazyLoadPostCommentWithCompositeKeyTests.swift in Sources */, + 21801CDE28F9A86800FFA37E /* Post4+Schema.swift in Sources */, + 21801D4A2906CF3B00FFA37E /* AWSDataStoreLazyLoadPostTagTests.swift in Sources */, + 21801D6829097D5800FFA37E /* DefaultPKChild.swift in Sources */, + 21801CDA28F9A86800FFA37E /* PostWithTagsCompositeKey.swift in Sources */, + 21801D0928F9B11E00FFA37E /* XCTestCase+AsyncTesting.swift in Sources */, + 21801D7129097E9400FFA37E /* AWSDataStoreLazyLoadProjectTeam2Tests.swift in Sources */, + 21801CF428F9A86800FFA37E /* Post7.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 21BBFB3D289BFF6A00B32A39 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3246,6 +3772,11 @@ target = 2118212B289BFB4B001B5945 /* DataStoreHostApp */; targetProxy = 213DBBF828A5772500B30280 /* PBXContainerItemProxy */; }; + 21801C7F28F9A22900FFA37E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2118212B289BFB4B001B5945 /* DataStoreHostApp */; + targetProxy = 21801C7E28F9A22900FFA37E /* PBXContainerItemProxy */; + }; 21BBFB46289BFF6A00B32A39 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2118212B289BFB4B001B5945 /* DataStoreHostApp */; @@ -3553,6 +4084,44 @@ }; name = Release; }; + 21801C8028F9A22900FFA37E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.AWSDataStorePluginLazyLoadTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DataStoreHostApp.app/DataStoreHostApp"; + }; + name = Debug; + }; + 21801C8128F9A22900FFA37E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.AWSDataStorePluginLazyLoadTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DataStoreHostApp.app/DataStoreHostApp"; + }; + name = Release; + }; 21BBFB47289BFF6A00B32A39 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3745,6 +4314,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 21801C8228F9A22900FFA37E /* Build configuration list for PBXNativeTarget "AWSDataStorePluginLazyLoadTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 21801C8028F9A22900FFA37E /* Debug */, + 21801C8128F9A22900FFA37E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 21BBFB49289BFF6A00B32A39 /* Build configuration list for PBXNativeTarget "AWSDataStorePluginIntegrationTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/AWSDataStorePluginLazyLoadTests.xcscheme b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/AWSDataStorePluginLazyLoadTests.xcscheme new file mode 100644 index 0000000000..68ce0eaa9a --- /dev/null +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/AWSDataStorePluginLazyLoadTests.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/DataStoreHostApp.xcscheme b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/DataStoreHostApp.xcscheme index e830be2802..4daa7ccb84 100644 --- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/DataStoreHostApp.xcscheme +++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/DataStoreHostApp.xcodeproj/xcshareddata/xcschemes/DataStoreHostApp.xcscheme @@ -28,6 +28,17 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + +