/* eslint-disable @typescript-eslint/ban-ts-comment*/ import { ABSENT, ResourcePart, expect as expectCdk, countResources, haveResource, } from "@aws-cdk/assert"; import * as cdk from "@aws-cdk/core"; import * as lambda from "@aws-cdk/aws-lambda"; import * as dynamodb from "@aws-cdk/aws-dynamodb"; import { App, Stack, Function, Table, TableProps, TableIndexProps, TableFieldType, KinesisStream, } from "../src"; const lambdaDefaultPolicy = { Action: ["xray:PutTraceSegments", "xray:PutTelemetryRecords"], Effect: "Allow", Resource: "*", }; ///////////////////////////// // Test constructor ///////////////////////////// test("constructor: no props", async () => { const stack = new Stack(new App(), "stack"); expect(() => { // @ts-ignore Allow type casting new Table(stack, "Table", {} as TableProps); }).toThrow(/Missing "fields"/); }); test("constructor: dynamodbTable is construct", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { dynamodbTable: new dynamodb.Table(stack, "DDB", { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, }), }); expect(table.tableArn).toBeDefined(); expect(table.tableName).toBeDefined(); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { TableName: ABSENT, PointInTimeRecoverySpecification: ABSENT, KeySchema: [{ AttributeName: "id", KeyType: "HASH" }], KinesisStreamSpecification: ABSENT, }) ); }); test("constructor: kinesisStream", async () => { const stack = new Stack(new App(), "stack"); const stream = new KinesisStream(stack, "Stream"); new Table(stack, "Table", { ...baseTableProps, kinesisStream: stream, }); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { KinesisStreamSpecification: { StreamArn: { "Fn::GetAtt": ["Stream862536A4", "Arn"] }, }, }) ); }); ///////////////////////////// // Test fields and index props ///////////////////////////// test("constructor: fields-primaryIndex-defined", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, }); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { TableName: "dev-my-app-Table", BillingMode: dynamodb.BillingMode.PAY_PER_REQUEST, PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true }, KeySchema: [ { AttributeName: "noteId", KeyType: "HASH" }, { AttributeName: "userId", KeyType: "RANGE" }, ], }) ); }); test("constructor: fields-primaryIndex-undefined", async () => { const stack = new Stack(new App(), "stack"); expect(() => { // @ts-ignore Allow type casting new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, } as TableProps); }).toThrow(/Missing "primaryIndex" in "Table" Table/); }); test("constructor: fields-secondaryIndexes-defined", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, time: TableFieldType.NUMBER, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time" }, }, }); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { KeySchema: [ { AttributeName: "noteId", KeyType: "HASH" }, { AttributeName: "userId", KeyType: "RANGE" }, ], GlobalSecondaryIndexes: [ { IndexName: "userTimeIndex", KeySchema: [ { AttributeName: "userId", KeyType: "HASH" }, { AttributeName: "time", KeyType: "RANGE" }, ], Projection: { ProjectionType: "ALL", }, }, ], }) ); }); test("constructor: fields-undefined-primaryIndex-defined", async () => { const stack = new Stack(new App(), "stack"); expect(() => { // @ts-ignore Allow type casting new Table(stack, "Table", { primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, } as TableProps); }).toThrow( /Cannot configure the "primaryIndex" without setting the "fields"/ ); }); test("constructor: fields-undefined-secondaryIndexes-defined", async () => { const stack = new Stack(new App(), "stack"); expect(() => { // @ts-ignore Allow type casting new Table(stack, "Table", { secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time" }, }, } as TableProps); }).toThrow( /Cannot configure the "secondaryIndexes" without setting the "fields"/ ); }); test("constructor: fields-empty-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: {}, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, }); }).toThrow(/No fields defined/); }); test("constructor: fields-primaryIndex-missing-partitionKey-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, // @ts-ignore Allow type casting primaryIndex: {} as TableIndexProps, }); }).toThrow(/Missing "partitionKey" in primary index/); }); test("constructor: fields-dynamodbTable-construct-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, dynamodbTable: new dynamodb.Table(stack, "DDB", { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, }), }); }).toThrow( /Cannot configure the "fields" when "dynamodbTable" is a construct/ ); }); test("constructor: fields-dynamodbTable-props", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, dynamodbTable: { removalPolicy: cdk.RemovalPolicy.DESTROY, }, }); expectCdk(stack).to( haveResource( "AWS::DynamoDB::Table", { DeletionPolicy: "Delete", }, ResourcePart.CompleteDefinition ) ); }); test("constructor: fields-dynamodbTable-props-with-partitionKey-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, // @ts-ignore Allow type casting dynamodbTable: { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, } as dynamodb.TableProps, }); }).toThrow(/Cannot configure the "dynamodbTableProps.partitionKey"/); }); test("constructor: fields-dynamodbTable-props-with-sortKey-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, // @ts-ignore Allow type casting dynamodbTable: { sortKey: { name: "id", type: dynamodb.AttributeType.STRING }, } as dynamodb.TableProps, }); }).toThrow(/Cannot configure the "dynamodbTableProps.sortKey"/); }); test("secondaryIndexes-options", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time", indexProps: { projectionType: dynamodb.ProjectionType.KEYS_ONLY, }, }, }, }); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { TableName: "dev-my-app-Table", GlobalSecondaryIndexes: [ { IndexName: "userTimeIndex", KeySchema: [ { AttributeName: "userId", KeyType: "HASH" }, { AttributeName: "time", KeyType: "RANGE" }, ], Projection: { ProjectionType: "KEYS_ONLY", }, }, ], }) ); }); test("secondaryIndexes-indexProps-indexName-exists-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time", // @ts-ignore Allow type casting indexProps: { indexName: "index", projectionType: dynamodb.ProjectionType.KEYS_ONLY, } as dynamodb.GlobalSecondaryIndexProps, }, }, }); }).toThrow(/Cannot configure the "indexProps.indexName"/); }); test("secondaryIndexes-indexProps-partitionKey-exists-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time", // @ts-ignore Allow type casting indexProps: { partitionKey: { name: "userId", type: dynamodb.AttributeType.STRING, }, projectionType: dynamodb.ProjectionType.KEYS_ONLY, } as dynamodb.GlobalSecondaryIndexProps, }, }, }); }).toThrow(/Cannot configure the "indexProps.partitionKey"/); }); test("secondaryIndexes-indexProps-sortKey-exists-error", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, secondaryIndexes: { userTimeIndex: { partitionKey: "userId", sortKey: "time", // @ts-ignore Allow type casting indexProps: { sortKey: { name: "userId", type: dynamodb.AttributeType.STRING }, projectionType: dynamodb.ProjectionType.KEYS_ONLY, } as dynamodb.GlobalSecondaryIndexProps, }, }, }); }).toThrow(/Cannot configure the "indexProps.sortKey"/); }); ///////////////////////////// // Test consumers props ///////////////////////////// const baseTableProps = { fields: { noteId: TableFieldType.STRING, userId: TableFieldType.STRING, }, primaryIndex: { partitionKey: "noteId", sortKey: "userId" }, }; test("consumers: no-consumer", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 0)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 0)); }); test("consumers: empty-consumer", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, consumers: {} }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 0)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 0)); }); test("consumers: consumers is array (deprecated)", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { ...baseTableProps, stream: true, // @ts-ignore: Testing for deprecated consumers property consumers: ["test/lambda.handler"], }); }).toThrow(/The "consumers" property no longer takes an array/); }); test("consumers: Function string single", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 1)); expectCdk(stack).to( haveResource("AWS::Lambda::Function", { Handler: "lambda.handler", Timeout: 10, }) ); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); expectCdk(stack).to( haveResource("AWS::Lambda::EventSourceMapping", { FunctionName: { Ref: "TableConsumer0BC1C1271" }, BatchSize: 100, EventSourceArn: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, StartingPosition: "LATEST", }) ); }); test("consumers: Function string single with defaultFunctionProps", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, defaultFunctionProps: { timeout: 3, }, }); expectCdk(stack).to( haveResource("AWS::Lambda::Function", { Handler: "lambda.handler", Timeout: 3, }) ); }); test("consumers: Function strings multi", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", Consumer_1: "test/lambda.handler", }, }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 2)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 2)); }); test("consumers: Function construct", async () => { const stack = new Stack(new App(), "stack"); const f = new Function(stack, "Function", { handler: "test/lambda.handler" }); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: f, }, }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 1)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); }); test("consumers: Function construct with defaultFunctionProps", async () => { const stack = new Stack(new App(), "stack"); const f = new Function(stack, "Function", { handler: "test/lambda.handler" }); expect(() => { new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: f, }, defaultFunctionProps: { timeout: 3, }, }); }).toThrow(/The "defaultFunctionProps" cannot be applied/); }); test("consumers: Function props", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: { handler: "test/lambda.handler" }, }, }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 1)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); }); test("consumers: Function props with defaultFunctionProps", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: { handler: "test/lambda.handler", timeout: 5, }, }, defaultFunctionProps: { timeout: 3, }, }); expectCdk(stack).to( haveResource("AWS::Lambda::Function", { Handler: "lambda.handler", Timeout: 5, }) ); }); test("consumers: TableFunctionConsumerProps", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: { function: "test/lambda.handler", consumerProps: { startingPosition: lambda.StartingPosition.TRIM_HORIZON, }, }, }, }); expectCdk(stack).to( haveResource("AWS::Lambda::Function", { Handler: "lambda.handler", }) ); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); expectCdk(stack).to( haveResource("AWS::Lambda::EventSourceMapping", { StartingPosition: "TRIM_HORIZON", }) ); }); test("consumers: stream-true", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); expectCdk(stack).to(countResources("AWS::DynamoDB::Table", 1)); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { StreamSpecification: { StreamViewType: "NEW_AND_OLD_IMAGES" }, }) ); expectCdk(stack).to(countResources("AWS::Lambda::Function", 1)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); }); test("consumers: stream-enum", async () => { const stack = new Stack(new App(), "stack"); new Table(stack, "Table", { ...baseTableProps, stream: dynamodb.StreamViewType.NEW_IMAGE, consumers: { Consumer_0: "test/lambda.handler", }, }); expectCdk(stack).to(countResources("AWS::DynamoDB::Table", 1)); expectCdk(stack).to( haveResource("AWS::DynamoDB::Table", { StreamSpecification: { StreamViewType: "NEW_IMAGE" }, }) ); expectCdk(stack).to(countResources("AWS::Lambda::Function", 1)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 1)); }); test("consumers: stream-conflict-with-globalTables", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { ...baseTableProps, stream: dynamodb.StreamViewType.NEW_IMAGE, consumers: { Consumer_0: "test/lambda.handler", }, dynamodbTable: { replicationRegions: ["us-west-1"], }, }); }).toThrow( /`stream` must be set to `NEW_AND_OLD_IMAGES` when specifying `replicationRegions`/ ); }); test("consumers: error-stream-undefined", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { ...baseTableProps, consumers: { Consumer_0: "test/lambda.handler", }, }); }).toThrow( /Please enable the "stream" option to add consumers to the "Table" Table/ ); }); test("consumers: error-stream-false", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { ...baseTableProps, stream: false, consumers: { Consumer_0: "test/lambda.handler", }, }); }).toThrow( /Please enable the "stream" option to add consumers to the "Table" Table/ ); }); test("consumers: error-stream-redefined", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { ...baseTableProps, dynamodbTable: { stream: dynamodb.StreamViewType.NEW_IMAGE, }, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); }).toThrow( /Cannot configure the "dynamodbTableProps.stream" in the "Table" Table/ ); }); test("consumers: error-dynamodbTable-construct", async () => { const stack = new Stack(new App(), "stack"); expect(() => { new Table(stack, "Table", { dynamodbTable: new dynamodb.Table(stack, "DDB", { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, }), stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); }).toThrow( /Cannot configure the "stream" when "dynamodbTable" is a construct in the "Table" Table/ ); }); ///////////////////////////// // Test Methods ///////////////////////////// test("addConsumers", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); table.addConsumers(stack, { Consumer_1: "test/lambda.handler", }); expectCdk(stack).to(countResources("AWS::Lambda::Function", 2)); expectCdk(stack).to(countResources("AWS::Lambda::EventSourceMapping", 2)); }); test("addConsumers: consumers is array (deprecated)", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); expect(() => { // @ts-ignore: Testing for deprecated consumers property table.addConsumers(stack, ["test/lambda.handler"]); }).toThrow(/The "consumers" property no longer takes an array/); }); test("getFunction", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", Consumer_1: "test/lambda.handler", }, }); expect(table.getFunction("Consumer_0")).toBeDefined(); }); test("attachPermissions", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", Consumer_1: "test/lambda.handler", }, }); table.attachPermissions(["s3"]); expectCdk(stack).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, }, { Action: "s3:*", Effect: "Allow", Resource: "*" }, ], Version: "2012-10-17", }, PolicyName: "TableConsumer0ServiceRoleDefaultPolicy710701A2", }) ); expectCdk(stack).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, }, { Action: "s3:*", Effect: "Allow", Resource: "*" }, ], Version: "2012-10-17", }, PolicyName: "TableConsumer1ServiceRoleDefaultPolicyE7C50644", }) ); }); test("attachPermissionsToConsumer", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", Consumer_1: "test/lambda.handler", }, }); table.attachPermissionsToConsumer("Consumer_0", ["s3"]); expectCdk(stack).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, }, { Action: "s3:*", Effect: "Allow", Resource: "*" }, ], Version: "2012-10-17", }, PolicyName: "TableConsumer0ServiceRoleDefaultPolicy710701A2", }) ); expectCdk(stack).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, }, ], Version: "2012-10-17", }, PolicyName: "TableConsumer1ServiceRoleDefaultPolicyE7C50644", }) ); }); test("attachPermissionsToConsumer: consumer not found", async () => { const stack = new Stack(new App(), "stack"); const table = new Table(stack, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", Consumer_1: "test/lambda.handler", }, }); expect(() => { table.attachPermissionsToConsumer("Consumer_2", ["s3"]); }).toThrow(/The "Consumer_2" consumer was not found in the "Table" Table/); }); test("attachPermissions-after-addConsumers", async () => { const app = new App(); const stackA = new Stack(app, "stackA"); const stackB = new Stack(app, "stackB"); const table = new Table(stackA, "Table", { ...baseTableProps, stream: true, consumers: { Consumer_0: "test/lambda.handler", }, }); table.attachPermissions(["s3"]); table.addConsumers(stackB, { Consumer_1: "test/lambda.handler", }); expectCdk(stackA).to(countResources("AWS::Lambda::EventSourceMapping", 1)); expectCdk(stackA).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::GetAtt": ["Table710B521B", "StreamArn"] }, }, { Action: "s3:*", Effect: "Allow", Resource: "*" }, ], Version: "2012-10-17", }, PolicyName: "TableConsumer0ServiceRoleDefaultPolicy710701A2", }) ); expectCdk(stackB).to(countResources("AWS::Lambda::EventSourceMapping", 1)); expectCdk(stackB).to( haveResource("AWS::IAM::Policy", { PolicyDocument: { Statement: [ lambdaDefaultPolicy, { Action: "dynamodb:ListStreams", Effect: "Allow", Resource: "*" }, { Action: [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", ], Effect: "Allow", Resource: { "Fn::ImportValue": "dev-my-app-stackA:ExportsOutputFnGetAttTable710B521BStreamArn08276382", }, }, { Action: "s3:*", Effect: "Allow", Resource: "*" }, ], Version: "2012-10-17", }, PolicyName: "Consumer1ServiceRoleDefaultPolicy3118BC76", }) ); });