diff --git a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/project.pbxproj index 15f0bc7a05..2d967426e6 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/project.pbxproj @@ -60,6 +60,18 @@ 213DBC8528A6CE9800B30280 /* GraphQLWithLambdaAuthIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698BA328899B3F004BD994 /* GraphQLWithLambdaAuthIntegrationTests.swift */; }; 213DBC8728A6CEDD00B30280 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213DBC8628A6CEDD00B30280 /* Todo.swift */; }; 213DBC8928A700E000B30280 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213DBC8828A700E000B30280 /* SubscriptionView.swift */; }; + 2163D60F2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D60E2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLBaseTest.swift */; }; + 2163D6172BE96E3D009689B1 /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6162BE96E3D009689B1 /* TestConfigHelper.swift */; }; + 2163D61B2BE97494009689B1 /* GraphQLLazyLoadPostComment4V2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D61A2BE97494009689B1 /* GraphQLLazyLoadPostComment4V2Tests.swift */; }; + 2163D61D2BE974B0009689B1 /* GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D61C2BE974B0009689B1 /* GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift */; }; + 2163D6222BE974D8009689B1 /* Comment4V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D61E2BE974D7009689B1 /* Comment4V2+Schema.swift */; }; + 2163D6232BE974D8009689B1 /* Post4V2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D61F2BE974D7009689B1 /* Post4V2+Schema.swift */; }; + 2163D6242BE974D8009689B1 /* Post4V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6202BE974D8009689B1 /* Post4V2.swift */; }; + 2163D6252BE974D8009689B1 /* Comment4V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6212BE974D8009689B1 /* Comment4V2.swift */; }; + 2163D62A2BE974E6009689B1 /* PostWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6262BE974E6009689B1 /* PostWithCompositeKey.swift */; }; + 2163D62B2BE974E6009689B1 /* CommentWithCompositeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6272BE974E6009689B1 /* CommentWithCompositeKey.swift */; }; + 2163D62C2BE974E6009689B1 /* CommentWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6282BE974E6009689B1 /* CommentWithCompositeKey+Schema.swift */; }; + 2163D62D2BE974E6009689B1 /* PostWithCompositeKey+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2163D6292BE974E6009689B1 /* PostWithCompositeKey+Schema.swift */; }; 21698AA328899921004BD994 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698A9F28899921004BD994 /* Todo.swift */; }; 21698AB72889996A004BD994 /* GraphQLConnectionScenario1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AA82889996A004BD994 /* GraphQLConnectionScenario1Tests.swift */; }; 21698AB92889996A004BD994 /* GraphQLConnectionScenario3Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAA2889996A004BD994 /* GraphQLConnectionScenario3Tests.swift */; }; @@ -214,76 +226,6 @@ 21EA887F28F9BCC30000BA75 /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE7028E7451D0000C36A /* AsyncExpectation.swift */; }; 21EA888028F9BCC50000BA75 /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE7128E7451D0000C36A /* XCTestCase+AsyncTesting.swift */; }; 21EA888228F9BCD90000BA75 /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21EA888128F9BCD90000BA75 /* TestConfigHelper.swift */; }; - 21F762512BD6B0710048845A /* Team2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271F289ABFE9003788E3 /* Team2+Schema.swift */; }; - 21F762522BD6B0710048845A /* EnumTestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262707289ABFE6003788E3 /* EnumTestModel.swift */; }; - 21F762532BD6B0710048845A /* GraphQLScalarAPISwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21809B802A69D09B00F70E38 /* GraphQLScalarAPISwiftTests.swift */; }; - 21F762542BD6B0710048845A /* ScalarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262720289ABFE9003788E3 /* ScalarContainer.swift */; }; - 21F762552BD6B0710048845A /* Project2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262719289ABFE8003788E3 /* Project2.swift */; }; - 21F762562BD6B0710048845A /* EnumTestModel+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271E289ABFE9003788E3 /* EnumTestModel+Schema.swift */; }; - 21F762572BD6B0710048845A /* ListStringContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262715289ABFE8003788E3 /* ListStringContainer.swift */; }; - 21F762582BD6B0710048845A /* GraphQLConnectionScenario3Tests+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAD2889996A004BD994 /* GraphQLConnectionScenario3Tests+List.swift */; }; - 21F762592BD6B0710048845A /* GraphQLConnectionScenario3Tests+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB02889996A004BD994 /* GraphQLConnectionScenario3Tests+Helpers.swift */; }; - 21F7625A2BD6B0710048845A /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE6F28E7451D0000C36A /* AsyncTesting.swift */; }; - 21F7625B2BD6B0710048845A /* ListIntContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270C289ABFE6003788E3 /* ListIntContainer.swift */; }; - 21F7625C2BD6B0710048845A /* Team2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262712289ABFE7003788E3 /* Team2.swift */; }; - 21F7625D2BD6B0710048845A /* GraphQLConnectionScenario3APISwiftTests+Subscribe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E581E52A698C4D0027D13A /* GraphQLConnectionScenario3APISwiftTests+Subscribe.swift */; }; - 21F7625E2BD6B0710048845A /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698A9F28899921004BD994 /* Todo.swift */; }; - 21F7625F2BD6B0710048845A /* ListStringContainer+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262724289ABFE9003788E3 /* ListStringContainer+Schema.swift */; }; - 21F762602BD6B0710048845A /* Project2+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262710289ABFE7003788E3 /* Project2+Schema.swift */; }; - 21F762612BD6B0710048845A /* NestedTypeTestModel+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272F289ABFEB003788E3 /* NestedTypeTestModel+Schema.swift */; }; - 21F762622BD6B0710048845A /* NestedTypeTestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272D289ABFEB003788E3 /* NestedTypeTestModel.swift */; }; - 21F762632BD6B0710048845A /* Post5+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262714289ABFE7003788E3 /* Post5+Schema.swift */; }; - 21F762642BD6B0710048845A /* TestEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272C289ABFEB003788E3 /* TestEnum.swift */; }; - 21F762652BD6B0710048845A /* GraphQLConnectionScenario3Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAA2889996A004BD994 /* GraphQLConnectionScenario3Tests.swift */; }; - 21F762662BD6B0710048845A /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE7128E7451D0000C36A /* XCTestCase+AsyncTesting.swift */; }; - 21F762672BD6B0710048845A /* GraphQLTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAF2889996A004BD994 /* GraphQLTestBase.swift */; }; - 21F762682BD6B0710048845A /* GraphQLConnectionScenario4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB32889996A004BD994 /* GraphQLConnectionScenario4Tests.swift */; }; - 21F762692BD6B0710048845A /* PostEditor5+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262721289ABFE9003788E3 /* PostEditor5+Schema.swift */; }; - 21F7626A2BD6B0710048845A /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E581E32A6835910027D13A /* API.swift */; }; - 21F7626B2BD6B0710048845A /* Nested.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262711289ABFE7003788E3 /* Nested.swift */; }; - 21F7626C2BD6B0710048845A /* Comment3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272A289ABFEA003788E3 /* Comment3.swift */; }; - 21F7626D2BD6B0710048845A /* Comment6+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271B289ABFE8003788E3 /* Comment6+Schema.swift */; }; - 21F7626E2BD6B0710048845A /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262523289ABB0C003788E3 /* TestConfigHelper.swift */; }; - 21F7626F2BD6B0710048845A /* Team1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270D289ABFE6003788E3 /* Team1.swift */; }; - 21F762702BD6B0710048845A /* Comment+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272B289ABFEA003788E3 /* Comment+Schema.swift */; }; - 21F762712BD6B0710048845A /* GraphQLConnectionScenario2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB62889996A004BD994 /* GraphQLConnectionScenario2Tests.swift */; }; - 21F762722BD6B0710048845A /* Post5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262703289ABFE5003788E3 /* Post5.swift */; }; - 21F762732BD6B0710048845A /* Nested+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262718289ABFE8003788E3 /* Nested+Schema.swift */; }; - 21F762742BD6B0710048845A /* Post3+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270A289ABFE6003788E3 /* Post3+Schema.swift */; }; - 21F762752BD6B0710048845A /* GraphQLConnectionScenario6Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAE2889996A004BD994 /* GraphQLConnectionScenario6Tests.swift */; }; - 21F762762BD6B0710048845A /* GraphQLConnectionScenario1APISwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E581E12A6707900027D13A /* GraphQLConnectionScenario1APISwiftTests.swift */; }; - 21F762772BD6B0710048845A /* Comment4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262717289ABFE8003788E3 /* Comment4.swift */; }; - 21F762782BD6B0710048845A /* GraphQLModelBasedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB42889996A004BD994 /* GraphQLModelBasedTests.swift */; }; - 21F762792BD6B0710048845A /* ListIntContainer+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262716289ABFE8003788E3 /* ListIntContainer+Schema.swift */; }; - 21F7627A2BD6B0710048845A /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFE7028E7451D0000C36A /* AsyncExpectation.swift */; }; - 21F7627B2BD6B0710048845A /* Post4+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262727289ABFEA003788E3 /* Post4+Schema.swift */; }; - 21F7627C2BD6B0710048845A /* GraphQLConnectionScenario3Tests+Subscribe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB22889996A004BD994 /* GraphQLConnectionScenario3Tests+Subscribe.swift */; }; - 21F7627D2BD6B0710048845A /* Post+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126272E289ABFEB003788E3 /* Post+Schema.swift */; }; - 21F7627E2BD6B0710048845A /* Blog6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270E289ABFE7003788E3 /* Blog6.swift */; }; - 21F7627F2BD6B0710048845A /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271C289ABFE8003788E3 /* Comment.swift */; }; - 21F762802BD6B0710048845A /* Post6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270F289ABFE7003788E3 /* Post6.swift */; }; - 21F762812BD6B0710048845A /* AppSyncRealTimeClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C8B782B895E5A00716094 /* AppSyncRealTimeClientTests.swift */; }; - 21F762822BD6B0710048845A /* Post3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262730289ABFEB003788E3 /* Post3.swift */; }; - 21F762832BD6B0710048845A /* User5+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262728289ABFEA003788E3 /* User5+Schema.swift */; }; - 21F762842BD6B0710048845A /* Blog6+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262725289ABFEA003788E3 /* Blog6+Schema.swift */; }; - 21F762852BD6B0710048845A /* Comment6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271D289ABFE9003788E3 /* Comment6.swift */; }; - 21F762862BD6B0710048845A /* GraphQLScalarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAB2889996A004BD994 /* GraphQLScalarTests.swift */; }; - 21F762872BD6B0710048845A /* Post4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262726289ABFEA003788E3 /* Post4.swift */; }; - 21F762882BD6B0710048845A /* ScalarContainer+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262702289ABFE4003788E3 /* ScalarContainer+Schema.swift */; }; - 21F762892BD6B0710048845A /* Project1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262723289ABFE9003788E3 /* Project1.swift */; }; - 21F7628A2BD6B0710048845A /* Team1+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126270B289ABFE6003788E3 /* Team1+Schema.swift */; }; - 21F7628B2BD6B0710048845A /* AmplifyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2126271A289ABFE8003788E3 /* AmplifyModels.swift */; }; - 21F7628C2BD6B0710048845A /* User5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262706289ABFE5003788E3 /* User5.swift */; }; - 21F7628D2BD6B0710048845A /* GraphQLConnectionScenario1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AA82889996A004BD994 /* GraphQLConnectionScenario1Tests.swift */; }; - 21F7628E2BD6B0710048845A /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262722289ABFE9003788E3 /* PostStatus.swift */; }; - 21F7628F2BD6B0710048845A /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262704289ABFE5003788E3 /* Post.swift */; }; - 21F762902BD6B0710048845A /* Project1+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262713289ABFE7003788E3 /* Project1+Schema.swift */; }; - 21F762912BD6B0710048845A /* Comment3+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262729289ABFEA003788E3 /* Comment3+Schema.swift */; }; - 21F762922BD6B0710048845A /* GraphQLModelBasedTests+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AAC2889996A004BD994 /* GraphQLModelBasedTests+List.swift */; }; - 21F762932BD6B0710048845A /* Comment4+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262708289ABFE6003788E3 /* Comment4+Schema.swift */; }; - 21F762942BD6B0710048845A /* Post6+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262709289ABFE6003788E3 /* Post6+Schema.swift */; }; - 21F762952BD6B0710048845A /* PostEditor5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21262705289ABFE5003788E3 /* PostEditor5.swift */; }; - 21F762962BD6B0710048845A /* GraphQLConnectionScenario5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21698AB52889996A004BD994 /* GraphQLConnectionScenario5Tests.swift */; }; 21FA8EF7295C9609009F6A07 /* GraphQLLazyLoadHasOneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FA8EF6295C9609009F6A07 /* GraphQLLazyLoadHasOneTests.swift */; }; 21FA8EF9295C962E009F6A07 /* GraphQLLazyLoadDefaultPKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FA8EF8295C962E009F6A07 /* GraphQLLazyLoadDefaultPKTests.swift */; }; 21FA8EFB295C9647009F6A07 /* GraphQLLazyLoadCompositePKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FA8EFA295C9647009F6A07 /* GraphQLLazyLoadCompositePKTests.swift */; }; @@ -434,28 +376,28 @@ remoteGlobalIDString = 21E73E6A28898D7800D7DB7E; remoteInfo = APIHostApp; }; - 21698A7F28899805004BD994 /* PBXContainerItemProxy */ = { + 2163D6102BE96C90009689B1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 21E73E6328898D7800D7DB7E /* Project object */; proxyType = 1; remoteGlobalIDString = 21E73E6A28898D7800D7DB7E; remoteInfo = APIHostApp; }; - 21698A9928899818004BD994 /* PBXContainerItemProxy */ = { + 21698A7F28899805004BD994 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 21E73E6328898D7800D7DB7E /* Project object */; proxyType = 1; remoteGlobalIDString = 21E73E6A28898D7800D7DB7E; remoteInfo = APIHostApp; }; - 21EA887728F9BC610000BA75 /* PBXContainerItemProxy */ = { + 21698A9928899818004BD994 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 21E73E6328898D7800D7DB7E /* Project object */; proxyType = 1; remoteGlobalIDString = 21E73E6A28898D7800D7DB7E; remoteInfo = APIHostApp; }; - 21F7624F2BD6B0710048845A /* PBXContainerItemProxy */ = { + 21EA887728F9BC610000BA75 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 21E73E6328898D7800D7DB7E /* Project object */; proxyType = 1; @@ -588,6 +530,20 @@ 213DBC8028A6C4FB00B30280 /* TestConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; 213DBC8628A6CEDD00B30280 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; 213DBC8828A700E000B30280 /* SubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; + 2163D60C2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSAPIPluginGen2GraphQLTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2163D60E2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLBaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSAPIPluginGen2GraphQLBaseTest.swift; sourceTree = ""; }; + 2163D6152BE96D12009689B1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 2163D6162BE96E3D009689B1 /* TestConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; + 2163D61A2BE97494009689B1 /* GraphQLLazyLoadPostComment4V2Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLLazyLoadPostComment4V2Tests.swift; sourceTree = ""; }; + 2163D61C2BE974B0009689B1 /* GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift; sourceTree = ""; }; + 2163D61E2BE974D7009689B1 /* Comment4V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Comment4V2+Schema.swift"; sourceTree = ""; }; + 2163D61F2BE974D7009689B1 /* Post4V2+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Post4V2+Schema.swift"; sourceTree = ""; }; + 2163D6202BE974D8009689B1 /* Post4V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post4V2.swift; sourceTree = ""; }; + 2163D6212BE974D8009689B1 /* Comment4V2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment4V2.swift; sourceTree = ""; }; + 2163D6262BE974E6009689B1 /* PostWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostWithCompositeKey.swift; sourceTree = ""; }; + 2163D6272BE974E6009689B1 /* CommentWithCompositeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentWithCompositeKey.swift; sourceTree = ""; }; + 2163D6282BE974E6009689B1 /* CommentWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CommentWithCompositeKey+Schema.swift"; sourceTree = ""; }; + 2163D6292BE974E6009689B1 /* PostWithCompositeKey+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PostWithCompositeKey+Schema.swift"; sourceTree = ""; }; 21698A7B28899804004BD994 /* AWSAPIPluginFunctionalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSAPIPluginFunctionalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 21698A9528899818004BD994 /* AWSAPIPluginGraphQLAuthDirectiveTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSAPIPluginGraphQLAuthDirectiveTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 21698A9F28899921004BD994 /* Todo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; @@ -755,8 +711,6 @@ 21EA887D28F9BCBB0000BA75 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 21EA888128F9BCD90000BA75 /* TestConfigHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; 21EA888328F9BD2D0000BA75 /* lazyload-schema.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = "lazyload-schema.graphql"; sourceTree = ""; }; - 21F7629D2BD6B0710048845A /* AWSAPIPluginGen2FunctionalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSAPIPluginGen2FunctionalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 21F7629E2BD6B0B40048845A /* AWSAPIPluginGen2FunctionalTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AWSAPIPluginGen2FunctionalTests.xctestplan; sourceTree = ""; }; 21FA8EF6295C9609009F6A07 /* GraphQLLazyLoadHasOneTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLLazyLoadHasOneTests.swift; sourceTree = ""; }; 21FA8EF8295C962E009F6A07 /* GraphQLLazyLoadDefaultPKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLLazyLoadDefaultPKTests.swift; sourceTree = ""; }; 21FA8EFA295C9647009F6A07 /* GraphQLLazyLoadCompositePKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLLazyLoadCompositePKTests.swift; sourceTree = ""; }; @@ -796,6 +750,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2163D6092BE96C90009689B1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 21698A7828899804004BD994 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -829,13 +790,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 21F762972BD6B0710048845A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 395906A928AC4A16004B96B1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -995,10 +949,45 @@ path = Base; sourceTree = ""; }; + 2163D60D2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests */ = { + isa = PBXGroup; + children = ( + 2163D6192BE97476009689B1 /* LL3 */, + 2163D6182BE9740B009689B1 /* LL1 */, + 2163D60E2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLBaseTest.swift */, + 2163D6152BE96D12009689B1 /* README.md */, + 2163D6162BE96E3D009689B1 /* TestConfigHelper.swift */, + ); + path = AWSAPIPluginGen2GraphQLTests; + sourceTree = ""; + }; + 2163D6182BE9740B009689B1 /* LL1 */ = { + isa = PBXGroup; + children = ( + 2163D6212BE974D8009689B1 /* Comment4V2.swift */, + 2163D61E2BE974D7009689B1 /* Comment4V2+Schema.swift */, + 2163D6202BE974D8009689B1 /* Post4V2.swift */, + 2163D61F2BE974D7009689B1 /* Post4V2+Schema.swift */, + 2163D61A2BE97494009689B1 /* GraphQLLazyLoadPostComment4V2Tests.swift */, + ); + path = LL1; + sourceTree = ""; + }; + 2163D6192BE97476009689B1 /* LL3 */ = { + isa = PBXGroup; + children = ( + 2163D6272BE974E6009689B1 /* CommentWithCompositeKey.swift */, + 2163D6282BE974E6009689B1 /* CommentWithCompositeKey+Schema.swift */, + 2163D6262BE974E6009689B1 /* PostWithCompositeKey.swift */, + 2163D6292BE974E6009689B1 /* PostWithCompositeKey+Schema.swift */, + 2163D61C2BE974B0009689B1 /* GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift */, + ); + path = LL3; + sourceTree = ""; + }; 21698A7C28899805004BD994 /* AWSAPIPluginFunctionalTests */ = { isa = PBXGroup; children = ( - 21F7629E2BD6B0B40048845A /* AWSAPIPluginGen2FunctionalTests.xctestplan */, 21E581E32A6835910027D13A /* API.swift */, 212626CA289ABC79003788E3 /* Base */, 606C8B782B895E5A00716094 /* AppSyncRealTimeClientTests.swift */, @@ -1267,6 +1256,7 @@ 395906C128AC63A9004B96B1 /* AWSAPIPluginRESTUserPoolTests */, 97914BC429558714002000EA /* GraphQLAPIStressTests */, 21EA887428F9BC600000BA75 /* AWSAPIPluginLazyLoadTests */, + 2163D60D2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests */, 21E73E6C28898D7900D7DB7E /* Products */, 21698BD728899EBB004BD994 /* Frameworks */, ); @@ -1289,7 +1279,7 @@ 681B35892A43962D0074F369 /* AWSAPIPluginFunctionalTestsWatch.xctest */, 681B35A12A4396CF0074F369 /* AWSAPIPluginGraphQLLambdaAuthTestsWatch.xctest */, 681B35C52A43970A0074F369 /* AWSAPIPluginRESTIAMTestsWatch.xctest */, - 21F7629D2BD6B0710048845A /* AWSAPIPluginGen2FunctionalTests.xctest */, + 2163D60C2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests.xctest */, ); name = Products; sourceTree = ""; @@ -1531,6 +1521,25 @@ productReference = 213DBC7528A6C47000B30280 /* AWSAPIPluginGraphQLLambdaAuthTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 2163D60B2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2163D6122BE96C90009689B1 /* Build configuration list for PBXNativeTarget "AWSAPIPluginGen2GraphQLTests" */; + buildPhases = ( + 2163D6082BE96C90009689B1 /* Sources */, + 2163D6092BE96C90009689B1 /* Frameworks */, + 2163D60A2BE96C90009689B1 /* Resources */, + 2163D62E2BE974F3009689B1 /* Copy Configuration Files */, + ); + buildRules = ( + ); + dependencies = ( + 2163D6112BE96C90009689B1 /* PBXTargetDependency */, + ); + name = AWSAPIPluginGen2GraphQLTests; + productName = AWSAPIPluginGen2GraphQLTests; + productReference = 2163D60C2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 21698A7A28899804004BD994 /* AWSAPIPluginFunctionalTests */ = { isa = PBXNativeTarget; buildConfigurationList = 21698A8328899805004BD994 /* Build configuration list for PBXNativeTarget "AWSAPIPluginFunctionalTests" */; @@ -1612,25 +1621,6 @@ productReference = 21EA887328F9BC600000BA75 /* AWSAPIPluginLazyLoadTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 21F7624D2BD6B0710048845A /* AWSAPIPluginGen2FunctionalTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 21F7629A2BD6B0710048845A /* Build configuration list for PBXNativeTarget "AWSAPIPluginGen2FunctionalTests" */; - buildPhases = ( - 21F762502BD6B0710048845A /* Sources */, - 21F762972BD6B0710048845A /* Frameworks */, - 21F762982BD6B0710048845A /* Resources */, - 21F762992BD6B0710048845A /* Copy Configuration folder */, - ); - buildRules = ( - ); - dependencies = ( - 21F7624E2BD6B0710048845A /* PBXTargetDependency */, - ); - name = AWSAPIPluginGen2FunctionalTests; - productName = AWSAPIPluginFunctionalTests; - productReference = 21F7629D2BD6B0710048845A /* AWSAPIPluginGen2FunctionalTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 395906AB28AC4A16004B96B1 /* AWSAPIPluginRESTIAMTests */ = { isa = PBXNativeTarget; buildConfigurationList = 395906B428AC4A16004B96B1 /* Build configuration list for PBXNativeTarget "AWSAPIPluginRESTIAMTests" */; @@ -1834,6 +1824,10 @@ CreatedOnToolsVersion = 14.0; TestTargetID = 21E73E6A28898D7800D7DB7E; }; + 2163D60B2BE96C90009689B1 = { + CreatedOnToolsVersion = 15.2; + TestTargetID = 21E73E6A28898D7800D7DB7E; + }; 21698A7A28899804004BD994 = { CreatedOnToolsVersion = 13.4.1; LastSwiftMigration = 1340; @@ -1911,7 +1905,7 @@ 681B353E2A43962D0074F369 /* AWSAPIPluginFunctionalTestsWatch */, 681B35912A4396CF0074F369 /* AWSAPIPluginGraphQLLambdaAuthTestsWatch */, 681B35B62A43970A0074F369 /* AWSAPIPluginRESTIAMTestsWatch */, - 21F7624D2BD6B0710048845A /* AWSAPIPluginGen2FunctionalTests */, + 2163D60B2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLTests */, ); }; /* End PBXProject section */ @@ -1924,37 +1918,37 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 21698A7928899804004BD994 /* Resources */ = { + 2163D60A2BE96C90009689B1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 21698A9328899818004BD994 /* Resources */ = { + 21698A7928899804004BD994 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 21E73E6928898D7800D7DB7E /* Resources */ = { + 21698A9328899818004BD994 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 21E73E7628898D7A00D7DB7E /* Preview Assets.xcassets in Resources */, - 21E73E7328898D7A00D7DB7E /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 21EA887128F9BC600000BA75 /* Resources */ = { + 21E73E6928898D7800D7DB7E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 21E73E7628898D7A00D7DB7E /* Preview Assets.xcassets in Resources */, + 21E73E7328898D7A00D7DB7E /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 21F762982BD6B0710048845A /* Resources */ = { + 21EA887128F9BC600000BA75 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -2046,7 +2040,7 @@ 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"; }; - 21698CC12889D5FE004BD994 /* Copy Configuration folder */ = { + 2163D62E2BE974F3009689B1 /* Copy Configuration Files */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2055,7 +2049,7 @@ ); inputPaths = ( ); - name = "Copy Configuration folder"; + name = "Copy Configuration Files"; outputFileListPaths = ( ); outputPaths = ( @@ -2064,7 +2058,7 @@ 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"; }; - 21EA887C28F9BC720000BA75 /* Copy Configuration Files */ = { + 21698CC12889D5FE004BD994 /* Copy Configuration folder */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2073,16 +2067,16 @@ ); inputPaths = ( ); - name = "Copy Configuration Files"; + name = "Copy Configuration folder"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nTEMP_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"; + 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"; }; - 21F762992BD6B0710048845A /* Copy Configuration folder */ = { + 21EA887C28F9BC720000BA75 /* Copy Configuration Files */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2091,14 +2085,14 @@ ); inputPaths = ( ); - name = "Copy Configuration folder"; + 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"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nTEMP_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"; }; 395906B528AC4A22004B96B1 /* Copy Configuration Files */ = { isa = PBXShellScriptBuildPhase; @@ -2278,6 +2272,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2163D6082BE96C90009689B1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2163D61B2BE97494009689B1 /* GraphQLLazyLoadPostComment4V2Tests.swift in Sources */, + 2163D60F2BE96C90009689B1 /* AWSAPIPluginGen2GraphQLBaseTest.swift in Sources */, + 2163D62D2BE974E6009689B1 /* PostWithCompositeKey+Schema.swift in Sources */, + 2163D6252BE974D8009689B1 /* Comment4V2.swift in Sources */, + 2163D6242BE974D8009689B1 /* Post4V2.swift in Sources */, + 2163D6172BE96E3D009689B1 /* TestConfigHelper.swift in Sources */, + 2163D62A2BE974E6009689B1 /* PostWithCompositeKey.swift in Sources */, + 2163D61D2BE974B0009689B1 /* GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift in Sources */, + 2163D62C2BE974E6009689B1 /* CommentWithCompositeKey+Schema.swift in Sources */, + 2163D62B2BE974E6009689B1 /* CommentWithCompositeKey.swift in Sources */, + 2163D6222BE974D8009689B1 /* Comment4V2+Schema.swift in Sources */, + 2163D6232BE974D8009689B1 /* Post4V2+Schema.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 21698A7728899804004BD994 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2511,83 +2524,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 21F762502BD6B0710048845A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 21F762512BD6B0710048845A /* Team2+Schema.swift in Sources */, - 21F762522BD6B0710048845A /* EnumTestModel.swift in Sources */, - 21F762532BD6B0710048845A /* GraphQLScalarAPISwiftTests.swift in Sources */, - 21F762542BD6B0710048845A /* ScalarContainer.swift in Sources */, - 21F762552BD6B0710048845A /* Project2.swift in Sources */, - 21F762562BD6B0710048845A /* EnumTestModel+Schema.swift in Sources */, - 21F762572BD6B0710048845A /* ListStringContainer.swift in Sources */, - 21F762582BD6B0710048845A /* GraphQLConnectionScenario3Tests+List.swift in Sources */, - 21F762592BD6B0710048845A /* GraphQLConnectionScenario3Tests+Helpers.swift in Sources */, - 21F7625A2BD6B0710048845A /* AsyncTesting.swift in Sources */, - 21F7625B2BD6B0710048845A /* ListIntContainer.swift in Sources */, - 21F7625C2BD6B0710048845A /* Team2.swift in Sources */, - 21F7625D2BD6B0710048845A /* GraphQLConnectionScenario3APISwiftTests+Subscribe.swift in Sources */, - 21F7625E2BD6B0710048845A /* Todo.swift in Sources */, - 21F7625F2BD6B0710048845A /* ListStringContainer+Schema.swift in Sources */, - 21F762602BD6B0710048845A /* Project2+Schema.swift in Sources */, - 21F762612BD6B0710048845A /* NestedTypeTestModel+Schema.swift in Sources */, - 21F762622BD6B0710048845A /* NestedTypeTestModel.swift in Sources */, - 21F762632BD6B0710048845A /* Post5+Schema.swift in Sources */, - 21F762642BD6B0710048845A /* TestEnum.swift in Sources */, - 21F762652BD6B0710048845A /* GraphQLConnectionScenario3Tests.swift in Sources */, - 21F762662BD6B0710048845A /* XCTestCase+AsyncTesting.swift in Sources */, - 21F762672BD6B0710048845A /* GraphQLTestBase.swift in Sources */, - 21F762682BD6B0710048845A /* GraphQLConnectionScenario4Tests.swift in Sources */, - 21F762692BD6B0710048845A /* PostEditor5+Schema.swift in Sources */, - 21F7626A2BD6B0710048845A /* API.swift in Sources */, - 21F7626B2BD6B0710048845A /* Nested.swift in Sources */, - 21F7626C2BD6B0710048845A /* Comment3.swift in Sources */, - 21F7626D2BD6B0710048845A /* Comment6+Schema.swift in Sources */, - 21F7626E2BD6B0710048845A /* TestConfigHelper.swift in Sources */, - 21F7626F2BD6B0710048845A /* Team1.swift in Sources */, - 21F762702BD6B0710048845A /* Comment+Schema.swift in Sources */, - 21F762712BD6B0710048845A /* GraphQLConnectionScenario2Tests.swift in Sources */, - 21F762722BD6B0710048845A /* Post5.swift in Sources */, - 21F762732BD6B0710048845A /* Nested+Schema.swift in Sources */, - 21F762742BD6B0710048845A /* Post3+Schema.swift in Sources */, - 21F762752BD6B0710048845A /* GraphQLConnectionScenario6Tests.swift in Sources */, - 21F762762BD6B0710048845A /* GraphQLConnectionScenario1APISwiftTests.swift in Sources */, - 21F762772BD6B0710048845A /* Comment4.swift in Sources */, - 21F762782BD6B0710048845A /* GraphQLModelBasedTests.swift in Sources */, - 21F762792BD6B0710048845A /* ListIntContainer+Schema.swift in Sources */, - 21F7627A2BD6B0710048845A /* AsyncExpectation.swift in Sources */, - 21F7627B2BD6B0710048845A /* Post4+Schema.swift in Sources */, - 21F7627C2BD6B0710048845A /* GraphQLConnectionScenario3Tests+Subscribe.swift in Sources */, - 21F7627D2BD6B0710048845A /* Post+Schema.swift in Sources */, - 21F7627E2BD6B0710048845A /* Blog6.swift in Sources */, - 21F7627F2BD6B0710048845A /* Comment.swift in Sources */, - 21F762802BD6B0710048845A /* Post6.swift in Sources */, - 21F762812BD6B0710048845A /* AppSyncRealTimeClientTests.swift in Sources */, - 21F762822BD6B0710048845A /* Post3.swift in Sources */, - 21F762832BD6B0710048845A /* User5+Schema.swift in Sources */, - 21F762842BD6B0710048845A /* Blog6+Schema.swift in Sources */, - 21F762852BD6B0710048845A /* Comment6.swift in Sources */, - 21F762862BD6B0710048845A /* GraphQLScalarTests.swift in Sources */, - 21F762872BD6B0710048845A /* Post4.swift in Sources */, - 21F762882BD6B0710048845A /* ScalarContainer+Schema.swift in Sources */, - 21F762892BD6B0710048845A /* Project1.swift in Sources */, - 21F7628A2BD6B0710048845A /* Team1+Schema.swift in Sources */, - 21F7628B2BD6B0710048845A /* AmplifyModels.swift in Sources */, - 21F7628C2BD6B0710048845A /* User5.swift in Sources */, - 21F7628D2BD6B0710048845A /* GraphQLConnectionScenario1Tests.swift in Sources */, - 21F7628E2BD6B0710048845A /* PostStatus.swift in Sources */, - 21F7628F2BD6B0710048845A /* Post.swift in Sources */, - 21F762902BD6B0710048845A /* Project1+Schema.swift in Sources */, - 21F762912BD6B0710048845A /* Comment3+Schema.swift in Sources */, - 21F762922BD6B0710048845A /* GraphQLModelBasedTests+List.swift in Sources */, - 21F762932BD6B0710048845A /* Comment4+Schema.swift in Sources */, - 21F762942BD6B0710048845A /* Post6+Schema.swift in Sources */, - 21F762952BD6B0710048845A /* PostEditor5.swift in Sources */, - 21F762962BD6B0710048845A /* GraphQLConnectionScenario5Tests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 395906A828AC4A16004B96B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2775,6 +2711,11 @@ target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; targetProxy = 213DBC7928A6C47000B30280 /* PBXContainerItemProxy */; }; + 2163D6112BE96C90009689B1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; + targetProxy = 2163D6102BE96C90009689B1 /* PBXContainerItemProxy */; + }; 21698A8028899805004BD994 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; @@ -2790,11 +2731,6 @@ target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; targetProxy = 21EA887728F9BC610000BA75 /* PBXContainerItemProxy */; }; - 21F7624E2BD6B0710048845A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; - targetProxy = 21F7624F2BD6B0710048845A /* PBXContainerItemProxy */; - }; 395906B128AC4A16004B96B1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 21E73E6A28898D7800D7DB7E /* APIHostApp */; @@ -2882,6 +2818,53 @@ }; name = Release; }; + 2163D6132BE96C90009689B1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.api.AWSAPIPluginGen2GraphQLTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/APIHostApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/APIHostApp"; + }; + name = Debug; + }; + 2163D6142BE96C90009689B1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.api.AWSAPIPluginGen2GraphQLTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/APIHostApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/APIHostApp"; + }; + name = Release; + }; 21698A8128899805004BD994 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3218,61 +3201,6 @@ }; name = Release; }; - 21F7629B2BD6B0710048845A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.5; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.api.AWSAPIPluginFunctionalTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/APIHostApp.app/APIHostApp"; - }; - name = Debug; - }; - 21F7629C2BD6B0710048845A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.5; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.api.AWSAPIPluginFunctionalTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/APIHostApp.app/APIHostApp"; - }; - name = Release; - }; 395906B228AC4A16004B96B1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3709,6 +3637,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 2163D6122BE96C90009689B1 /* Build configuration list for PBXNativeTarget "AWSAPIPluginGen2GraphQLTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2163D6132BE96C90009689B1 /* Debug */, + 2163D6142BE96C90009689B1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 21698A8328899805004BD994 /* Build configuration list for PBXNativeTarget "AWSAPIPluginFunctionalTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3754,15 +3691,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 21F7629A2BD6B0710048845A /* Build configuration list for PBXNativeTarget "AWSAPIPluginGen2FunctionalTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 21F7629B2BD6B0710048845A /* Debug */, - 21F7629C2BD6B0710048845A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 395906B428AC4A16004B96B1 /* Build configuration list for PBXNativeTarget "AWSAPIPluginRESTIAMTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/APIHostApp.xcscheme b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/APIHostApp.xcscheme index 8d3f8617a9..5d52fc8542 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/APIHostApp.xcscheme +++ b/AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/xcshareddata/xcschemes/APIHostApp.xcscheme @@ -60,6 +60,17 @@ ReferencedContainer = "container:APIHostApp.xcodeproj"> + + + + - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + skipped = "NO" + parallelizable = "YES"> diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AWSAPIPluginGen2FunctionalTests.xctestplan b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AWSAPIPluginGen2FunctionalTests.xctestplan deleted file mode 100644 index 1567400a72..0000000000 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AWSAPIPluginGen2FunctionalTests.xctestplan +++ /dev/null @@ -1,39 +0,0 @@ -{ - "configurations" : [ - { - "id" : "59DC9034-3288-4494-BBD9-9F891FF0A7FA", - "name" : "Test Scheme Action", - "options" : { - - } - } - ], - "defaultOptions" : { - "commandLineArgumentEntries" : [ - { - "argument" : "GEN2" - } - ] - }, - "testTargets" : [ - { - "skippedTests" : [ - "AppSyncRealTimeClientTests", - "GraphQLConnectionScenario1Tests", - "GraphQLConnectionScenario2Tests", - "GraphQLConnectionScenario3Tests", - "GraphQLConnectionScenario4Tests", - "GraphQLConnectionScenario5Tests", - "GraphQLConnectionScenario6Tests", - "GraphQLScalarTests", - "GraphQLTestBase" - ], - "target" : { - "containerPath" : "container:APIHostApp.xcodeproj", - "identifier" : "21F7624D2BD6B0710048845A", - "name" : "AWSAPIPluginGen2FunctionalTests" - } - } - ], - "version" : 1 -} diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift index 1cec4e476b..44837045b1 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/Base/TestConfigHelper.swift @@ -6,25 +6,16 @@ // import Foundation -@_spi(InternalAmplifyConfiguration) @testable import Amplify +@testable import Amplify class TestConfigHelper { - static var useGen2Configuration: Bool { - ProcessInfo.processInfo.arguments.contains("GEN2") - } - static func retrieveAmplifyConfiguration(forResource: String) throws -> AmplifyConfiguration { let data = try retrieve(forResource: forResource) return try AmplifyConfiguration.decodeAmplifyConfiguration(from: data) } - static func retrieveAmplifyOutputsData(forResource: String) throws -> AmplifyOutputsData { - let data = try retrieve(forResource: forResource) - return try AmplifyOutputsData.decodeAmplifyOutputsData(from: data) - } - static func retrieveCredentials(forResource: String) throws -> [String: String] { let data = try retrieve(forResource: forResource) diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift index 1790c35b16..8d16762f17 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLModelBasedTests.swift @@ -18,7 +18,6 @@ import XCTest class GraphQLModelBasedTests: XCTestCase { static let amplifyConfiguration = "testconfiguration/GraphQLModelBasedTests-amplifyconfiguration" - static let amplifyOutputs = "testconfiguration/GraphQLModelBasedTests-amplify_outputs" final public class PostCommentModelRegistration: AmplifyModelRegistration { public func registerModels(registry: ModelRegistry.Type) { @@ -37,16 +36,10 @@ class GraphQLModelBasedTests: XCTestCase { do { try Amplify.add(plugin: plugin) - - if TestConfigHelper.useGen2Configuration { - let amplifyConfig = try TestConfigHelper.retrieveAmplifyOutputsData( - forResource: GraphQLModelBasedTests.amplifyOutputs) - try Amplify.configure(amplifyConfig) - } else { - let amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration( - forResource: GraphQLModelBasedTests.amplifyConfiguration) - try Amplify.configure(amplifyConfig) - } + let amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration( + forResource: GraphQLModelBasedTests.amplifyConfiguration) + try Amplify.configure(amplifyConfig) + ModelRegistry.register(modelType: Comment.self) ModelRegistry.register(modelType: Post.self) diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/README.md b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/README.md index ece9d90323..1a2c01c142 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/README.md +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/README.md @@ -250,112 +250,3 @@ cp amplifyconfiguration.json ~/.aws-amplify/amplify-ios/testconfiguration/GraphQ ``` You can now run the tests! - -## Schema: AWSAPIPluginGen2FunctionalTests - -The following steps demonstrate how to set up an GraphQL endpoint with AppSync using Amplify CLI (Gen2). The auth configured will be API Key. - -### Set-up - -At the time this was written, it follows the steps from here https://docs.amplify.aws/gen2/deploy-and-host/fullstack-branching/mono-and-multi-repos/ - -1. From a new folder, run `npm create amplify@beta`. This uses the following versions of the Amplify CLI, see `package.json` file below. - -```json -{ - ... - "devDependencies": { - "@aws-amplify/backend": "^0.13.0-beta.14", - "@aws-amplify/backend-cli": "^0.12.0-beta.16", - "aws-cdk": "^2.134.0", - "aws-cdk-lib": "^2.134.0", - "constructs": "^10.3.0", - "esbuild": "^0.20.2", - "tsx": "^4.7.1", - "typescript": "^5.4.3" - }, - "dependencies": { - "aws-amplify": "^6.0.25" - } -} - -``` -2. Update `amplify/data/resource.ts` to allow `public` access. This allows using API Key as the auth type to perform CRUD operations against the Comment and Post models. The resulting file should look like this - -```ts -const schema = a.schema({ - Post: a - .model({ - title: a.string().required(), - content: a.string().required(), - draft: a.boolean(), - rating: a.float(), - status: a.enum(["PRIVATE", "DRAFT", "PUBLISHED"]), - comments: a.hasMany('Comment') - }) - .authorization([a.allow.public()]), - Comment: a - .model({ - content: a.string().required(), - post: a.belongsTo('Post'), - }) - .authorization([a.allow.public()]), -}); -``` - -3. (Optional) Update the API Key expiry to the maximum. This should be done if this backend is used for CI testing. - -``` -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - // API Key is used for a.allow.public() rules - apiKeyAuthorizationMode: { - expiresInDays: 365, - }, - }, -}); -``` - -4. Deploy the backend with npx amplify sandbox - -For example, this deploys to a sandbox env and generates the amplify_outputs.json file. - -``` -npx amplify sandbox --config-out-dir ./config --config-version 1 --profile [PROFILE] -``` - -5. Copy the `amplify_outputs.json` file over to the test directory as `GraphQLModelBasedTests-amplify_outputs.json`. The tests will automatically pick this file up. Create the directories in this path first if it currently doesn't exist. - -``` -cp amplify_outputs.json ~/.aws-amplify/amplify-ios/testconfiguration/GraphQLModelBasedTests-amplify_outputs.json -``` - -6. (Optional) The code generated model files are already checked into the tests so you will only have to re-generate them if you are expecting modifications to them and replace the existing ones checked in. - -``` -npx amplify generate graphql-client-code --format=modelgen --model-target=swift --branch main --app-id [APP_ID] --profile [AWS_PROFILE] -``` - -### Deploying from a branch (Optional) - -If you want to be able utilize Git commits for deployments - -1. Commit and push the files to a git repository. - -2. Navigate to the AWS Amplify console (https://us-east-1.console.aws.amazon.com/amplify/home?region=us-east-1#/) - -3. Click on "Try Amplify Gen 2" button. - -4. Choose "Option 2: Start with an existing app", and choose Github, and press Next. - -5. Find the repository and branch, and click Next - -6. Click "Save and deploy" and wait for deployment to finish. - -7. Generate the `amplify_outputs.json` configuration file - -``` -npx amplify generate config --branch main --app-id [APP_ID] --profile [AWS_PROFILE] --config-version 1 -``` diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/AWSAPIPluginGen2GraphQLBaseTest.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/AWSAPIPluginGen2GraphQLBaseTest.swift new file mode 100644 index 0000000000..b8fb23a94a --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/AWSAPIPluginGen2GraphQLBaseTest.swift @@ -0,0 +1,243 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import AWSAPIPlugin +@_spi(InternalAmplifyConfiguration) @testable import Amplify +@testable import APIHostApp +@testable import AWSPluginsCore + +class AWSAPIPluginGen2GraphQLBaseTest: XCTestCase { + + var amplifyConfig: AmplifyOutputsData! + + override func setUp() { + continueAfterFailure = false + } + + override func tearDown() async throws { + await Amplify.reset() + try await Task.sleep(seconds: 1) + } + + func setupConfig() { + let basePath = "testconfiguration" + let baseFileName = "Gen2GraphQLTests" + let configFile = "\(basePath)/\(baseFileName)-amplify_outputs" + + do { + amplifyConfig = try TestConfigHelper.retrieveAmplifyOutputsData(forResource: configFile) + } catch { + XCTFail("Error during setup: \(error)") + } + } + + /// Setup API with given models + /// - Parameter models: DataStore models + func setup(withModels models: AmplifyModelRegistration, + logLevel: LogLevel = .verbose) async { + do { + setupConfig() + Amplify.Logging.logLevel = logLevel + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: models)) + try Amplify.configure(amplifyConfig) + + } catch { + XCTFail("Error during setup: \(error)") + } + } + + @discardableResult + func mutate(_ request: GraphQLRequest) async throws -> M { + do { + let graphQLResponse = try await Amplify.API.mutate(request: request) + switch graphQLResponse { + case .success(let model): + return model + case .failure(let graphQLError): + XCTFail("Failed with error \(graphQLError)") + } + } catch { + XCTFail("Failed with error \(error)") + } + + throw "See XCTFail message" + } + + func query(_ request: GraphQLRequest) async throws -> M? { + do { + let graphQLResponse = try await Amplify.API.query(request: request) + switch graphQLResponse { + case .success(let model): + return model + case .failure(let graphQLError): + XCTFail("Failed with error \(graphQLError)") + } + } catch { + XCTFail("Failed with error \(error)") + } + throw "See XCTFail message" + } + + func listQuery(_ request: GraphQLRequest>) async throws -> List { + do { + let graphQLResponse = try await Amplify.API.query(request: request) + switch graphQLResponse { + case .success(let models): + return models + case .failure(let graphQLError): + XCTFail("Failed with error \(graphQLError)") + } + } catch { + XCTFail("Failed with error \(error)") + } + throw "See XCTFail message" + } + + enum AssertLazyModelState { + case notLoaded(identifiers: [LazyReferenceIdentifier]?) + case loaded(model: M?) + } + + func assertLazyReference(_ lazyModel: LazyReference, + 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))") + } + } + } + + enum AssertListState { + case isNotLoaded(associatedIdentifiers: [String], associatedFields: [String]) + case isLoaded(count: Int) + } + + func assertList(_ list: List, state: AssertListState) { + switch state { + case .isNotLoaded(let expectedAssociatedIdentifiers, let expectedAssociatedFields): + if case .notLoaded(let associatedIdentifiers, let associatedFields) = list.listProvider.getState() { + XCTAssertEqual(associatedIdentifiers, expectedAssociatedIdentifiers) + XCTAssertEqual(associatedFields, expectedAssociatedFields) + } else { + XCTFail("It should be not loaded with expected associatedIds \(expectedAssociatedIdentifiers) associatedFields \(expectedAssociatedFields)") + } + 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)") + } + } + } + + func assertModelExists(_ model: M) async throws { + let modelExists = try await query(for: model) != nil + XCTAssertTrue(modelExists) + } + + func assertModelDoesNotExist(_ model: M) async throws { + let modelExists = try await query(for: model) != nil + XCTAssertFalse(modelExists) + } + + func query(for model: M, includes: IncludedAssociations = { _ in [] }) async throws -> M? { + let id = M.identifier(model)(schema: model.schema) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: model.schema, + operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + + if let modelPath = M.rootPath as? ModelPath { + let associations = includes(modelPath) + documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) + } + documentBuilder.add(decorator: ModelIdDecorator(identifierFields: id.fields)) + let document = documentBuilder.build() + + let request = GraphQLRequest(document: document.stringValue, + variables: document.variables, + responseType: M?.self, + decodePath: document.name) + return try await query(request) + } + + func subscribe( + of modelType: M.Type, + type: GraphQLSubscriptionType, + verifyChange: @escaping (M) async throws -> Bool + ) async throws -> (XCTestExpectation, AmplifyAsyncThrowingSequence>) { + let connected = expectation(description: "Subscription connected") + let eventReceived = expectation(description: "\(type.rawValue) received") + let subscription = Amplify.API.subscribe(request: .subscription(of: modelType, type: type)) + + Task { + for try await subscriptionEvent in subscription { + if subscriptionEvent.isConnected() { + connected.fulfill() + } + + if let error = subscriptionEvent.extractError() { + XCTFail("Failed to \(type.rawValue) \(modelType), error: \(error.errorDescription)") + } + + if let data = subscriptionEvent.extractData(), + try await verifyChange(data) + { + eventReceived.fulfill() + } + } + } + + await fulfillment(of: [connected], timeout: 10) + return (eventReceived, subscription) + } +} + +extension LazyReferenceIdentifier: Equatable { + public static func == (lhs: LazyReferenceIdentifier, rhs: LazyReferenceIdentifier) -> Bool { + return lhs.name == rhs.name && lhs.value == rhs.value + } +} + + +extension GraphQLSubscriptionEvent { + func isConnected() -> Bool { + if case .connection(.connected) = self { + return true + } + return false + } + + func extractData() -> T? { + if case .data(.success(let data)) = self { + return data + } + return nil + } + + func extractError() -> GraphQLResponseError? { + if case .data(.failure(let error)) = self { + return error + } + return nil + } + +} diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2+Schema.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2+Schema.swift new file mode 100644 index 0000000000..e02097061d --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2+Schema.swift @@ -0,0 +1,66 @@ +// 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, provider: .apiKey, operations: [.create, .update, .delete, .read]) + ] + + model.listPluralName = "Comment4V2s" + model.syncPluralName = "Comment4V2s" + + model.attributes( + .index(fields: ["id"], name: nil), + .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) + ) + } + public class Path: ModelPath { } + + public static var rootPath: PropertyContainerPath? { Path() } +} + +extension Comment4V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} +extension ModelPath where ModelType == Comment4V2 { + public var id: FieldPath { + string("id") + } + public var content: FieldPath { + string("content") + } + public var post: ModelPath { + Post4V2.Path(name: "post", parent: self) + } + public var createdAt: FieldPath { + datetime("createdAt") + } + public var updatedAt: FieldPath { + datetime("updatedAt") + } +} \ No newline at end of file diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2.swift new file mode 100644 index 0000000000..1c6a50f04f --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Comment4V2.swift @@ -0,0 +1,56 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct Comment4V2: Model { + public let id: String + public var content: String + internal var _post: LazyReference + 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 = LazyReference(post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + public mutating func setPost(_ post: Post4V2? = nil) { + self._post = LazyReference(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.decodeIfPresent(LazyReference.self, forKey: .post) ?? LazyReference(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) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift new file mode 100644 index 0000000000..6f7750cdb3 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/GraphQLLazyLoadPostComment4V2Tests.swift @@ -0,0 +1,474 @@ +// +// 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 GraphQLLazyLoadPostComment4V2Tests: AWSAPIPluginGen2GraphQLBaseTest { + + func testSave() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + } + + // Without `includes` and latest codegenerated types with the model path, the post should be lazy loaded + func testCommentWithLazyLoadPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let createdComment = try await mutate(.create(comment)) + + // The comment's post should not be loaded, since no `includes` is passed in. + // And the codegenerated swift models have the new modelPath properties. + assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: createdPost.id)])) + let loadedPost = try await createdComment.post! + XCTAssertEqual(loadedPost.id, createdPost.id) + + // The loaded post should have comments that are also not loaded + let comments = loadedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [createdPost.id], associatedFields: ["post"])) + // load the comments + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + // the loaded comment's post should not be loaded + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: createdPost.id)])) + } + + // With `includes` on `comment.post`, the comment's post should be eager loaded. + func testCommentWithEagerLoadPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let createdComment = try await mutate(.create(comment, includes: { comment in [comment.post]})) + // The comment's post should be loaded, since `includes` include the post + assertLazyReference(createdComment._post, state: .loaded(model: createdPost)) + let loadedPost = try await createdComment.post! + XCTAssertEqual(loadedPost.id, post.id) + // The loaded post should have comments that are not loaded + let comments = loadedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [post.id], associatedFields: ["post"])) + // load the comments + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + // further nested models should not be loaded + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + } + + // With `includes` on `comment.post.comments`, + func testCommentWithEagerLoadPostAndPostComments() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let request = GraphQLRequest.create(comment, includes: { comment in [comment.post.comments]}) + let expectedDocument = """ + mutation CreateComment4V2($input: CreateComment4V2Input!) { + createComment4V2(input: $input) { + id + content + createdAt + updatedAt + post { + id + __typename + createdAt + title + updatedAt + comments { + items { + id + content + createdAt + updatedAt + post { + id + __typename + } + __typename + } + } + } + __typename + } + } + """ + XCTAssertEqual(request.document, expectedDocument) + let createdComment = try await mutate(request) + assertLazyReference(createdComment._post, state: .loaded(model: createdPost)) + let loadedPost = try await createdComment.post! + // The loaded post should have comments that are also loaded, since `includes` include the `post.comments` + let comments = loadedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + // further nested models should not be loaded + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.identifier)])) + } + + + /* + - Given: Api plugin is cleared + - When: + - Initialize with PostComment4V2Models + - Create post + - Create comment with [comment.post.comments.post] association path + - Then: + - The comment creation request GraphQL Selection Set represents the assocation path + */ + func testCommentWithEagerLoadPostAndPostCommentsAndPostCommentsPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + let request = GraphQLRequest.create(comment, includes: { comment in [comment.post.comments.post]}) + let expectedDocument = """ + mutation CreateComment4V2($input: CreateComment4V2Input!) { + createComment4V2(input: $input) { + id + content + createdAt + updatedAt + post { + id + __typename + createdAt + title + updatedAt + comments { + items { + id + content + createdAt + updatedAt + post { + id + createdAt + title + updatedAt + __typename + } + __typename + } + } + } + __typename + } + } + """ + XCTAssertEqual(request.document, expectedDocument) + try await mutate(request) + } + + // Without `includes` and latest codegenerated types with the model path, the post's comments should be lazy loaded + func testPostWithLazyLoadComments() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, byId: post.id))! + let comments = queriedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [post.id], associatedFields: ["post"])) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + } + + // With `includes` on `post.comments` should eager load the post's comments + func testPostWithEagerLoadComments() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, byId: post.id, includes: { post in [post.comments]}))! + let comments = queriedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + } + + // With `includes` on `post.comments.post` should eager load the post's comments' post + func testPostWithEagerLoadCommentsAndPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, byId: post.id, includes: { post in [post.comments.post]}))! + let comments = queriedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .loaded(model: createdPost)) + } + + func testListPostsListComments() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + + let queriedPosts = try await listQuery(.list(Post.self, where: Post.keys.id == post.id)) + assertList(queriedPosts, state: .isLoaded(count: 1)) + assertList(queriedPosts.first!.comments!, + state: .isNotLoaded(associatedIdentifiers: [post.id], associatedFields: ["post"])) + + let queriedComments = try await listQuery(.list(Comment.self, where: Comment.keys.id == comment.id)) + assertList(queriedComments, state: .isLoaded(count: 1)) + assertLazyReference(queriedComments.first!._post, + state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + } + + func testCreateWithoutPost() async throws { + await setup(withModels: PostComment4V2Models()) + let comment = Comment(content: "content") + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byId: comment.id))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: nil)) + let post = Post(title: "title") + let createdPost = try await mutate(.create(post)) + queriedComment.setPost(createdPost) + let updateCommentWithPost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, byId: updateCommentWithPost.id))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + let queriedCommentWithPost = try await query(.get(Comment.self, byId: queriedCommentAfterUpdate.id, includes: { comment in [comment.post]}))! + assertLazyReference(queriedCommentWithPost._post, state: .loaded(model: createdPost)) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byId: comment.id))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + + let newPost = Post(title: "title") + let createdNewPost = try await mutate(.create(newPost)) + queriedComment.setPost(newPost) + let updateCommentWithPost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, byId: updateCommentWithPost.id))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: [.init(name: "id", value: newPost.id)])) + let queriedCommentWithPost = try await query(.get(Comment.self, byId: queriedCommentAfterUpdate.id, includes: { comment in [comment.post]}))! + assertLazyReference(queriedCommentWithPost._post, state: .loaded(model: createdNewPost)) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byId: comment.id))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + + queriedComment.setPost(nil) + let updateCommentRemovePost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, byId: updateCommentRemovePost.id))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: nil)) + } + + func testDelete() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + try await mutate(.create(comment)) + + try await mutate(.delete(createdPost)) + let queriedPost = try await query(.get(Post.self, byId: post.id)) + XCTAssertNil(queriedPost) + let queriedComment = try await query(.get(Comment.self, byId: comment.id))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: nil)) + try await mutate(.delete(queriedComment)) + let queryDeletedComment = try await query(.get(Comment.self, byId: comment.id)) + XCTAssertNil(queryDeletedComment) + } + + func testSubscribeToComments() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + try await mutate(.create(post)) + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") + let subscription = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate)) + Task { + do { + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdComment): + log.verbose("Successfully got createdComment from subscription: \(createdComment)") + assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id)])) + onCreatedComment.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + let comment = Comment(content: "content", post: post) + try await mutate(.create(comment)) + await fulfillment(of: [onCreatedComment], timeout: 10) + subscription.cancel() + } + + // The identical `includes` parameter should be used because the selection set of the mutation + // has to match the selection set of the subscription. + func testSubscribeToCommentsIncludesPost() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + try await mutate(.create(post)) + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") + let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Comment.self, + type: .onCreate, + includes: { comment in [comment.post]})) + Task { + do { + for try await subscriptionEvent in subscriptionIncludes { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdComment): + log.verbose("Successfully got createdComment from subscription: \(createdComment)") + assertLazyReference(createdComment._post, state: .loaded(model: post)) + onCreatedComment.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 20) + let comment = Comment(content: "content", post: post) + try await mutate(.create(comment, includes: { comment in [comment.post] })) + await fulfillment(of: [onCreatedComment], timeout: 20) + subscriptionIncludes.cancel() + } + + func testSubscribeToPosts() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") + let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate)) + Task { + do { + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdPost): + log.verbose("Successfully got createdPost from subscription: \(createdPost)") + assertList(createdPost.comments!, state: .isNotLoaded(associatedIdentifiers: [post.id], associatedFields: ["post"])) + onCreatedPost.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + try await mutate(.create(post)) + await fulfillment(of: [onCreatedPost], timeout: 10) + subscription.cancel() + } + + func testSubscribeToPostsIncludes() async throws { + await setup(withModels: PostComment4V2Models()) + let post = Post(title: "title") + + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") + let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Post.self, + type: .onCreate, + includes: { post in [post.comments]})) + Task { + do { + for try await subscriptionEvent in subscriptionIncludes { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdPost): + log.verbose("Successfully got createdPost from subscription: \(createdPost)") + assertList(createdPost.comments!, state: .isLoaded(count: 0)) + onCreatedPost.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + try await mutate(.create(post, includes: { post in [post.comments]})) + await fulfillment(of: [onCreatedPost], timeout: 10) + subscriptionIncludes.cancel() + } +} + +extension GraphQLLazyLoadPostComment4V2Tests: DefaultLogger { } + +extension GraphQLLazyLoadPostComment4V2Tests { + 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/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Post4V2+Schema.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Post4V2+Schema.swift new file mode 100644 index 0000000000..0bbc928091 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Post4V2+Schema.swift @@ -0,0 +1,66 @@ +// 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, provider: .apiKey, operations: [.create, .update, .delete, .read]) + ] + + model.listPluralName = "Post4V2s" + model.syncPluralName = "Post4V2s" + + model.attributes( + .index(fields: ["id"], name: nil), + .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, associatedFields: [Comment4V2.keys.post]), + .field(post4V2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(post4V2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } + public class Path: ModelPath { } + + public static var rootPath: PropertyContainerPath? { Path() } +} + +extension Post4V2: ModelIdentifiable { + public typealias IdentifierFormat = ModelIdentifierFormat.Default + public typealias IdentifierProtocol = DefaultModelIdentifier +} +extension ModelPath where ModelType == Post4V2 { + public var id: FieldPath { + string("id") + } + public var title: FieldPath { + string("title") + } + public var comments: ModelPath { + Comment4V2.Path(name: "comments", isCollection: true, parent: self) + } + public var createdAt: FieldPath { + datetime("createdAt") + } + public var updatedAt: FieldPath { + datetime("updatedAt") + } +} diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Post4V2.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL1/Post4V2.swift new file mode 100644 index 0000000000..08f3e4b429 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/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/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey+Schema.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..8e4fbcf9d2 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey+Schema.swift @@ -0,0 +1,73 @@ +// 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.authRules = [ + rule(allow: .public, provider: .apiKey, operations: [.create, .update, .delete, .read]) + ] + + model.listPluralName = "CommentWithCompositeKeys" + model.syncPluralName = "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) + ) + } + public class Path: ModelPath { } + + public static var rootPath: PropertyContainerPath? { Path() } +} + +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)]) + } +} +extension ModelPath where ModelType == CommentWithCompositeKey { + public var id: FieldPath { + string("id") + } + public var content: FieldPath { + string("content") + } + public var post: ModelPath { + PostWithCompositeKey.Path(name: "post", parent: self) + } + public var createdAt: FieldPath { + datetime("createdAt") + } + public var updatedAt: FieldPath { + datetime("updatedAt") + } +} \ No newline at end of file diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey.swift new file mode 100644 index 0000000000..2ac30be7be --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/CommentWithCompositeKey.swift @@ -0,0 +1,56 @@ +// swiftlint:disable all +import Amplify +import Foundation + +public struct CommentWithCompositeKey: Model { + public let id: String + public let content: String + internal var _post: LazyReference + 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 = LazyReference(post) + self.createdAt = createdAt + self.updatedAt = updatedAt + } + public mutating func setPost(_ post: PostWithCompositeKey? = nil) { + self._post = LazyReference(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.decodeIfPresent(LazyReference.self, forKey: .post) ?? LazyReference(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) + } +} \ No newline at end of file diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift new file mode 100644 index 0000000000..f6af16533f --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/GraphQLLazyLoadPostCommentWithCompositeKeyTests.swift @@ -0,0 +1,447 @@ +// +// 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 GraphQLLazyLoadPostCommentWithCompositeKeyTests: AWSAPIPluginGen2GraphQLBaseTest { + + func testSave() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let savedPost = try await mutate(.create(post)) + let savedComment = try await mutate(.create(comment)) + } + + func testCommentWithLazyLoadPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let createdComment = try await mutate(.create(comment)) + + // The comment's post should not be loaded, since no `includes` is passed in. + // And the codegenerated swift models have the new modelPath properties. + assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: createdPost.id), + .init(name: "title", value: createdPost.title)])) + let loadedPost = try await createdComment.post! + XCTAssertEqual(loadedPost.id, createdPost.id) + + // The loaded post should have comments that are also not loaded + let comments = loadedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], + associatedFields: ["post"])) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: createdPost.id), + .init(name: "title", value: createdPost.title)])) + } + + // With `includes` on `comment.post`, the comment's post should be eager loaded. + func testCommentWithEagerLoadPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let createdComment = try await mutate(.create(comment, includes: { comment in [comment.post]})) + // The comment's post should be loaded, since `includes` include the post + assertLazyReference(createdComment._post, state: .loaded(model: createdPost)) + let loadedPost = try await createdComment.post! + XCTAssertEqual(loadedPost.id, post.id) + // The loaded post should have comments that are not loaded + let comments = loadedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], + associatedFields: ["post"])) + // load the comments + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + // further nested models should not be loaded + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: createdPost.id), + .init(name: "title", value: createdPost.title)])) + } + + // With `includes` on `comment.post.comments`, + func testCommentWithEagerLoadPostAndPostComments() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + let request = GraphQLRequest.create(comment, includes: { comment in [comment.post.comments]}) + let expectedDocument = """ + mutation CreateCommentWithCompositeKey($input: CreateCommentWithCompositeKeyInput!) { + createCommentWithCompositeKey(input: $input) { + id + content + createdAt + updatedAt + post { + id + title + __typename + createdAt + updatedAt + comments { + items { + id + content + createdAt + updatedAt + post { + id + title + __typename + } + __typename + } + } + } + __typename + } + } + """ + XCTAssertEqual(request.document, expectedDocument) + let createdComment = try await mutate(request) + assertLazyReference(createdComment._post, state: .loaded(model: createdPost)) + let loadedPost = try await createdComment.post! + // The loaded post should have comments that are also loaded, since `includes` include the `post.comments` + let comments = loadedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + // further nested models should not be loaded + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + } + + // Without `includes` and latest codegenerated types with the model path, the post's comments should be lazy loaded + func testPostWithLazyLoadComments() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, byIdentifier: .identifier(id: post.id, title: post.title)))! + let comments = queriedPost.comments! + assertList(comments, state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], associatedFields: ["post"])) + try await comments.fetch() + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + } + + // With `includes` on `post.comments` should eager load the post's comments + func testPostWithEagerLoadComments() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + _ = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, byIdentifier: .identifier(id: post.id, title: post.title), includes: { post in [post.comments]}))! + let comments = queriedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + } + + // With `includes` on `post.comments.post` should eager load the post's comments' post + func testPostWithEagerLoadCommentsAndPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + _ = try await mutate(.create(comment)) + let queriedPost = try await query(.get(Post.self, + byIdentifier: .identifier(id: post.id, + title: post.title), + includes: { post in [post.comments.post]}))! + let comments = queriedPost.comments! + assertList(comments, state: .isLoaded(count: 1)) + assertLazyReference(comments.first!._post, state: .loaded(model: createdPost)) + } + + func testListPostsListComments() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + + let queriedPosts = try await listQuery(.list(Post.self, where: Post.keys.id == post.id)) + assertList(queriedPosts, state: .isLoaded(count: 1)) + assertList(queriedPosts.first!.comments!, + state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], associatedFields: ["post"])) + + let queriedComments = try await listQuery(.list(Comment.self, where: Comment.keys.id == comment.id)) + assertList(queriedComments, state: .isLoaded(count: 1)) + assertLazyReference(queriedComments.first!._post, + state: .notLoaded(identifiers: [ + .init(name: "id", value: post.id), + .init(name: "title", value: "title")])) + } + + func testCreateWithoutPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let comment = Comment(content: "content") + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byIdentifier: .identifier(id: comment.id, content: comment.content)))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: nil)) + let post = Post(title: "title") + let createdPost = try await mutate(.create(post)) + queriedComment.setPost(createdPost) + let updateCommentWithPost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, + byIdentifier: .identifier(id: updateCommentWithPost.id, + content: updateCommentWithPost.content)))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + let queriedCommentWithPost = try await query(.get(Comment.self, + byIdentifier: .identifier(id: queriedCommentAfterUpdate.id, + content: queriedCommentAfterUpdate.content), + includes: { comment in [comment.post]}))! + assertLazyReference(queriedCommentWithPost._post, state: .loaded(model: createdPost)) + } + + func testUpdateToNewPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byIdentifier: .identifier(id: comment.id, content: comment.content)))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + + let newPost = Post(title: "title") + let createdNewPost = try await mutate(.create(newPost)) + queriedComment.setPost(newPost) + let updateCommentWithPost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, + byIdentifier: .identifier(id: updateCommentWithPost.id, + content: updateCommentWithPost.content)))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: [.init(name: "id", value: newPost.id), + .init(name: "title", value: newPost.title)])) + let queriedCommentWithPost = try await query(.get(Comment.self, + byIdentifier: .identifier(id: queriedCommentAfterUpdate.id, + content: queriedCommentAfterUpdate.content), + includes: { comment in [comment.post]}))! + assertLazyReference(queriedCommentWithPost._post, state: .loaded(model: createdNewPost)) + } + + func testUpdateRemovePost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + try await mutate(.create(post)) + try await mutate(.create(comment)) + var queriedComment = try await query(.get(Comment.self, byIdentifier: .identifier(id: comment.id, content: comment.content)))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + + queriedComment.setPost(nil) + let updateCommentRemovePost = try await mutate(.update(queriedComment)) + let queriedCommentAfterUpdate = try await query(.get(Comment.self, byIdentifier: .identifier(id: updateCommentRemovePost.id, content: updateCommentRemovePost.content)))! + assertLazyReference(queriedCommentAfterUpdate._post, state: .notLoaded(identifiers: nil)) + } + + func testDelete() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + let comment = Comment(content: "content", post: post) + let createdPost = try await mutate(.create(post)) + try await mutate(.create(comment)) + + try await mutate(.delete(createdPost)) + let queriedPost = try await query(.get(Post.self, byIdentifier: .identifier(id: post.id, title: post.title))) + XCTAssertNil(queriedPost) + let queriedComment = try await query(.get(Comment.self, byIdentifier: .identifier(id: comment.id, content: comment.content)))! + assertLazyReference(queriedComment._post, state: .notLoaded(identifiers: nil)) + try await mutate(.delete(queriedComment)) + let queryDeletedComment = try await query(.get(Comment.self, byIdentifier: .identifier(id: comment.id, content: comment.content))) + XCTAssertNil(queryDeletedComment) + } + + func testSubscribeToComments() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + try await mutate(.create(post)) + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") + let subscription = Amplify.API.subscribe(request: .subscription(of: Comment.self, type: .onCreate)) + Task { + do { + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdComment): + log.verbose("Successfully got createdComment from subscription: \(createdComment)") + assertLazyReference(createdComment._post, state: .notLoaded(identifiers: [.init(name: "id", value: post.id), + .init(name: "title", value: post.title)])) + onCreatedComment.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + let comment = Comment(content: "content", post: post) + try await mutate(.create(comment)) + await fulfillment(of: [onCreatedComment], timeout: 10) + subscription.cancel() + } + + // The identical `includes` parameter should be used because the selection set of the mutation + // has to match the selection set of the subscription. + func testSubscribeToCommentsIncludesPost() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + try await mutate(.create(post)) + let connected = expectation(description: "subscription connected") + let onCreatedComment = expectation(description: "onCreatedComment received") + let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Comment.self, + type: .onCreate, + includes: { comment in [comment.post]})) + Task { + do { + for try await subscriptionEvent in subscriptionIncludes { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdComment): + log.verbose("Successfully got createdComment from subscription: \(createdComment)") + assertLazyReference(createdComment._post, state: .loaded(model: post)) + onCreatedComment.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 20) + let comment = Comment(content: "content", post: post) + try await mutate(.create(comment, includes: { comment in [comment.post] })) + await fulfillment(of: [onCreatedComment], timeout: 20) + subscriptionIncludes.cancel() + } + + func testSubscribeToPosts() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") + let subscription = Amplify.API.subscribe(request: .subscription(of: Post.self, type: .onCreate)) + Task { + do { + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdPost): + log.verbose("Successfully got createdPost from subscription: \(createdPost)") + assertList(createdPost.comments!, state: .isNotLoaded(associatedIdentifiers: [post.id, post.title], associatedFields: ["post"])) + onCreatedPost.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + try await mutate(.create(post)) + await fulfillment(of: [onCreatedPost], timeout: 10) + subscription.cancel() + } + + func testSubscribeToPostsIncludes() async throws { + await setup(withModels: PostCommentWithCompositeKeyModels()) + let post = Post(title: "title") + + let connected = expectation(description: "subscription connected") + let onCreatedPost = expectation(description: "onCreatedPost received") + let subscriptionIncludes = Amplify.API.subscribe(request: .subscription(of: Post.self, + type: .onCreate, + includes: { post in [post.comments]})) + Task { + do { + for try await subscriptionEvent in subscriptionIncludes { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + log.verbose("Subscription connect state is \(subscriptionConnectionState)") + if case .connected = subscriptionConnectionState { + connected.fulfill() + } + case .data(let result): + switch result { + case .success(let createdPost): + log.verbose("Successfully got createdPost from subscription: \(createdPost)") + assertList(createdPost.comments!, state: .isLoaded(count: 0)) + onCreatedPost.fulfill() + case .failure(let error): + XCTFail("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + XCTFail("Subscription has terminated with \(error)") + } + } + + await fulfillment(of: [connected], timeout: 10) + try await mutate(.create(post, includes: { post in [post.comments]})) + await fulfillment(of: [onCreatedPost], timeout: 10) + subscriptionIncludes.cancel() + } +} + +extension GraphQLLazyLoadPostCommentWithCompositeKeyTests: DefaultLogger { } + +extension GraphQLLazyLoadPostCommentWithCompositeKeyTests { + 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/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/PostWithCompositeKey+Schema.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/PostWithCompositeKey+Schema.swift new file mode 100644 index 0000000000..a11b35aa0d --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/PostWithCompositeKey+Schema.swift @@ -0,0 +1,73 @@ +// 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.authRules = [ + rule(allow: .public, provider: .apiKey, operations: [.create, .update, .delete, .read]) + ] + + model.listPluralName = "PostWithCompositeKeys" + model.syncPluralName = "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) + ) + } + public class Path: ModelPath { } + + public static var rootPath: PropertyContainerPath? { Path() } +} + +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)]) + } +} +extension ModelPath where ModelType == PostWithCompositeKey { + public var id: FieldPath { + string("id") + } + public var title: FieldPath { + string("title") + } + public var comments: ModelPath { + CommentWithCompositeKey.Path(name: "comments", isCollection: true, parent: self) + } + public var createdAt: FieldPath { + datetime("createdAt") + } + public var updatedAt: FieldPath { + datetime("updatedAt") + } +} \ No newline at end of file diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/PostWithCompositeKey.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/LL3/PostWithCompositeKey.swift new file mode 100644 index 0000000000..e9de7de986 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/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/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/README.md b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/README.md new file mode 100644 index 0000000000..033e2c4ea2 --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/README.md @@ -0,0 +1,138 @@ +## Schema: AWSAPIPluginGen2GraphQLTests + +The following steps demonstrate how to set up an GraphQL endpoint with AppSync using Amplify CLI (Gen2). The auth configured will be API Key. + +### Set-up + +At the time this was written, it follows the steps from here https://docs.amplify.aws/gen2/deploy-and-host/fullstack-branching/mono-and-multi-repos/ + +1. From a new folder, run `npm create amplify@beta`. This uses the following versions of the Amplify CLI, see `package.json` file below. + +```json +{ + ... + "devDependencies": { + "@aws-amplify/backend": "^0.15.0", + "@aws-amplify/backend-cli": "^0.15.0", + "aws-cdk": "^2.139.0", + "aws-cdk-lib": "^2.139.0", + "constructs": "^10.3.0", + "esbuild": "^0.20.2", + "tsx": "^4.7.3", + "typescript": "^5.4.5" + }, + "dependencies": { + "aws-amplify": "^6.2.0" + }, +} +``` + +2. Update `amplify/data/resource.ts` to allow `public` access. This allows using API Key as the auth type to perform CRUD operations against the Comment and Post models. The resulting file should look like this + +```ts +const schema = a.schema({ + //# LL.1. Explicit Bi-Directional Belongs-to Has-many PostComment4V2 + //# 11 Explicit Bi-Directional Belongs to Relationship + Post4V2: a.model({ + id: a.id().required(), // You can omit this + title: a.string().required(), + comments: a.hasMany('Comment4V2', 'postID') + }) + .authorization(allow => [allow.publicApiKey()]), + Comment4V2: a.model({ + id: a.id().required(), // You can omit this + postID: a.id(), + content: a.string().required(), + post: a.belongsTo('Post4V2', 'postID') + }) + .authorization(allow => [allow.publicApiKey()]), + + //# 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` + PostWithCompositeKey: a + .model({ + id: a.id().required(), + title: a.string().required(), + comments: a.hasMany("CommentWithCompositeKey", []), + }) + .identifier(["id", "title"]) + .authorization((allow) => [allow.publicApiKey()]), + + CommentWithCompositeKey: a + .model({ + id: a.id().required(), + content: a.string().required(), + post: a.belongsTo("PostWithCompositeKey", []), + }) + .identifier(["id", "content"]) + .authorization((allow) => [allow.publicApiKey()]), +}); + +``` + +3. Update the API Key expiry to the maximum. This should be done if this backend is used for CI testing. + +``` +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + // API Key is used for a.allow.public() rules + apiKeyAuthorizationMode: { + expiresInDays: 365, + }, + }, +}); +``` + +4. Deploy the backend with npx amplify sandbox + +For example, this deploys to a sandbox env and generates the amplify_outputs.json file. + +``` +npx amplify sandbox --config-out-dir ./config --profile [PROFILE] +``` + +5. Copy `amplify_outputs.json` to a new file named `Gen2GraphQLTests-amplify_outputs.json` inside `~/.aws-amplify/amplify-ios/testconfiguration/` + +```perl +cp amplify_outputs.json ~/.aws-amplify/amplify-ios/testconfiguration/Gen2GraphQLTests-amplify_outputs.json +``` + +``` + +6. (Optional) The code generated model files are already checked into the tests so you will only have to re-generate them if you are expecting modifications to them and replace the existing ones checked in. + +``` +npx amplify generate graphql-client-code --format=modelgen --model-target=swift --out=models --profile lawmicha +``` + + +### Deploying from a branch (Optional) + +If you want to be able utilize Git commits for deployments + +1. Commit and push the files to a git repository. + +2. Navigate to the AWS Amplify console (https://us-east-1.console.aws.amazon.com/amplify/home?region=us-east-1#/) + +3. Click on "Try Amplify Gen 2" button. + +4. Choose "Option 2: Start with an existing app", and choose Github, and press Next. + +5. Find the repository and branch, and click Next + +6. Click "Save and deploy" and wait for deployment to finish. + +7. Generate the `amplify_outputs.json` configuration file + +``` +npx amplify generate outputs --branch main --app-id [APP_ID] --profile [AWS_PROFILE] +``` + +8. (Optional) The code generated model files are already checked into the tests so you will only have to re-generate them if you are expecting modifications to them and replace the existing ones checked in. + +``` +npx amplify generate graphql-client-code --format=modelgen --model-target=swift --branch main --app-id [APP_ID] --profile [AWS_PROFILE] +``` diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/TestConfigHelper.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/TestConfigHelper.swift new file mode 100644 index 0000000000..6818b3b76b --- /dev/null +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginGen2GraphQLTests/TestConfigHelper.swift @@ -0,0 +1,32 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +@_spi(InternalAmplifyConfiguration) @testable import Amplify + +class TestConfigHelper { + + static func retrieveAmplifyOutputsData(forResource: String) throws -> AmplifyOutputsData { + let data = try retrieve(forResource: forResource) + return try AmplifyOutputsData.decodeAmplifyOutputsData(from: data) + } + + 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) + } +} + +extension String { + var withUUID: String { + "\(self)-\(UUID().uuidString)" + } +}