aws-cdk
Version:
CDK Toolkit, the command line tool for CDK apps
1,088 lines (1,087 loc) • 178 kB
JavaScript
"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({