UNPKG

@aws-cdk/aws-certificatemanager

Version:

The CDK Construct Library for AWS::CertificateManager

1,168 lines (1,060 loc) 37.6 kB
'use strict'; const AWS = require('aws-sdk-mock'); const LambdaTester = require('lambda-tester').noVersionCheck(); const sinon = require('sinon'); const handler = require('..'); const nock = require('nock'); const ResponseURL = 'https://cloudwatch-response-mock.example.com/'; AWS.setSDK(require.resolve('aws-sdk')); describe('DNS Validated Certificate Handler', () => { let origLog = console.log; const testRequestId = 'f4ef1b10-c39a-44e3-99c0-fbf7e53c3943'; const testDomainName = 'test.example.com'; const testSubjectAlternativeName = 'foo.example.com'; const testHostedZoneId = '/hostedzone/Z3P5QSUBK4POTI'; const testCertificateArn = 'arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012'; const testRRName = '_3639ac514e785e898d2646601fa951d5.example.com'; const testRRValue = '_x2.acm-validations.aws'; const testAltRRName = '_3639ac514e785e898d2646601fa951d5.foo.example.com'; const testAltRRValue = '_x3.acm-validations.aws'; const testTags = { Tag1: 'Test1', Tag2: 'Test2' }; const testTagsValue = [{ Key: 'Tag1', Value: 'Test1' }, { Key: 'Tag2', Value: 'Test2' }]; const spySleep = sinon.spy(function (ms) { return Promise.resolve(); }); beforeEach(() => { handler.withDefaultResponseURL(ResponseURL); handler.withWaiter(function () { // Mock waiter is merely a self-fulfilling promise return { promise: () => { return new Promise((resolve) => { resolve(); }); } }; }); handler.withSleep(spySleep); console.log = function () { }; }); afterEach(() => { // Restore waiters and logger handler.resetWaiter(); handler.resetSleep(); handler.resetMaxAttempts(); AWS.restore(); nock.cleanAll(); console.log = origLog; spySleep.resetHistory(); }); test('Empty event payload fails', () => { const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED' && body.Reason === 'Unsupported request type undefined'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({}) .expectResolve(() => { expect(request.isDone()).toBe(true); }); }); test('Bogus operation fails', () => { const bogusType = 'bogus'; const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED' && body.Reason === 'Unsupported request type ' + bogusType; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: bogusType }) .expectResolve(() => { expect(request.isDone()).toBe(true); }); }); test('Create operation requests a certificate', () => { const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [{ ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }] } }); const addTagsToCertificateFake = sinon.fake.resolves({}); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', Tags: testTags } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [{ Action: 'UPSERT', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }] }, HostedZoneId: testHostedZoneId })); sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Create operation with `SubjectAlternativeNames` requests a certificate with validation records for all options', () => { const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [ { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }, { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testAltRRName, Type: 'CNAME', Value: testAltRRValue } } ] } }); const addTagsToCertificateFake = sinon.fake.resolves({ Certificate: testCertificateArn, Tags: testTags, }); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, SubjectAlternativeNames: [testSubjectAlternativeName], HostedZoneId: testHostedZoneId, Region: 'us-east-1', Tags: testTags, } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS', SubjectAlternativeNames: [testSubjectAlternativeName] })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [ { Action: 'UPSERT', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }, { Action: 'UPSERT', ResourceRecordSet: { Name: testAltRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testAltRRValue }] } } ] }, HostedZoneId: testHostedZoneId })); sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Create operation with `SubjectAlternativeNames` requests a certificate for all options without duplicates', () => { const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [ { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }, { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testAltRRName, Type: 'CNAME', Value: testAltRRValue } }, { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } } ] } }); const addTagsToCertificateFake = sinon.fake.resolves({ Certificate: testCertificateArn, Tags: testTags, }); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', Tags: testTags, } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [ { Action: 'UPSERT', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }, { Action: 'UPSERT', ResourceRecordSet: { Name: testAltRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testAltRRValue }] } } ] }, HostedZoneId: testHostedZoneId })); sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Create operation with `SubjectAlternativeNames` gracefully handles partial results from DescribeCertificate', () => { const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [ { ValidationStatus: 'PENDING_VALIDATION', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }, { ValidationStatus: 'PENDING_VALIDATION', }, ] } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [ { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }, { ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testAltRRName, Type: 'CNAME', Value: testAltRRValue } }, ] } }); const addTagsToCertificateFake = sinon.fake.resolves({ Certificate: testCertificateArn, Tags: testTags, }); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', Tags: testTags, } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [ { Action: 'UPSERT', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }, { Action: 'UPSERT', ResourceRecordSet: { Name: testAltRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testAltRRValue }] } } ] }, HostedZoneId: testHostedZoneId })); sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Create operation fails after more than 60s if certificate has no DomainValidationOptions', () => { handler.withRandom(() => 0); const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED' && body.Reason.startsWith('Response from describeCertificate did not contain DomainValidationOptions'); }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); expect(request.isDone()).toBe(true); const totalSleep = spySleep.getCalls().map(call => call.args[0]).reduce((p, n) => p + n, 0); expect(totalSleep).toBeGreaterThan(60 * 1000); }); }); test('Create operation fails within 360s and 10 attempts if certificate has no DomainValidationOptions', () => { handler.withRandom(() => 1); const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn } }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED' && body.Reason.startsWith('Response from describeCertificate did not contain DomainValidationOptions'); }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); expect(request.isDone()).toBe(true); expect(spySleep.callCount).toBe(10); const totalSleep = spySleep.getCalls().map(call => call.args[0]).reduce((p, n) => p + n, 0); expect(totalSleep).toBeLessThan(360 * 1000); }); }); test('Create operation with a maximum of 1 attempts describes the certificate once', () => { handler.withMaxAttempts(1); const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [{ ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }] } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const addTagsToCertificateFake = sinon.fake.resolves({ Certificate: testCertificateArn, Tags: testTags, }); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', Tags: testTags, } }) .expectResolve(() => { sinon.assert.calledOnce(describeCertificateFake); sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn, })); sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Create operation succeeds with no tags passed', () => { const requestCertificateFake = sinon.fake.resolves({ CertificateArn: testCertificateArn, }); const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [{ ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }] } }); const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); const addTagsToCertificateFake = sinon.fake.resolves({ Certificate: testCertificateArn, }); AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Create', RequestId: testRequestId, ResourceProperties: { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1' } }) .expectResolve(() => { sinon.assert.calledWith(requestCertificateFake, sinon.match({ DomainName: testDomainName, ValidationMethod: 'DNS' })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [{ Action: 'UPSERT', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }] }, HostedZoneId: testHostedZoneId })); sinon.assert.neverCalledWith(addTagsToCertificateFake, sinon.match({ "CertificateArn": testCertificateArn, "Tags": testTagsValue, })); expect(request.isDone()).toBe(true); }); }); test('Delete operation deletes the certificate', () => { const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const deleteCertificateFake = sinon.fake.resolves({}); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); test('Delete operation is idempotent', () => { const error = new Error(); error.name = 'ResourceNotFoundException'; const describeCertificateFake = sinon.fake.rejects(error); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const deleteCertificateFake = sinon.fake.rejects(error); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.neverCalledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); test('Delete operation succeeds if certificate becomes not-in-use', () => { const usedByArn = 'arn:aws:cloudfront::123456789012:distribution/d111111abcdef8'; const describeCertificateFake = sinon.stub(); describeCertificateFake.onFirstCall().resolves({ Certificate: { CertificateArn: testCertificateArn, InUseBy: [usedByArn], } }); describeCertificateFake.resolves({ Certificate: { CertificateArn: testCertificateArn, InUseBy: [], } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const deleteCertificateFake = sinon.fake.resolves({}); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); test('Delete operation fails within 360s and 10 attempts if certificate is in-use', () => { const usedByArn = 'arn:aws:cloudfront::123456789012:distribution/d111111abcdef8'; const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, InUseBy: [usedByArn], } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const error = new Error(); error.name = 'ResourceInUseException'; const deleteCertificateFake = sinon.fake.rejects(error); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.neverCalledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); const totalSleep = spySleep.getCalls().map(call => call.args[0]).reduce((p, n) => p + n, 0); expect(totalSleep).toBeLessThan(360 * 1000); expect(spySleep.callCount).toBe(10); expect(request.isDone()).toBe(true); }); }); test('Delete operation fails if some other error is encountered during describe', () => { const error = new Error(); error.name = 'SomeOtherException'; const describeCertificateFake = sinon.fake.rejects(error); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const deleteCertificateFake = sinon.fake.resolves({}); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.neverCalledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); test('Delete operation fails if some other error is encountered during delete', () => { const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const error = new Error(); error.name = 'SomeOtherException'; const deleteCertificateFake = sinon.fake.rejects(error); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); test('Delete operation with a maximum of 1 attempts describes the certificate once', () => { handler.withMaxAttempts(1); const describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); const deleteCertificateFake = sinon.fake.resolves({}); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); expect(request.isDone()).toBe(true); }); }); describe('Delete option record cleanup', () => { let describeCertificateFake; let deleteCertificateFake; let changeResourceRecordSetsFake; beforeEach(() => { deleteCertificateFake = sinon.fake.resolves({}); AWS.mock('ACM', 'deleteCertificate', deleteCertificateFake); changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, DomainValidationOptions: [{ ValidationStatus: 'SUCCESS', ResourceRecord: { Name: testRRName, Type: 'CNAME', Value: testRRValue } }] } }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); }); test('ignores records if CleanupRecords is not set', () => { const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', HostedZoneId: testHostedZoneId, } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.notCalled(changeResourceRecordSetsFake); expect(request.isDone()).toBe(true); }); }); test('ignores records if CleanupRecords is not set to "true"', () => { const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', HostedZoneId: testHostedZoneId, CleanupRecords: 'TRUE', // Not "true" } }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.notCalled(changeResourceRecordSetsFake); expect(request.isDone()).toBe(true); }); }); test('deletes records if CleanupRecords is set to true and records are present', () => { const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; }).reply(200); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', HostedZoneId: testHostedZoneId, CleanupRecords: 'true', }, }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(deleteCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ ChangeBatch: { Changes: [{ Action: 'DELETE', ResourceRecordSet: { Name: testRRName, Type: 'CNAME', TTL: 60, ResourceRecords: [{ Value: testRRValue }] } }] }, HostedZoneId: testHostedZoneId })); expect(request.isDone()).toBe(true); }); }); test('fails if CleanupRecords is set to true and records are not present', () => { describeCertificateFake = sinon.fake.resolves({ Certificate: { CertificateArn: testCertificateArn, } }); AWS.remock('ACM', 'describeCertificate', describeCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'FAILED' && body.Reason.startsWith('Response from describeCertificate did not contain DomainValidationOptions'); }).reply(200); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); return LambdaTester(handler.certificateRequestHandler) .event({ RequestType: 'Delete', RequestId: testRequestId, PhysicalResourceId: testCertificateArn, ResourceProperties: { Region: 'us-east-1', HostedZoneId: testHostedZoneId, CleanupRecords: 'true', }, }) .expectResolve(() => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn })); sinon.assert.notCalled(deleteCertificateFake); sinon.assert.notCalled(changeResourceRecordSetsFake); expect(request.isDone()).toBe(true); }); }); }); });