mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
397 lines
11 KiB
JavaScript
397 lines
11 KiB
JavaScript
'use strict'
|
|
|
|
const chai = require('chai')
|
|
const sinon = require('sinon')
|
|
const runServerless = require('../../../../../utils/run-serverless')
|
|
|
|
chai.use(require('chai-as-promised'))
|
|
chai.use(require('sinon-chai'))
|
|
const expect = require('chai').expect
|
|
|
|
describe('test/unit/lib/plugins/aws/remove/index.test.js', () => {
|
|
const deleteObjectsStub = sinon.stub().resolves()
|
|
const deleteStackStub = sinon.stub().resolves()
|
|
const describeStackEventsStub = sinon.stub().resolves({
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
],
|
|
})
|
|
const describeRepositoriesStub = sinon.stub()
|
|
const deleteRepositoryStub = sinon.stub().resolves()
|
|
const awsRequestStubMap = {
|
|
ECR: {
|
|
deleteRepository: deleteRepositoryStub,
|
|
describeRepositories: describeRepositoriesStub,
|
|
},
|
|
S3: {
|
|
deleteObjects: deleteObjectsStub,
|
|
listObjectsV2: { Contents: [{ Key: 'first' }, { Key: 'second' }] },
|
|
headBucket: {},
|
|
},
|
|
CloudFormation: {
|
|
describeStackEvents: describeStackEventsStub,
|
|
deleteStack: deleteStackStub,
|
|
describeStackResource: {
|
|
StackResourceDetail: { PhysicalResourceId: 'resource-id' },
|
|
},
|
|
},
|
|
STS: {
|
|
getCallerIdentity: {
|
|
ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' },
|
|
UserId: 'XXXXXXXXXXXXXXXXXXXXX',
|
|
Account: '999999999999',
|
|
Arn: 'arn:aws:iam::999999999999:user/test',
|
|
},
|
|
},
|
|
}
|
|
|
|
beforeEach(() => {
|
|
deleteObjectsStub.resetHistory()
|
|
deleteStackStub.resetHistory()
|
|
describeStackEventsStub.resetHistory()
|
|
describeRepositoriesStub.reset()
|
|
deleteRepositoryStub.resetHistory()
|
|
})
|
|
|
|
it('executes expected operations during removal when repository does not exist', async () => {
|
|
describeRepositoriesStub.throws({
|
|
providerError: { code: 'RepositoryNotFoundException' },
|
|
})
|
|
|
|
const { awsNaming } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap,
|
|
})
|
|
|
|
expect(deleteObjectsStub).to.be.calledWithExactly({
|
|
Bucket: 'resource-id',
|
|
Delete: {
|
|
Objects: [{ Key: 'first' }, { Key: 'second' }],
|
|
},
|
|
})
|
|
expect(deleteStackStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(deleteStackStub.calledAfter(deleteObjectsStub)).to.be.true
|
|
expect(describeStackEventsStub.calledAfter(deleteStackStub)).to.be.true
|
|
expect(deleteRepositoryStub).not.to.be.called
|
|
})
|
|
|
|
it('executes expected operations during removal when repository cannot be accessed due to denied access', async () => {
|
|
describeRepositoriesStub.throws({
|
|
providerError: { code: 'AccessDeniedException' },
|
|
})
|
|
|
|
const { awsNaming } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap,
|
|
})
|
|
|
|
expect(deleteObjectsStub).to.be.calledWithExactly({
|
|
Bucket: 'resource-id',
|
|
Delete: {
|
|
Objects: [{ Key: 'first' }, { Key: 'second' }],
|
|
},
|
|
})
|
|
expect(deleteStackStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(deleteStackStub.calledAfter(deleteObjectsStub)).to.be.true
|
|
expect(describeStackEventsStub.calledAfter(deleteStackStub)).to.be.true
|
|
expect(deleteRepositoryStub).not.to.be.called
|
|
})
|
|
|
|
it('executes expected operations related to files removal when S3 bucket has files', async () => {
|
|
await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
deleteObjects: deleteObjectsStub,
|
|
listObjectsV2: { Contents: [] },
|
|
headBucket: {},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(deleteObjectsStub).not.to.be.called
|
|
})
|
|
|
|
it('executes expected operations related to files removal when S3 bucket is empty', async () => {
|
|
await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap,
|
|
})
|
|
|
|
expect(deleteObjectsStub).to.be.calledWithExactly({
|
|
Bucket: 'resource-id',
|
|
Delete: {
|
|
Objects: [{ Key: 'first' }, { Key: 'second' }],
|
|
},
|
|
})
|
|
})
|
|
|
|
it('skips attempts to remove S3 objects if S3 bucket not found', async () => {
|
|
const { awsNaming } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
deleteObjects: deleteObjectsStub,
|
|
listObjectsV2: { Contents: [{ Key: 'first' }, { Key: 'second' }] },
|
|
headBucket: () => {
|
|
const err = new Error('err')
|
|
err.code = 'AWS_S3_HEAD_BUCKET_NOT_FOUND'
|
|
throw err
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(deleteObjectsStub).not.to.be.called
|
|
expect(deleteStackStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub.calledAfter(deleteStackStub)).to.be.true
|
|
})
|
|
|
|
it('skips attempts to remove S3 objects if S3 bucket resource missing from CloudFormation template', async () => {
|
|
const headBucketStub = sinon.stub()
|
|
const { awsNaming } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
...awsRequestStubMap.S3,
|
|
headBucket: headBucketStub,
|
|
},
|
|
CloudFormation: {
|
|
...awsRequestStubMap.CloudFormation,
|
|
describeStackResource: () => {
|
|
const err = new Error('does not exist for stack')
|
|
err.providerError = {
|
|
code: 'ValidationError',
|
|
}
|
|
throw err
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(headBucketStub).not.to.be.called
|
|
expect(deleteObjectsStub).not.to.be.called
|
|
expect(deleteStackStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub).to.be.calledWithExactly({
|
|
StackName: awsNaming.getStackName(),
|
|
})
|
|
expect(describeStackEventsStub.calledAfter(deleteStackStub)).to.be.true
|
|
})
|
|
|
|
it('removes ECR repository if it exists', async () => {
|
|
describeRepositoriesStub.resolves()
|
|
const { awsNaming } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'remove',
|
|
awsRequestStubMap,
|
|
})
|
|
|
|
expect(deleteRepositoryStub).to.be.calledWithExactly({
|
|
repositoryName: awsNaming.getEcrRepositoryName(),
|
|
registryId: '999999999999',
|
|
force: true,
|
|
})
|
|
})
|
|
|
|
it('should execute expected operations with versioning enabled if no object versions are present', async () => {
|
|
const listObjectVersionsStub = sinon.stub().resolves()
|
|
|
|
const { serverless } = await runServerless({
|
|
command: 'remove',
|
|
fixture: 'function',
|
|
configExt: {
|
|
provider: {
|
|
deploymentPrefix: 'serverless',
|
|
deploymentBucket: {
|
|
name: 'bucket',
|
|
versioning: true,
|
|
},
|
|
},
|
|
},
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
listObjectVersions: listObjectVersionsStub,
|
|
headBucket: {},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(listObjectVersionsStub).to.be.calledWithExactly({
|
|
Bucket: 'bucket',
|
|
Prefix: `serverless/${serverless.service.service}/dev`,
|
|
})
|
|
})
|
|
|
|
it('should execute expected operations with versioning enabled if object versions are present', async () => {
|
|
const listObjectVersionsStub = sinon.stub().resolves({
|
|
Versions: [
|
|
{ Key: 'object1', VersionId: null },
|
|
{ Key: 'object2', VersionId: 'v1' },
|
|
],
|
|
DeleteMarkers: [{ Key: 'object3', VersionId: 'v2' }],
|
|
})
|
|
|
|
const innerDeleteObjectsStub = sinon.stub().resolves({
|
|
Deleted: [
|
|
{ Key: 'object1', VersionId: null },
|
|
{ Key: 'object2', VersionId: 'v1' },
|
|
{ Key: 'object3', VersionId: 'v2' },
|
|
],
|
|
})
|
|
|
|
const { serverless } = await runServerless({
|
|
command: 'remove',
|
|
fixture: 'function',
|
|
configExt: {
|
|
provider: {
|
|
deploymentPrefix: 'serverless',
|
|
deploymentBucket: {
|
|
name: 'bucket',
|
|
versioning: true,
|
|
},
|
|
},
|
|
},
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
listObjectVersions: listObjectVersionsStub,
|
|
deleteObjects: innerDeleteObjectsStub,
|
|
headBucket: {},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(listObjectVersionsStub).to.be.calledWithExactly({
|
|
Bucket: 'bucket',
|
|
Prefix: `serverless/${serverless.service.service}/dev`,
|
|
})
|
|
|
|
expect(innerDeleteObjectsStub).to.be.calledWithExactly({
|
|
Bucket: 'bucket',
|
|
Delete: {
|
|
Objects: [
|
|
{ Key: 'object1', VersionId: null },
|
|
{ Key: 'object2', VersionId: 'v1' },
|
|
{ Key: 'object3', VersionId: 'v2' },
|
|
],
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should throw an error when deleteObjects operation was not successfull', async () => {
|
|
const innerDeleteObjectsStub = sinon.stub().resolves({
|
|
Deleted: [],
|
|
Errors: [
|
|
{
|
|
Code: 'InternalError',
|
|
},
|
|
],
|
|
})
|
|
|
|
await expect(
|
|
runServerless({
|
|
command: 'remove',
|
|
fixture: 'function',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
...awsRequestStubMap.S3,
|
|
deleteObjects: innerDeleteObjectsStub,
|
|
headBucket: {},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'CANNOT_DELETE_S3_OBJECTS_GENERIC',
|
|
)
|
|
})
|
|
|
|
it('should throw an error when deleteObjects operation was not successfull due to "AccessDenied"', async () => {
|
|
const innerDeleteObjectsStub = sinon.stub().resolves({
|
|
Deleted: [],
|
|
Errors: [
|
|
{
|
|
Code: 'AccessDenied',
|
|
},
|
|
],
|
|
})
|
|
|
|
await expect(
|
|
runServerless({
|
|
command: 'remove',
|
|
fixture: 'function',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
...awsRequestStubMap.S3,
|
|
deleteObjects: innerDeleteObjectsStub,
|
|
headBucket: {},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'CANNOT_DELETE_S3_OBJECTS_ACCESS_DENIED',
|
|
)
|
|
})
|
|
|
|
it('should throw an error when cannot list objects from the bucket', async () => {
|
|
await expect(
|
|
runServerless({
|
|
command: 'remove',
|
|
fixture: 'function',
|
|
awsRequestStubMap: {
|
|
...awsRequestStubMap,
|
|
S3: {
|
|
...awsRequestStubMap.S3,
|
|
listObjectsV2: () => {
|
|
const err = new Error('ff')
|
|
err.code = 'AWS_S3_LIST_OBJECTS_V2_ACCESS_DENIED'
|
|
throw err
|
|
},
|
|
headBucket: {},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'AWS_S3_LIST_OBJECTS_V2_ACCESS_DENIED',
|
|
)
|
|
})
|
|
})
|