UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

1,088 lines (1,087 loc) 178 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = require("stream"); const client_appsync_1 = require("@aws-sdk/client-appsync"); const client_s3_1 = require("@aws-sdk/client-s3"); const util_stream_1 = require("@smithy/util-stream"); const setup = require("./hotswap-test-setup"); const common_1 = require("../../../lib/api/hotswap/common"); const mock_sdk_1 = require("../../util/mock-sdk"); const silent_1 = require("../../util/silent"); let hotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); }); const getBodyStream = (input) => { const stream = new stream_1.Readable(); stream._read = () => { }; stream.push(input); stream.push(null); // close the stream return (0, util_stream_1.sdkStreamMixin)(stream); }; describe.each([common_1.HotswapMode.FALL_BACK, common_1.HotswapMode.HOTSWAP_ONLY])('%p mode', (hotswapMode) => { (0, silent_1.silentTest)(`A new Resolver being added to the Stack returns undefined in CLASSIC mode and returns a noOp in HOTSWAP_ONLY mode`, async () => { // GIVEN const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN if (hotswapMode === common_1.HotswapMode.FALL_BACK) { expect(deployStackResult).toBeUndefined(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { expect(deployStackResult).not.toBeUndefined(); expect(deployStackResult?.noOp).toEqual(true); } expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateFunctionCommand); expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateResolverCommand); }); (0, silent_1.silentTest)('calls the updateResolver() API when it receives only a mapping template difference in a Unit Resolver', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'UNIT', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'UNIT', RequestMappingTemplate: '## new request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', typeName: 'Query', fieldName: 'myField', kind: 'UNIT', requestMappingTemplate: '## new request template', responseMappingTemplate: '## original response template', }); }); (0, silent_1.silentTest)('calls the updateResolver() API when it receives only a mapping template difference s3 location in a Unit Resolver', async () => { // GIVEN const body = getBodyStream('template defined in s3'); mock_sdk_1.mockS3Client.on(client_s3_1.GetObjectCommand).resolves({ Body: body, }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'UNIT', RequestMappingTemplateS3Location: 's3://test-bucket/old_location', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'UNIT', RequestMappingTemplateS3Location: 's3://test-bucket/path/to/key', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', typeName: 'Query', fieldName: 'myField', kind: 'UNIT', requestMappingTemplate: 'template defined in s3', responseMappingTemplate: '## original response template', }); expect(mock_sdk_1.mockS3Client).toHaveReceivedCommandWith(client_s3_1.GetObjectCommand, { Bucket: 'test-bucket', Key: 'path/to/key', }); }); (0, silent_1.silentTest)('calls the updateResolver() API when it receives only a code s3 location in a Pipeline Resolver', async () => { // GIVEN const body = getBodyStream('code defined in s3'); mock_sdk_1.mockS3Client.on(client_s3_1.GetObjectCommand).resolves({ Body: body, }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', PipelineConfig: ['function1'], CodeS3Location: 's3://test-bucket/old_location', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', PipelineConfig: ['function1'], CodeS3Location: 's3://test-bucket/path/to/key', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', typeName: 'Query', fieldName: 'myField', pipelineConfig: ['function1'], code: 'code defined in s3', }); expect(mock_sdk_1.mockS3Client).toHaveReceivedCommandWith(client_s3_1.GetObjectCommand, { Bucket: 'test-bucket', Key: 'path/to/key', }); }); (0, silent_1.silentTest)('calls the updateResolver() API when it receives only a code difference in a Pipeline Resolver', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', PipelineConfig: ['function1'], Code: 'old code', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', PipelineConfig: ['function1'], Code: 'new code', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', typeName: 'Query', fieldName: 'myField', pipelineConfig: ['function1'], code: 'new code', }); }); (0, silent_1.silentTest)('calls the updateResolver() API when it receives only a mapping template difference in a Pipeline Resolver', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'PIPELINE', PipelineConfig: ['function1'], RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ApiId: 'apiId', FieldName: 'myField', TypeName: 'Query', DataSourceName: 'my-datasource', Kind: 'PIPELINE', PipelineConfig: ['function1'], RequestMappingTemplate: '## new request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', typeName: 'Query', fieldName: 'myField', kind: 'PIPELINE', pipelineConfig: ['function1'], requestMappingTemplate: '## new request template', responseMappingTemplate: '## original response template', }); }); (0, silent_1.silentTest)(`when it receives a change that is not a mapping template difference in a Resolver, it does not call the updateResolver() API in CLASSIC mode but does call the updateResolver() API in HOTSWAP_ONLY mode`, async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ResponseMappingTemplate: '## original response template', RequestMappingTemplate: '## original request template', FieldName: 'oldField', ApiId: 'apiId', TypeName: 'Query', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncResolver', 'AWS::AppSync::Resolver', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/types/Query/resolvers/myField')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::Resolver', Properties: { ResponseMappingTemplate: '## original response template', RequestMappingTemplate: '## new request template', FieldName: 'newField', ApiId: 'apiId', TypeName: 'Query', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN if (hotswapMode === common_1.HotswapMode.FALL_BACK) { expect(deployStackResult).toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateResolverCommand); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateResolverCommand, { apiId: 'apiId', typeName: 'Query', fieldName: 'oldField', requestMappingTemplate: '## new request template', responseMappingTemplate: '## original response template', }); } expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateFunctionCommand); }); (0, silent_1.silentTest)('does not call the updateResolver() API when a resource with type that is not AWS::AppSync::Resolver but has the same properties is changed', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncResolver: { Type: 'AWS::AppSync::NotAResolver', Properties: { RequestMappingTemplate: '## original template', FieldName: 'oldField', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncResolver: { Type: 'AWS::AppSync::NotAResolver', Properties: { RequestMappingTemplate: '## new template', FieldName: 'newField', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN if (hotswapMode === common_1.HotswapMode.FALL_BACK) { expect(deployStackResult).toBeUndefined(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { expect(deployStackResult).not.toBeUndefined(); expect(deployStackResult?.noOp).toEqual(true); } expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateFunctionCommand); expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateResolverCommand); }); (0, silent_1.silentTest)('calls the updateFunction() API when it receives only a mapping template difference in a Function', async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolves({ functions: [{ name: 'my-function', functionId: 'functionId' }] }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## new response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', functionVersion: '2018-05-29', name: 'my-function', requestMappingTemplate: '## original request template', responseMappingTemplate: '## new response template', }); }); (0, silent_1.silentTest)('calls the updateFunction() API with function version when it receives both function version and runtime with a mapping template in a Function', async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolves({ functions: [{ name: 'my-function', functionId: 'functionId' }] }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## new response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', functionVersion: '2018-05-29', name: 'my-function', requestMappingTemplate: '## original request template', responseMappingTemplate: '## new response template', }); }); (0, silent_1.silentTest)('calls the updateFunction() API with runtime when it receives both function version and runtime with code in a Function', async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolves({ functions: [{ name: 'my-function', functionId: 'functionId' }] }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', Code: 'old test code', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', Code: 'new test code', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', runtime: 'APPSYNC_JS', name: 'my-function', code: 'new test code', }); }); (0, silent_1.silentTest)('calls the updateFunction() API when it receives only a mapping template s3 location difference in a Function', async () => { // GIVEN mock_sdk_1.mockS3Client.on(client_s3_1.GetObjectCommand).resolves({ Body: getBodyStream('template defined in s3'), }); mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolves({ functions: [{ name: 'my-function', functionId: 'functionId' }] }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplateS3Location: 's3://test-bucket/old_location', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplateS3Location: 's3://test-bucket/path/to/key', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', functionVersion: '2018-05-29', name: 'my-function', requestMappingTemplate: '## original request template', responseMappingTemplate: 'template defined in s3', }); expect(mock_sdk_1.mockS3Client).toHaveReceivedCommandWith(client_s3_1.GetObjectCommand, { Bucket: 'test-bucket', Key: 'path/to/key', }); }); (0, silent_1.silentTest)(`when it receives a change that is not a mapping template difference in a Function, it does not call the updateFunction() API in CLASSIC mode but does in HOTSWAP_ONLY mode`, async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolves({ functions: [{ name: 'my-function', functionId: 'functionId' }] }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { RequestMappingTemplate: '## new request template', ResponseMappingTemplate: '## original response template', ApiId: 'apiId', Name: 'my-function', DataSourceName: 'new-datasource', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN if (hotswapMode === common_1.HotswapMode.FALL_BACK) { expect(deployStackResult).toBeUndefined(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', name: 'my-function', requestMappingTemplate: '## new request template', responseMappingTemplate: '## original response template', }); expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateResolverCommand); } }); (0, silent_1.silentTest)('does not call the updateFunction() API when a resource with type that is not AWS::AppSync::FunctionConfiguration but has the same properties is changed', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::NotAFunctionConfiguration', Properties: { RequestMappingTemplate: '## original template', Name: 'my-function', DataSourceName: 'my-datasource', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::NotAFunctionConfiguration', Properties: { RequestMappingTemplate: '## new template', Name: 'my-resolver', DataSourceName: 'my-datasource', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN if (hotswapMode === common_1.HotswapMode.FALL_BACK) { expect(deployStackResult).toBeUndefined(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { expect(deployStackResult).not.toBeUndefined(); expect(deployStackResult?.noOp).toEqual(true); } expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateFunctionCommand); expect(mock_sdk_1.mockAppSyncClient).not.toHaveReceivedCommand(client_appsync_1.UpdateResolverCommand); }); (0, silent_1.silentTest)('calls the startSchemaCreation() API when it receives only a definition difference in a graphql schema', async () => { // GIVEN mock_sdk_1.mockAppSyncClient.on(client_appsync_1.StartSchemaCreationCommand).resolvesOnce({ status: 'SUCCESS', }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncGraphQLSchema: { Type: 'AWS::AppSync::GraphQLSchema', Properties: { ApiId: 'apiId', Definition: 'original graphqlSchema', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncGraphQLSchema', 'AWS::AppSync::GraphQLSchema', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/schema/my-schema')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncGraphQLSchema: { Type: 'AWS::AppSync::GraphQLSchema', Properties: { ApiId: 'apiId', Definition: 'new graphqlSchema', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.StartSchemaCreationCommand, { apiId: 'apiId', definition: 'new graphqlSchema', }); }); (0, silent_1.silentTest)('updateFunction() API recovers from failed update attempt through retry logic', async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolvesOnce({ functions: [{ name: 'my-function', functionId: 'functionId' }], }); const ConcurrentModError = new Error('ConcurrentModificationException: Schema is currently being altered, please wait until that is complete.'); ConcurrentModError.name = 'ConcurrentModificationException'; mock_sdk_1.mockAppSyncClient .on(client_appsync_1.UpdateFunctionCommand) .rejectsOnce(ConcurrentModError) .resolvesOnce({ functionConfiguration: { name: 'my-function', dataSourceName: 'my-datasource', functionId: 'functionId' } }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## new response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandTimes(client_appsync_1.UpdateFunctionCommand, 2); // 1st failure then success on retry expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', functionVersion: '2018-05-29', name: 'my-function', requestMappingTemplate: '## original request template', responseMappingTemplate: '## new response template', }); }); (0, silent_1.silentTest)('updateFunction() API fails if it recieves 7 failed attempts in a row', async () => { // Ignore the wait times that the SDK tries to impose and always set timers for 1 ms const realSetTimeout = setTimeout; const mockSetTimeout = jest.spyOn(global, 'setTimeout').mockImplementation((fn) => { return realSetTimeout(fn, 1); }); // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolvesOnce({ functions: [{ name: 'my-function', functionId: 'functionId' }], }); const ConcurrentModError = new Error('ConcurrentModificationException: Schema is currently being altered, please wait until that is complete.'); ConcurrentModError.name = 'ConcurrentModificationException'; mock_sdk_1.mockAppSyncClient .on(client_appsync_1.UpdateFunctionCommand) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .rejectsOnce(ConcurrentModError) .resolvesOnce({ functionConfiguration: { name: 'my-function', dataSourceName: 'my-datasource', functionId: 'functionId' } }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## original response template', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', RequestMappingTemplate: '## original request template', ResponseMappingTemplate: '## new response template', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN await expect(() => hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact)).rejects.toThrow('ConcurrentModificationException'); // THEN expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandTimes(client_appsync_1.UpdateFunctionCommand, 7); // 1st attempt and then 6 retries before bailing expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', functionVersion: '2018-05-29', name: 'my-function', requestMappingTemplate: '## original request template', responseMappingTemplate: '## new response template', }); mockSetTimeout.mockRestore(); }, 320000); (0, silent_1.silentTest)('calls the updateFunction() API with functionId when function is listed on second page', async () => { // GIVEN mock_sdk_1.mockAppSyncClient .on(client_appsync_1.ListFunctionsCommand) .resolvesOnce({ functions: [{ name: 'other-function', functionId: 'other-functionId' }], nextToken: 'nextToken', }) .resolvesOnce({ functions: [{ name: 'my-function', functionId: 'functionId' }], }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', Code: 'old test code', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncFunction: { Type: 'AWS::AppSync::FunctionConfiguration', Properties: { Name: 'my-function', ApiId: 'apiId', DataSourceName: 'my-datasource', FunctionVersion: '2018-05-29', Runtime: 'APPSYNC_JS', Code: 'new test code', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandTimes(client_appsync_1.ListFunctionsCommand, 2); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedNthCommandWith(1, client_appsync_1.ListFunctionsCommand, { apiId: 'apiId', nextToken: 'nextToken', }); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedNthCommandWith(2, client_appsync_1.ListFunctionsCommand, { apiId: 'apiId', }); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.UpdateFunctionCommand, { apiId: 'apiId', dataSourceName: 'my-datasource', functionId: 'functionId', runtime: 'APPSYNC_JS', name: 'my-function', code: 'new test code', }); }); (0, silent_1.silentTest)('calls the startSchemaCreation() API when it receives only a definition difference in a graphql schema', async () => { // GIVEN mock_sdk_1.mockAppSyncClient.on(client_appsync_1.StartSchemaCreationCommand).resolves({ status: 'SUCCESS' }); setup.setCurrentCfnStackTemplate({ Resources: { AppSyncGraphQLSchema: { Type: 'AWS::AppSync::GraphQLSchema', Properties: { ApiId: 'apiId', Definition: 'original graphqlSchema', }, Metadata: { 'aws:asset:path': 'old-path', }, }, }, }); setup.pushStackResourceSummaries(setup.stackSummaryOf('AppSyncGraphQLSchema', 'AWS::AppSync::GraphQLSchema', 'arn:aws:appsync:us-east-1:111111111111:apis/apiId/schema/my-schema')); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { AppSyncGraphQLSchema: { Type: 'AWS::AppSync::GraphQLSchema', Properties: { ApiId: 'apiId', Definition: 'new graphqlSchema', }, Metadata: { 'aws:asset:path': 'new-path', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockAppSyncClient).toHaveReceivedCommandWith(client_appsync_1.StartSchemaCreationCommand, { apiId: 'apiId', definition: 'new graphqlSchema', }); }); (0, silent_1.silentTest)('calls the startSchemaCreation() API when it receives only a definition s3 location difference in a graphql schema', async () => { // GIVEN mock_sdk_1.mockS3Client.on(client_s3_1.GetObjectCommand).resolves({ Body: getBodyStream('schema defined in s3'), }); setup.setCurrentCfnStackTemplate({