diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index a354c8a4b3196..f7be4f954d7e8 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, Lazy, Names, Stack } from '@aws-cdk/core'; +import { Arn, ArnFormat, Duration, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnAuthorizer } from '../apigateway.generated'; import { Authorizer, IAuthorizer } from '../authorizer'; @@ -254,5 +254,6 @@ export class RequestAuthorizer extends LambdaAuthorizer { * constructs the authorizerURIArn. */ function lambdaAuthorizerArn(handler: lambda.IFunction) { - return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; + const { region, partition } = Arn.split( handler.functionArn, ArnFormat.COLON_RESOURCE_NAME); + return `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; } diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json index 9768e9692a548..981f02ebed888 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json @@ -253,11 +253,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json index b3d35baa2e42c..eda922f948d66 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json @@ -122,11 +122,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json index f36705d28f193..237a238eefcaa 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json @@ -253,11 +253,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts index fdaa20af10cd3..6728e0204ed37 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts @@ -35,11 +35,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -89,11 +109,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -167,11 +207,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -218,11 +278,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -269,11 +349,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -341,11 +441,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts index 19bede1303437..da740d582bbad 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts @@ -150,7 +150,7 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { return Grant.addToPrincipal({ grantee: identity, actions: ['execute-api:ManageConnections'], - resourceArns: [`${arn}/*/POST/@connections/*`], + resourceArns: [`${arn}/*/*/@connections/*`], }); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts index 6d5cc8527fef0..685850a746f4e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts @@ -131,7 +131,7 @@ export class WebSocketStage extends StageBase implements IWebSocketStage { return Grant.addToPrincipal({ grantee: identity, actions: ['execute-api:ManageConnections'], - resourceArns: [`${arn}/${this.stageName}/POST/@connections/*`], + resourceArns: [`${arn}/${this.stageName}/*/@connections/*`], }); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts index ba687a79a9afe..1ac6cfbae315f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts @@ -141,7 +141,7 @@ describe('WebSocketApi', () => { { Ref: 'apiC8550315', }, - '/*/POST/@connections/*', + '/*/*/@connections/*', ]], }, }]), diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts index b873f7fa74efa..b1af6af2e59bc 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts @@ -99,7 +99,7 @@ describe('WebSocketStage', () => { { Ref: 'ApiF70053CD', }, - `/${defaultStage.stageName}/POST/@connections/*`, + `/${defaultStage.stageName}/*/@connections/*`, ]], }, }]), diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 202cee9c796a3..c251e138255da 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -82,7 +82,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/application.test.ts b/packages/@aws-cdk/aws-codedeploy/test/ecs/application.test.ts index 6a6966167cd2f..ec130559aaff8 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/ecs/application.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/application.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; import * as codedeploy from '../../lib'; @@ -7,7 +7,7 @@ describe('CodeDeploy ECS Application', () => { const stack = new cdk.Stack(); new codedeploy.EcsApplication(stack, 'MyApp'); - expect(stack).toHaveResource('AWS::CodeDeploy::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::Application', { ComputePlatform: 'ECS', }); }); @@ -18,7 +18,7 @@ describe('CodeDeploy ECS Application', () => { applicationName: 'my-name', }); - expect(stack).toHaveResource('AWS::CodeDeploy::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::Application', { ApplicationName: 'my-name', ComputePlatform: 'ECS', }); diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/application.test.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/application.test.ts index eaa140d0c045c..6ccbd816935ba 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/application.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/application.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; import * as codedeploy from '../../lib'; @@ -6,7 +6,7 @@ describe('CodeDeploy Lambda Application', () => { test('can be created', () => { const stack = new cdk.Stack(); new codedeploy.LambdaApplication(stack, 'MyApp'); - expect(stack).toHaveResource('AWS::CodeDeploy::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::Application', { ComputePlatform: 'Lambda', }); }); @@ -16,7 +16,7 @@ describe('CodeDeploy Lambda Application', () => { new codedeploy.LambdaApplication(stack, 'MyApp', { applicationName: 'my-name', }); - expect(stack).toHaveResource('AWS::CodeDeploy::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::Application', { ApplicationName: 'my-name', ComputePlatform: 'Lambda', }); diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/custom-deployment-config.test.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/custom-deployment-config.test.ts index 9f69328613dac..7755402502857 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/custom-deployment-config.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/custom-deployment-config.test.ts @@ -1,5 +1,4 @@ -import { ResourcePart } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import * as codedeploy from '../../lib'; @@ -45,7 +44,7 @@ test('custom resource created', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWS', { + Template.fromStack(stack).hasResourceProperties('Custom::AWS', { ServiceToken: { 'Fn::GetAtt': [ 'AWS679f53fac002430cb0da5b7982bd22872D164C4C', @@ -57,7 +56,7 @@ test('custom resource created', () => { Delete: '{"service":"CodeDeploy","action":"deleteDeploymentConfig","parameters":{"deploymentConfigName":"CustomConfig.LambdaCanary5Percent1Minutes"}}', }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -91,7 +90,7 @@ test('custom resource created with specific name', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWS', { + Template.fromStack(stack).hasResourceProperties('Custom::AWS', { Create: '{"service":"CodeDeploy","action":"createDeploymentConfig","parameters":{"deploymentConfigName":"MyDeploymentConfig","computePlatform":"Lambda","trafficRoutingConfig":{"type":"TimeBasedCanary","timeBasedCanary":{"canaryInterval":"1","canaryPercentage":"5"}}},"physicalResourceId":{"id":"MyDeploymentConfig"}}', Update: '{"service":"CodeDeploy","action":"createDeploymentConfig","parameters":{"deploymentConfigName":"MyDeploymentConfig","computePlatform":"Lambda","trafficRoutingConfig":{"type":"TimeBasedCanary","timeBasedCanary":{"canaryInterval":"1","canaryPercentage":"5"}}},"physicalResourceId":{"id":"MyDeploymentConfig"}}', Delete: '{"service":"CodeDeploy","action":"deleteDeploymentConfig","parameters":{"deploymentConfigName":"MyDeploymentConfig"}}', @@ -112,7 +111,7 @@ test('can create linear custom config', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { DeploymentConfigName: 'CustomConfig.LambdaLinear5PercentEvery1Minutes', }); }); @@ -131,7 +130,7 @@ test('can create canary custom config', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { DeploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', }); }); @@ -150,7 +149,7 @@ test('dependency on the config exists to ensure ordering', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResource('AWS::CodeDeploy::DeploymentGroup', { Properties: { DeploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', }, @@ -158,5 +157,5 @@ test('dependency on the config exists to ensure ordering', () => { 'CustomConfigDeploymentConfigCustomResourcePolicy0426B684', 'CustomConfigDeploymentConfigE9E1F384', ], - }, ResourcePart.CompleteDefinition); + }); }); diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.test.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.test.ts index 1f4b18e852cff..9333b05106220 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.test.ts @@ -1,5 +1,4 @@ -import { ResourcePart } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; @@ -33,7 +32,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { deploymentConfig: codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE, }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { ApplicationName: { Ref: 'MyApp3CE31C26', }, @@ -56,7 +55,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }); - expect(stack).toHaveResource('AWS::Lambda::Alias', { + Template.fromStack(stack).hasResource('AWS::Lambda::Alias', { Type: 'AWS::Lambda::Alias', Properties: { FunctionName: { @@ -80,9 +79,9 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }, }, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [{ Action: 'sts:AssumeRole', @@ -120,7 +119,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { deploymentGroupName: 'test', }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { DeploymentGroupName: 'test', }); }); @@ -140,7 +139,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { role: serviceRole, }); - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [{ Action: 'sts:AssumeRole', @@ -176,7 +175,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { ApplicationName: { Ref: 'MyApp3CE31C26', }, @@ -216,7 +215,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { })], }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { AlarmConfiguration: { Alarms: [{ Name: { @@ -268,7 +267,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { deploymentConfig: codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE, }); - expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { + Template.fromStack(stack).hasResource('AWS::Lambda::Alias', { UpdatePolicy: { CodeDeployLambdaAliasUpdate: { ApplicationName: { @@ -282,9 +281,9 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }, }, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyDGServiceRoleDefaultPolicy65E8E1EA', Roles: [{ Ref: 'MyDGServiceRole5E94FD88', @@ -316,7 +315,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }); group.addPreHook(mockFunction(stack, 'PreHook')); - expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { + Template.fromStack(stack).hasResource('AWS::Lambda::Alias', { UpdatePolicy: { CodeDeployLambdaAliasUpdate: { ApplicationName: { @@ -330,9 +329,9 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }, }, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyDGServiceRoleDefaultPolicy65E8E1EA', Roles: [{ Ref: 'MyDGServiceRole5E94FD88', @@ -364,7 +363,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { deploymentConfig: codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE, }); - expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { + Template.fromStack(stack).hasResource('AWS::Lambda::Alias', { UpdatePolicy: { CodeDeployLambdaAliasUpdate: { ApplicationName: { @@ -378,9 +377,9 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }, }, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyDGServiceRoleDefaultPolicy65E8E1EA', Roles: [{ Ref: 'MyDGServiceRole5E94FD88', @@ -412,7 +411,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }); group.addPostHook(mockFunction(stack, 'PostHook')); - expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { + Template.fromStack(stack).hasResource('AWS::Lambda::Alias', { UpdatePolicy: { CodeDeployLambdaAliasUpdate: { ApplicationName: { @@ -426,9 +425,9 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }, }, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyDGServiceRoleDefaultPolicy65E8E1EA', Roles: [{ Ref: 'MyDGServiceRole5E94FD88', @@ -467,7 +466,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { })], }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { AlarmConfiguration: { Alarms: [{ Name: { @@ -494,7 +493,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { ApplicationName: { Ref: 'MyApp3CE31C26', }, @@ -526,7 +525,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { AutoRollbackConfiguration: { Enabled: true, Events: [ @@ -557,7 +556,7 @@ describe('CodeDeploy Lambda DeploymentGroup', () => { })], }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { AutoRollbackConfiguration: { Enabled: true, Events: [ diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/deployment-config.test.ts b/packages/@aws-cdk/aws-codedeploy/test/server/deployment-config.test.ts index 2838bacfdc7fc..52652e8024b28 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/deployment-config.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/deployment-config.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; import * as codedeploy from '../../lib'; @@ -12,7 +12,7 @@ describe('CodeDeploy DeploymentConfig', () => { minimumHealthyHosts: codedeploy.MinimumHealthyHosts.count(1), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentConfig', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentConfig', { 'MinimumHealthyHosts': { 'Type': 'HOST_COUNT', 'Value': 1, @@ -27,7 +27,7 @@ describe('CodeDeploy DeploymentConfig', () => { minimumHealthyHosts: codedeploy.MinimumHealthyHosts.percentage(75), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentConfig', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentConfig', { 'MinimumHealthyHosts': { 'Type': 'FLEET_PERCENT', 'Value': 75, diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/deployment-group.test.ts b/packages/@aws-cdk/aws-codedeploy/test/server/deployment-group.test.ts index 79d20323d5a21..43acaadc3e7fc 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/deployment-group.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/deployment-group.test.ts @@ -1,5 +1,4 @@ -import { SynthUtils } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; @@ -18,7 +17,7 @@ describe('CodeDeploy Server Deployment Group', () => { application, }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'ApplicationName': { 'Ref': 'MyApp3CE31C26', }, @@ -36,9 +35,8 @@ describe('CodeDeploy Server Deployment Group', () => { value: serverDeploymentGroup.application.applicationName, }); - expect(stack2).toHaveOutput({ - outputName: 'Output', - outputValue: 'defaultmydgapplication78dba0bb0c7580b32033', + Template.fromStack(stack2).hasOutput('Output', { + Value: 'defaultmydgapplication78dba0bb0c7580b32033', }); }); @@ -68,7 +66,7 @@ describe('CodeDeploy Server Deployment Group', () => { installAgent: true, }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { 'UserData': { 'Fn::Base64': { 'Fn::Join': [ @@ -104,7 +102,7 @@ describe('CodeDeploy Server Deployment Group', () => { installAgent: true, }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { 'UserData': { 'Fn::Base64': { 'Fn::Join': [ @@ -135,7 +133,7 @@ describe('CodeDeploy Server Deployment Group', () => { autoScalingGroups: [asg], }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'AutoScalingGroups': [ { 'Ref': 'ASG46ED3070', @@ -156,7 +154,7 @@ describe('CodeDeploy Server Deployment Group', () => { const deploymentGroup = new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup'); deploymentGroup.addAutoScalingGroup(asg); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'AutoScalingGroups': [ { 'Ref': 'ASG46ED3070', @@ -178,7 +176,7 @@ describe('CodeDeploy Server Deployment Group', () => { loadBalancer: codedeploy.LoadBalancer.application(targetGroup), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'LoadBalancerInfo': { 'TargetGroupInfoList': [ { @@ -210,7 +208,7 @@ describe('CodeDeploy Server Deployment Group', () => { loadBalancer: codedeploy.LoadBalancer.network(targetGroup), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'LoadBalancerInfo': { 'TargetGroupInfoList': [ { @@ -241,7 +239,7 @@ describe('CodeDeploy Server Deployment Group', () => { ), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'Ec2TagSet': { 'Ec2TagSetList': [ { @@ -276,7 +274,7 @@ describe('CodeDeploy Server Deployment Group', () => { ), }); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'OnPremisesTagSet': { 'OnPremisesTagSetList': [ { @@ -343,7 +341,7 @@ describe('CodeDeploy Server Deployment Group', () => { const deploymentGroup = new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup'); deploymentGroup.addAlarm(alarm); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'AlarmConfiguration': { 'Alarms': [ { @@ -362,7 +360,7 @@ describe('CodeDeploy Server Deployment Group', () => { new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup'); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'AutoRollbackConfiguration': { 'Enabled': true, 'Events': [ @@ -391,7 +389,7 @@ describe('CodeDeploy Server Deployment Group', () => { }); deploymentGroup.addAlarm(alarm); - expect(stack).toHaveResource('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'AutoRollbackConfiguration': { 'Enabled': true, 'Events': [ @@ -402,7 +400,8 @@ describe('CodeDeploy Server Deployment Group', () => { }); test('setting to roll back on alarms without providing any results in an exception', () => { - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app); new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', { autoRollback: { @@ -410,7 +409,7 @@ describe('CodeDeploy Server Deployment Group', () => { }, }); - expect(() => SynthUtils.toCloudFormation(stack)).toThrow(/deploymentInAlarm/); + expect(() => app.synth()).toThrow(/deploymentInAlarm/); }); test('can be used with an imported ALB Target Group as the load balancer', () => { @@ -424,7 +423,7 @@ describe('CodeDeploy Server Deployment Group', () => { ), }); - expect(stack).toHaveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { 'LoadBalancerInfo': { 'TargetGroupInfoList': [ { diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index a4953cd3fb355..146ec9cc2d741 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -79,7 +79,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 3c125c1bc0061..448aa7119eee6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -1,5 +1,4 @@ -import { arrayWith, ABSENT, ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; @@ -84,20 +83,20 @@ describe('default properties', () => { test('hash key only', () => { new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [{ AttributeName: 'hashKey', AttributeType: 'S' }], KeySchema: [{ AttributeName: 'hashKey', KeyType: 'HASH' }], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.RETAIN }, ResourcePart.CompleteDefinition); + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.RETAIN }); }); test('removalPolicy is DESTROY', () => { new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, removalPolicy: RemovalPolicy.DESTROY }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.DELETE }, ResourcePart.CompleteDefinition); + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.DELETE }); }); @@ -107,7 +106,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, { AttributeName: 'sortKey', AttributeType: 'N' }, @@ -126,7 +125,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -146,7 +145,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -167,7 +166,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -188,7 +187,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -209,7 +208,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -234,7 +233,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -261,7 +260,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -288,7 +287,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -313,8 +312,8 @@ describe('default properties', () => { // since the resource has not been used in a cross-environment manner, // so the name should not be filled - expect(stack).toHaveResourceLike('AWS::DynamoDB::Table', { - TableName: ABSENT, + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { + TableName: Match.absent(), }); }); }); @@ -338,7 +337,7 @@ testDeprecated('when specifying every property', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -377,7 +376,7 @@ test('when specifying sse with customer managed CMK', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -403,7 +402,7 @@ test('when specifying only encryptionKey', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -430,7 +429,7 @@ test('when specifying sse with customer managed CMK with encryptionKey provided }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -514,15 +513,15 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission }); const user = new iam.User(stack, 'MyUser'); table.grantReadWriteData(user); - expect(stack).toMatchTemplate({ - 'Resources': { - 'TableAKey07CC09EC': { - 'Type': 'AWS::KMS::Key', - 'Properties': { - 'KeyPolicy': { - 'Statement': [ + Template.fromStack(stack).templateMatches({ + Resources: { + TableAKey07CC09EC: { + Type: 'AWS::KMS::Key', + Properties: { + KeyPolicy: { + Statement: [ { - 'Action': [ + Action: [ 'kms:Create*', 'kms:Describe*', 'kms:Enable*', @@ -539,99 +538,99 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission 'kms:TagResource', 'kms:UntagResource', ], - 'Effect': 'Allow', - 'Principal': { - 'AWS': { + Effect: 'Allow', + Principal: { + AWS: { 'Fn::Join': [ '', [ 'arn:', { - 'Ref': 'AWS::Partition', + Ref: 'AWS::Partition', }, ':iam::', { - 'Ref': 'AWS::AccountId', + Ref: 'AWS::AccountId', }, ':root', ], ], }, }, - 'Resource': '*', + Resource: '*', }, { - 'Action': [ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Principal': { - 'AWS': { + Effect: 'Allow', + Principal: { + AWS: { 'Fn::GetAtt': [ 'MyUserDC45028B', 'Arn', ], }, }, - 'Resource': '*', + Resource: '*', }, ], - 'Version': '2012-10-17', + Version: '2012-10-17', }, - 'Description': 'Customer-managed key auto-created for encrypting DynamoDB table at Default/Table A', - 'EnableKeyRotation': true, + Description: 'Customer-managed key auto-created for encrypting DynamoDB table at Default/Table A', + EnableKeyRotation: true, }, - 'UpdateReplacePolicy': 'Retain', - 'DeletionPolicy': 'Retain', + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', }, - 'TableA3D7B5AFA': { - 'Type': 'AWS::DynamoDB::Table', - 'Properties': { - 'KeySchema': [ + TableA3D7B5AFA: { + Type: 'AWS::DynamoDB::Table', + Properties: { + KeySchema: [ { - 'AttributeName': 'hashKey', - 'KeyType': 'HASH', + AttributeName: 'hashKey', + KeyType: 'HASH', }, ], - 'AttributeDefinitions': [ + AttributeDefinitions: [ { - 'AttributeName': 'hashKey', - 'AttributeType': 'S', + AttributeName: 'hashKey', + AttributeType: 'S', }, ], - 'ProvisionedThroughput': { - 'ReadCapacityUnits': 5, - 'WriteCapacityUnits': 5, + ProvisionedThroughput: { + ReadCapacityUnits: 5, + WriteCapacityUnits: 5, }, - 'SSESpecification': { - 'KMSMasterKeyId': { + SSESpecification: { + KMSMasterKeyId: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', ], }, - 'SSEEnabled': true, - 'SSEType': 'KMS', + SSEEnabled: true, + SSEType: 'KMS', }, - 'TableName': 'MyTable', + TableName: 'MyTable', }, - 'UpdateReplacePolicy': 'Retain', - 'DeletionPolicy': 'Retain', + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', }, - 'MyUserDC45028B': { - 'Type': 'AWS::IAM::User', + MyUserDC45028B: { + Type: 'AWS::IAM::User', }, - 'MyUserDefaultPolicy7B897426': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ + MyUserDefaultPolicy7B897426: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyDocument: { + Statement: [ { - 'Action': [ + Action: [ 'dynamodb:BatchGetItem', 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', @@ -644,8 +643,8 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission 'dynamodb:UpdateItem', 'dynamodb:DeleteItem', ], - 'Effect': 'Allow', - 'Resource': [ + Effect: 'Allow', + Resource: [ { 'Fn::GetAtt': [ 'TableA3D7B5AFA', @@ -653,20 +652,20 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission ], }, { - 'Ref': 'AWS::NoValue', + Ref: 'AWS::NoValue', }, ], }, { - 'Action': [ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Resource': { + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', @@ -674,12 +673,12 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission }, }, ], - 'Version': '2012-10-17', + Version: '2012-10-17', }, - 'PolicyName': 'MyUserDefaultPolicy7B897426', - 'Users': [ + PolicyName: 'MyUserDefaultPolicy7B897426', + Users: [ { - 'Ref': 'MyUserDC45028B', + Ref: 'MyUserDC45028B', }, ], }, @@ -698,24 +697,24 @@ test('if an encryption key is included, encrypt/decrypt permissions are added to const user = new iam.User(stack, 'MyUser'); table.grantReadWriteData(user); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith({ - 'Action': [ + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Resource': { + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', ], }, - }), + }]), }, }); }); @@ -728,7 +727,7 @@ test('when specifying PAY_PER_REQUEST billing mode', () => { partitionKey: TABLE_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -891,7 +890,7 @@ test('when adding a global secondary index with hash key only', () => { writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -933,7 +932,7 @@ test('when adding a global secondary index with hash + range key', () => { writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -975,7 +974,7 @@ test('when adding a global secondary index with projection type KEYS_ONLY', () = projectionType: ProjectionType.KEYS_ONLY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1017,7 +1016,7 @@ test('when adding a global secondary index with projection type INCLUDE', () => writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1056,7 +1055,7 @@ test('when adding a global secondary index on a table with PAY_PER_REQUEST billi partitionKey: GSI_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1171,7 +1170,7 @@ test('when adding multiple global secondary indexes', () => { table.addGlobalSecondaryIndex(gsiGenerator.next().value); } - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1242,7 +1241,7 @@ test('when adding a global secondary index without specifying read and write cap partitionKey: GSI_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1277,7 +1276,7 @@ test('when adding a local secondary index with hash + range key', () => { sortKey: LSI_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1312,7 +1311,7 @@ test('when adding a local secondary index with projection type KEYS_ONLY', () => projectionType: ProjectionType.KEYS_ONLY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1349,7 +1348,7 @@ test('when adding a local secondary index with projection type INCLUDE', () => { nonKeyAttributes: [lsiNonKeyAttributeGenerator.next().value, lsiNonKeyAttributeGenerator.next().value], }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1426,13 +1425,13 @@ test('can enable Read AutoScaling', () => { table.autoScaleReadCapacity({ minCapacity: 50, maxCapacity: 500 }).scaleOnUtilization({ targetUtilizationPercent: 75 }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { MaxCapacity: 500, MinCapacity: 50, ScalableDimension: 'dynamodb:table:ReadCapacityUnits', ServiceNamespace: 'dynamodb', }); - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, @@ -1450,13 +1449,13 @@ test('can enable Write AutoScaling', () => { table.autoScaleWriteCapacity({ minCapacity: 50, maxCapacity: 500 }).scaleOnUtilization({ targetUtilizationPercent: 75 }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { MaxCapacity: 500, MinCapacity: 50, ScalableDimension: 'dynamodb:table:WriteCapacityUnits', ServiceNamespace: 'dynamodb', }); - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, @@ -1537,7 +1536,7 @@ test('can autoscale on a schedule', () => { }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { ScheduledActions: [ { ScalableTargetAction: { 'MaxCapacity': 10 }, @@ -1806,7 +1805,7 @@ describe('grants', () => { table.grant(user, 'dynamodb:action1', 'dynamodb:action2'); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1874,7 +1873,7 @@ describe('grants', () => { Table.grantListStreams(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1920,7 +1919,7 @@ describe('grants', () => { table.grantTableListStreams(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1966,7 +1965,7 @@ describe('grants', () => { table.grantStreamRead(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2007,7 +2006,7 @@ describe('grants', () => { table.grantReadData(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2066,7 +2065,7 @@ describe('grants', () => { table.grant(user, 'dynamodb:*'); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2159,7 +2158,7 @@ describe('import', () => { table.grantReadData(role); // it is possible to obtain a permission statement for a ref - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2201,7 +2200,7 @@ describe('import', () => { table.grantReadWriteData(role); // it is possible to obtain a permission statement for a ref - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2284,7 +2283,7 @@ describe('import', () => { expect(table.grantTableListStreams(role)).toBeDefined(); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2312,7 +2311,7 @@ describe('import', () => { expect(table.grantStreamRead(role)).toBeDefined(); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2347,7 +2346,7 @@ describe('import', () => { table.grantReadData(role); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2410,7 +2409,7 @@ describe('global', () => { }); // THEN - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2418,9 +2417,9 @@ describe('global', () => { Region: 'eu-west-2', }, Condition: 'TableStackRegionNotEqualseuwest2A03859E7', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2428,19 +2427,18 @@ describe('global', () => { Region: 'eu-central-1', }, Condition: 'TableStackRegionNotEqualseucentral199D46FC0', - }, ResourcePart.CompleteDefinition); + }); - expect(SynthUtils.toCloudFormation(stack).Conditions).toEqual({ - TableStackRegionNotEqualseuwest2A03859E7: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, - ], - }, - TableStackRegionNotEqualseucentral199D46FC0: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, - ], - }, + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseuwest2A03859E7', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, + ], + }); + + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseucentral199D46FC0', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, + ], }); }); @@ -2462,7 +2460,7 @@ describe('global', () => { }); // THEN - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2471,9 +2469,9 @@ describe('global', () => { SkipReplicationCompletedWait: 'true', }, Condition: 'TableStackRegionNotEqualseuwest2A03859E7', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2482,19 +2480,18 @@ describe('global', () => { SkipReplicationCompletedWait: 'true', }, Condition: 'TableStackRegionNotEqualseucentral199D46FC0', - }, ResourcePart.CompleteDefinition); + }); - expect(SynthUtils.toCloudFormation(stack).Conditions).toEqual({ - TableStackRegionNotEqualseuwest2A03859E7: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, - ], - }, - TableStackRegionNotEqualseucentral199D46FC0: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, - ], - }, + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseuwest2A03859E7', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, + ], + }); + + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseucentral199D46FC0', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, + ], }); }); @@ -2523,7 +2520,7 @@ describe('global', () => { table.grantReadData(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2677,7 +2674,7 @@ describe('global', () => { table.grantReadData(user); // THEN - expect(stack2).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack2).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2822,7 +2819,7 @@ describe('global', () => { table.grantTableListStreams(user); // THEN - expect(stack2).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack2).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2855,7 +2852,7 @@ describe('global', () => { // THEN expect(() => { - SynthUtils.synthesize(stack); + Template.fromStack(stack); }).toThrow(/A global Table that uses PROVISIONED as the billing mode needs auto-scaled write capacity/); }); @@ -2882,7 +2879,7 @@ describe('global', () => { // THEN expect(() => { - SynthUtils.synthesize(stack); + Template.fromStack(stack); }).toThrow(/A global Table that uses PROVISIONED as the billing mode needs auto-scaled write capacity with a policy/); }); @@ -2907,8 +2904,8 @@ describe('global', () => { maxCapacity: 10, }).scaleOnUtilization({ targetUtilizationPercent: 75 }); - expect(stack).toHaveResourceLike('AWS::DynamoDB::Table', { - BillingMode: ABSENT, // PROVISIONED is the default + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { + BillingMode: Match.absent(), // PROVISIONED is the default }); }); @@ -2971,7 +2968,8 @@ describe('global', () => { }); // THEN - expect(SynthUtils.toCloudFormation(stack).Conditions).toBeUndefined(); + const conditions = Template.fromStack(stack).findConditions('*'); + expect(Object.keys(conditions).length).toEqual(0); }); test('can configure timeout', () => { @@ -3014,7 +3012,7 @@ test('L1 inside L2 expects removalpolicy to have been set', () => { new FakeTableL2(stack, 'Table'); expect(() => { - SynthUtils.toCloudFormation(stack); + Template.fromStack(stack); }).toThrow(/is a stateful resource type/); }); @@ -3029,7 +3027,7 @@ function testGrant(expectedActions: string[], invocation: (user: iam.IPrincipal, // THEN const action = expectedActions.length > 1 ? expectedActions.map(a => `dynamodb:${a}`) : `dynamodb:${expectedActions[0]}`; - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index 685633b13c976..c05d56e6b3ddb 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -77,7 +77,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts index 3a5f28f648441..f642836ded6e2 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts @@ -1,6 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; -import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; @@ -17,7 +17,7 @@ describeDeprecated('awsauth', () => { new AwsAuth(stack, 'AwsAuth', { cluster }); // THEN - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify([{ apiVersion: 'v1', kind: 'ConfigMap', @@ -44,8 +44,8 @@ describeDeprecated('awsauth', () => { cluster.awsAuth.addAccount('5566776655'); // THEN - expect(stack).toCountResources(KubernetesResource.RESOURCE_TYPE, 1); - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).resourceCountIs(KubernetesResource.RESOURCE_TYPE, 1); + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -106,7 +106,7 @@ describeDeprecated('awsauth', () => { cluster.awsAuth.addUserMapping(user, { groups: ['group2'] }); // THEN - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts index b7f1666fb79b0..61188e858110a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; @@ -18,7 +18,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Cluster', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Cluster', { ResourcesVpcConfig: { SubnetIds: [ { Ref: 'VPCPublicSubnet1SubnetB4246D30' }, @@ -40,7 +40,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'cluster'); // THEN - expect(stack).toHaveResource('AWS::EC2::VPC'); + Template.fromStack(stack).resourceCountIs('AWS::EC2::VPC', 1); }); @@ -55,8 +55,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeDefined(); - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '2' }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm5.large' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '2' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm5.large' }); }); @@ -72,8 +72,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeDefined(); - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '10' }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm2.xlarge' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '10' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm2.xlarge' }); }); @@ -86,8 +86,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeUndefined(); - expect(stack).not.toHaveResource('AWS::AutoScaling::AutoScalingGroup'); - expect(stack).not.toHaveResource('AWS::AutoScaling::LaunchConfiguration'); + Template.fromStack(stack).resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 0); + Template.fromStack(stack).resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 0); }); }); @@ -100,7 +100,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource('AWS::EC2::Subnet', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { Tags: [ { Key: 'aws-cdk:subnet-name', Value: 'Private' }, { Key: 'aws-cdk:subnet-type', Value: 'Private' }, @@ -120,7 +120,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource('AWS::EC2::Subnet', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { MapPublicIpOnLaunch: true, Tags: [ { Key: 'aws-cdk:subnet-name', Value: 'Public' }, @@ -144,7 +144,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { Key: { 'Fn::Join': ['', ['kubernetes.io/cluster/', { Ref: 'ClusterEB0386A7' }]] }, @@ -182,7 +182,7 @@ describeDeprecated('cluster', () => { new cdk.CfnOutput(stack2, 'ClusterARN', { value: imported.clusterArn }); // THEN - expect(stack2).toMatchTemplate({ + Template.fromStack(stack2).templateMatches({ Outputs: { ClusterARN: { Value: { @@ -216,7 +216,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, mastersRole: role, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -247,11 +247,11 @@ describeDeprecated('cluster', () => { cluster.addResource('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: '[{"foo":123}]', }); - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: '[{"bar":123},{"boor":[1,2,3]}]', }); @@ -269,7 +269,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -302,7 +302,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); @@ -317,7 +317,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); @@ -524,7 +524,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(spotInterruptHandler()) }); + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(spotInterruptHandler()) }); }); @@ -540,7 +540,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts index 9606f892d5f1c..90b814a8bc0b3 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; @@ -15,7 +15,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Namespace: 'default' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Namespace: 'default' }); }); test('should have a lowercase default release name', () => { @@ -26,7 +26,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361' }); }); test('should trim the last 63 of the default release name', () => { @@ -37,7 +37,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChartNameWhichISMostProbablyLongerThenSixtyThreeCharacters', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'rtnamewhichismostprobablylongerthensixtythreecharactersb800614d' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'rtnamewhichismostprobablylongerthensixtythreecharactersb800614d' }); }); test('with values', () => { @@ -48,7 +48,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', values: { foo: 123 } }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Values: '{\"foo\":123}' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Values: '{\"foo\":123}' }); }); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts index 9a3de8f587f4c..2558945d61e94 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { testFixtureNoVpc } from './util'; @@ -69,7 +69,7 @@ describeDeprecated('manifest', () => { manifest, }); - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(manifest), }); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 21f0c02004903..e7080c9ab72a0 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -192,6 +192,7 @@ new lambda.NodejsFunction(this, 'my-handler', { footer: '/* comments */', // requires esbuild >= 0.9.0, defaults to none charset: lambda.Charset.UTF8, // do not escape non-ASCII characters, defaults to Charset.ASCII format: lambda.OutputFormat.ESM, // ECMAScript module output format, defaults to OutputFormat.CJS (OutputFormat.ESM requires Node.js 14.x) + mainFields: ['module', 'main'], // prefer ECMAScript versions of dependencies }, }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 62b5d56a0bc1d..d0cae32489818 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -211,6 +211,7 @@ export class Bundling implements cdk.BundlingOptions { ...this.props.banner ? [`--banner:js=${JSON.stringify(this.props.banner)}`] : [], ...this.props.footer ? [`--footer:js=${JSON.stringify(this.props.footer)}`] : [], ...this.props.charset ? [`--charset=${this.props.charset}`] : [], + ...this.props.mainFields ? [`--main-fields=${this.props.mainFields.join(',')}`] : [], ]; let depsCommand = ''; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index b74ac1df3b74a..e43dc6d41be1e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -270,6 +270,14 @@ export interface BundlingOptions { * @default OutputFormat.CJS */ readonly format?: OutputFormat; + + /** + * How to determine the entry point for modules. + * Try ['module', 'main'] to default to ES module versions. + * + * @default ['main', 'module'] + */ + readonly mainFields?: string[]; } /** diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 5941ce880a987..f9b8301eacacc 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -201,6 +201,7 @@ test('esbuild bundling with esbuild options', () => { footer: '/* comments */', charset: Charset.UTF8, forceDockerBundling: true, + mainFields: ['module', 'main'], define: { 'process.env.KEY': JSON.stringify('VALUE'), 'process.env.BOOL': 'true', @@ -224,7 +225,7 @@ test('esbuild bundling with esbuild options', () => { defineInstructions, '--log-level=silent --keep-names --tsconfig=/asset-input/lib/custom-tsconfig.ts', '--metafile=/asset-output/index.meta.json --banner:js="/* comments */" --footer:js="/* comments */"', - '--charset=utf8', + '--charset=utf8 --main-fields=module,main', ].join(' '), ], }), diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index ea25e2c01467b..a6c39016bf9cc 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -356,9 +356,7 @@ export interface IBucket extends IResource { } /** - * A reference to a bucket. The easiest way to instantiate is to call - * `bucket.export()`. Then, the consumer can use `Bucket.import(this, ref)` and - * get a `Bucket`. + * A reference to a bucket outside this stack */ export interface BucketAttributes { /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index fb5e52d2831b2..bff92d431cbab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -116,7 +116,7 @@ export interface PassProps { /** * Define a Pass in the state machine * - * A Pass state can be used to transform the current exeuction's state. + * A Pass state can be used to transform the current execution's state. */ export class Pass extends State implements INextable { public readonly endStates: INextable[]; diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index d2966e756b69d..ac87e25c29655 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,5 +1,6 @@ import { Writable } from 'stream'; import * as archiver from 'archiver'; +import * as AWS from 'aws-sdk'; import { flatMap } from '../../util'; import { ISDK } from '../aws-auth'; import { CfnEvaluationException, EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; @@ -232,7 +233,7 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { const operations: Promise[] = []; if (resource.code !== undefined) { - const updateFunctionCodePromise = lambda.updateFunctionCode({ + const updateFunctionCodeResponse = await lambda.updateFunctionCode({ FunctionName: this.lambdaFunctionResource.physicalName, S3Bucket: resource.code.s3Bucket, S3Key: resource.code.s3Key, @@ -240,17 +241,10 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { ZipFile: resource.code.functionCodeZip, }).promise(); + await this.waitForLambdasCodeUpdateToFinish(updateFunctionCodeResponse, lambda); + // only if the code changed is there any point in publishing a new Version if (this.lambdaFunctionResource.publishVersion) { - // we need to wait for the code update to be done before publishing a new Version - await updateFunctionCodePromise; - // if we don't wait for the Function to finish updating, - // we can get a "The operation cannot be performed at this time. An update is in progress for resource:" - // error when publishing a new Version - await lambda.waitFor('functionUpdated', { - FunctionName: this.lambdaFunctionResource.physicalName, - }).promise(); - const publishVersionPromise = lambda.publishVersion({ FunctionName: this.lambdaFunctionResource.physicalName, }).promise(); @@ -269,8 +263,6 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { } else { operations.push(publishVersionPromise); } - } else { - operations.push(updateFunctionCodePromise); } } @@ -304,6 +296,53 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { // run all of our updates in parallel return Promise.all(operations); } + + /** + * After a Lambda Function is updated, it cannot be updated again until the + * `State=Active` and the `LastUpdateStatus=Successful`. + * + * Depending on the configuration of the Lambda Function this could happen relatively quickly + * or very slowly. For example, Zip based functions _not_ in a VPC can take ~1 second whereas VPC + * or Container functions can take ~25 seconds (and 'idle' VPC functions can take minutes). + */ + private async waitForLambdasCodeUpdateToFinish(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { + const functionIsInVpcOrUsesDockerForCode = currentFunctionConfiguration.VpcConfig?.VpcId || + currentFunctionConfiguration.PackageType === 'Image'; + + // if the function is deployed in a VPC or if it is a container image function + // then the update will take much longer and we can wait longer between checks + // otherwise, the update will be quick, so a 1-second delay is fine + const delaySeconds = functionIsInVpcOrUsesDockerForCode ? 5 : 1; + + // configure a custom waiter to wait for the function update to complete + (lambda as any).api.waiters.updateFunctionCodeToFinish = { + name: 'UpdateFunctionCodeToFinish', + operation: 'getFunction', + // equates to 1 minute for zip function not in a VPC and + // 5 minutes for container functions or function in a VPC + maxAttempts: 60, + delay: delaySeconds, + acceptors: [ + { + matcher: 'path', + argument: "Configuration.LastUpdateStatus == 'Successful' && Configuration.State == 'Active'", + expected: true, + state: 'success', + }, + { + matcher: 'path', + argument: 'Configuration.LastUpdateStatus', + expected: 'Failed', + state: 'failure', + }, + ], + }; + + const updateFunctionCodeWaiter = new (AWS as any).ResourceWaiter(lambda, 'updateFunctionCodeToFinish'); + await updateFunctionCodeWaiter.wait({ + FunctionName: this.lambdaFunctionResource.physicalName, + }).promise(); + } } /** diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 00a6b2095a569..67b0117a8d7ae 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let mockGetEndpointSuffix: () => string; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockUpdateMachineDefinition = jest.fn(); mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index e47b127e68766..7b6aebb9a81ee 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -83,8 +83,29 @@ export class HotswapMockSdkProvider { }); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - this.mockSdkProvider.stubLambda(stubs); + public stubLambda( + stubs: SyncHandlerSubsetOf, + serviceStubs?: SyncHandlerSubsetOf, + additionalProperties: { [key: string]: any } = {}, + ): void { + this.mockSdkProvider.stubLambda(stubs, { + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, + ...serviceStubs, + ...additionalProperties, + }); + } + + public getLambdaApiWaiters(): { [key: string]: any } { + return (this.mockSdkProvider.sdk.lambda() as any).api.waiters; } public setUpdateProjectMock(mockUpdateProject: (input: codebuild.UpdateProjectInput) => codebuild.UpdateProjectOutput) { diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 9aef8b8778cf0..3ed53e5fd908b 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -5,16 +5,26 @@ let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => La let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + PackageType: 'Image', + }); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + makeRequest: mockMakeRequest, }); }); @@ -65,3 +75,53 @@ test('calls the updateLambdaCode() API when it receives only a code difference i ImageUri: 'new-image', }); }); + +test('calls the getFunction() API with a delay of 5', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'current-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'new-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 0b43563f233d9..d96b8530bce88 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -4,17 +4,25 @@ import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + makeRequest: mockMakeRequest, }); }); @@ -539,3 +547,177 @@ test('does not call the updateLambdaCode() API when a resource with type that is expect(deployStackResult).toBeUndefined(); expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); }); + +test('calls getFunction() after function code is updated with delay 1', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); + +test('calls getFunction() after function code is updated and VpcId is empty string with delay 1', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: '', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); + +test('calls getFunction() after function code is updated on a VPC function with delay 5', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: 'abc', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts index 13554cc655dcb..478fd80b538bf 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts index 4596bb2c96ac0..f937be7c5a5a0 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockPublishVersion = jest.fn(); mockUpdateAlias = jest.fn(); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/api/logs/logs-monitor.test.ts b/packages/aws-cdk/test/api/logs/logs-monitor.test.ts index becb3d97dfbf3..930e6427dbab7 100644 --- a/packages/aws-cdk/test/api/logs/logs-monitor.test.ts +++ b/packages/aws-cdk/test/api/logs/logs-monitor.test.ts @@ -17,20 +17,13 @@ afterAll(() => { monitor.deactivate(); }); -let TIMESTAMP: number; -let HUMAN_TIME: string; - -beforeAll(() => { - TIMESTAMP = new Date().getTime(); - HUMAN_TIME = new Date(TIMESTAMP).toLocaleTimeString(); -}); - test('continue to the next page if it exists', async () => { // GIVEN + const eventDate = new Date(T0 + 102 * 1000); sdk.stubCloudWatchLogs({ filterLogEvents() { return { - events: [event(102, 'message')], + events: [event(102, 'message', eventDate)], nextToken: 'some-token', }; }, @@ -50,22 +43,23 @@ test('continue to the next page if it exists', async () => { await sleep(1000); // THEN + const expectedLocaleTimeString = eventDate.toLocaleTimeString(); expect(stderrMock).toHaveBeenCalledTimes(2); expect(stderrMock.mock.calls[0][0]).toContain( - `[${blue('loggroup')}] ${yellow(HUMAN_TIME)} message`, + `[${blue('loggroup')}] ${yellow(expectedLocaleTimeString)} message`, ); expect(stderrMock.mock.calls[1][0]).toContain( - `[${blue('loggroup')}] ${yellow(new Date(T100).toLocaleTimeString())} >>> \`watch\` shows only the first 100 log messages - the rest have been truncated...`, + `[${blue('loggroup')}] ${yellow(expectedLocaleTimeString)} >>> \`watch\` shows only the first 100 log messages - the rest have been truncated...`, ); }); const T0 = 1597837230504; const T100 = T0 + 100 * 1000; -function event(nr: number, message: string): AWS.CloudWatchLogs.FilteredLogEvent { +function event(nr: number, message: string, timestamp: Date): AWS.CloudWatchLogs.FilteredLogEvent { return { eventId: `${nr}`, message, - timestamp: new Date(T0 * nr * 1000).getTime(), - ingestionTime: new Date(T0 * nr * 1000).getTime(), + timestamp: timestamp.getTime(), + ingestionTime: timestamp.getTime(), }; } diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index a1ed61a431366..bb8eaae251c47 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -102,8 +102,8 @@ export class MockSdkProvider extends SdkProvider { (this.sdk as any).ssm = jest.fn().mockReturnValue(partialAwsService(stubs)); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs)); + public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); } public stubStepFunctions(stubs: SyncHandlerSubsetOf) {