Skip to content

Commit

Permalink
chore(custom-resources): reorg and migreate to jest (aws#4847)
Browse files Browse the repository at this point in the history
* chore(custom-resources): reorg and migreate to jest

As a preparation for adding the custom resource provider framework (aws#4572), which includes multiple files and tests, reorg the file structure of this module such that all files
related to the `AwsCustomResource` construct will be under `lib/aws-custom-resource` and `test/aws-custom-resource`.

Also, migrate all unit tests from nodeunit to jest.

* Update .gitignore
  • Loading branch information
Elad Ben-Israel authored and mergify[bot] committed Nov 5, 2019
1 parent b840784 commit b43f3f4
Show file tree
Hide file tree
Showing 10 changed files with 622 additions and 654 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class AwsCustomResource extends cdk.Construct implements iam.IGrantable {
}

const provider = new lambda.SingletonFunction(this, 'Provider', {
code: lambda.Code.fromAsset(path.join(__dirname, 'aws-custom-resource-provider')),
code: lambda.Code.fromAsset(path.join(__dirname, 'runtime')),
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.handler',
uuid: '679f53fa-c002-430c-b0da-5b7982bd2287',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './aws-custom-resource';
13 changes: 12 additions & 1 deletion packages/@aws-cdk/custom-resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"cdk-build": {
"pre": [
"cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/sdk-api-metadata.json"
"cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/aws-custom-resource/sdk-api-metadata.json"
]
},
"keywords": [
Expand Down Expand Up @@ -96,6 +96,17 @@
"@aws-cdk/aws-sns": "1.15.0",
"@aws-cdk/core": "1.15.0"
},
"jest": {
"moduleFileExtensions": [
"js"
],
"coverageThreshold": {
"global": {
"branches": 70,
"statements": 80
}
}
},
"engines": {
"node": ">= 10.3.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
import SDK = require('aws-sdk');
import AWS = require('aws-sdk-mock');
import nock = require('nock');
import sinon = require('sinon');
import { AwsSdkCall } from '../../lib';
import { handler } from '../../lib/aws-custom-resource/runtime';

AWS.setSDK(require.resolve('aws-sdk'));

const eventCommon = {
ServiceToken: 'token',
ResponseURL: 'https://localhost',
StackId: 'stackId',
RequestId: 'requestId',
LogicalResourceId: 'logicalResourceId',
ResourceType: 'Custom::AWS',
};

function createRequest(bodyPredicate: (body: AWSLambda.CloudFormationCustomResourceResponse) => boolean) {
return nock('https://localhost')
.put('/', bodyPredicate)
.reply(200);
}

afterEach(() => {
AWS.restore();
nock.cleanAll();
});

test('create event with physical resource id path', async () => {
const listObjectsFake = sinon.fake.resolves({
Contents: [
{
Key: 'first-key',
ETag: 'first-key-etag'
},
{
Key: 'second-key',
ETag: 'second-key-etag',
}
]
} as SDK.S3.ListObjectsOutput);

AWS.mock('S3', 'listObjects', listObjectsFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'S3',
action: 'listObjects',
parameters: {
Bucket: 'my-bucket'
},
physicalResourceIdPath: 'Contents.1.ETag'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.PhysicalResourceId === 'second-key-etag' &&
body.Data!['Contents.0.Key'] === 'first-key'
);

await handler(event, {} as AWSLambda.Context);

sinon.assert.calledWith(listObjectsFake, {
Bucket: 'my-bucket'
});

expect(request.isDone()).toBeTruthy();
});

test('update event with physical resource id', async () => {
const publish = sinon.fake.resolves({});

AWS.mock('SNS', 'publish', publish);

const event: AWSLambda.CloudFormationCustomResourceUpdateEvent = {
...eventCommon,
RequestType: 'Update',
PhysicalResourceId: 'physicalResourceId',
OldResourceProperties: {},
ResourceProperties: {
ServiceToken: 'token',
Update: {
service: 'SNS',
action: 'publish',
parameters: {
Message: 'hello',
TopicArn: 'topicarn'
},
physicalResourceId: 'topicarn'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.PhysicalResourceId === 'topicarn'
);

await handler(event, {} as AWSLambda.Context);

expect(request.isDone()).toBeTruthy();
});

test('delete event', async () => {
const listObjectsFake = sinon.fake.resolves({});

AWS.mock('S3', 'listObjects', listObjectsFake);

const event: AWSLambda.CloudFormationCustomResourceDeleteEvent = {
...eventCommon,
RequestType: 'Delete',
PhysicalResourceId: 'physicalResourceId',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'S3',
action: 'listObjects',
parameters: {
Bucket: 'my-bucket'
},
physicalResourceIdPath: 'Contents.1.ETag'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.PhysicalResourceId === 'physicalResourceId' &&
Object.keys(body.Data!).length === 0
);

await handler(event, {} as AWSLambda.Context);

sinon.assert.notCalled(listObjectsFake);

expect(request.isDone()).toBeTruthy();
});

test('catch errors', async () => {
const error: NodeJS.ErrnoException = new Error();
error.code = 'NoSuchBucket';
const listObjectsFake = sinon.fake.rejects(error);

AWS.mock('S3', 'listObjects', listObjectsFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'S3',
action: 'listObjects',
parameters: {
Bucket: 'my-bucket'
},
physicalResourceId: 'physicalResourceId',
catchErrorPattern: 'NoSuchBucket'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.PhysicalResourceId === 'physicalResourceId' &&
Object.keys(body.Data!).length === 0
);

await handler(event, {} as AWSLambda.Context);

expect(request.isDone()).toBeTruthy();
});

test('decodes booleans', async () => {
const putItemFake = sinon.fake.resolves({});

AWS.mock('DynamoDB', 'putItem', putItemFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'DynamoDB',
action: 'putItem',
parameters: {
TableName: 'table',
Item: {
True: {
BOOL: 'TRUE:BOOLEAN'
},
TrueString: {
S: 'true'
},
False: {
BOOL: 'FALSE:BOOLEAN'
},
FalseString: {
S: 'false'
},
}
},
physicalResourceId: 'put-item'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS'
);

await handler(event, {} as AWSLambda.Context);

sinon.assert.calledWith(putItemFake, {
TableName: 'table',
Item: {
True: {
BOOL: true
},
TrueString: {
S: 'true'
},
False: {
BOOL: false
},
FalseString: {
S: 'false'
},
}
});

expect(request.isDone()).toBeTruthy();
});

test('restrict output path', async () => {
const listObjectsFake = sinon.fake.resolves({
Contents: [
{
Key: 'first-key',
ETag: 'first-key-etag'
},
{
Key: 'second-key',
ETag: 'second-key-etag',
}
]
} as SDK.S3.ListObjectsOutput);

AWS.mock('S3', 'listObjects', listObjectsFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'S3',
action: 'listObjects',
parameters: {
Bucket: 'my-bucket'
},
physicalResourceId: 'id',
outputPath: 'Contents.0'
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.PhysicalResourceId === 'id' &&
body.Data!['Contents.0.Key'] === 'first-key' &&
body.Data!['Contents.1.Key'] === undefined
);

await handler(event, {} as AWSLambda.Context);

expect(request.isDone()).toBeTruthy();
});

test('can specify apiVersion and region', async () => {
const publishFake = sinon.fake.resolves({});

AWS.mock('SNS', 'publish', publishFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'SNS',
action: 'publish',
parameters: {
Message: 'message',
TopicArn: 'topic'
},
apiVersion: '2010-03-31',
region: 'eu-west-1',
physicalResourceId: 'id',
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS' &&
body.Data!.apiVersion === '2010-03-31' &&
body.Data!.region === 'eu-west-1'
);

await handler(event, {} as AWSLambda.Context);

expect(request.isDone()).toBeTruthy();
});
Loading

0 comments on commit b43f3f4

Please sign in to comment.