mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
353 lines
11 KiB
JavaScript
353 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');
|
|
});
|
|
});
|