Repository: HyperBrain/serverless-aws-alias (js)

Issue:
Support deployment bucket server side encryption
As Serverless now can be configured to use server side encryption for a pre existing deployment bucket, can we use the same for this plugin?

Pull request description:
Support S3 server side encryption
Closes #63 

Apply the SSE configuration from serverless.yml if present. The plugin now follows the same procedure as Serverless does.

Additionally added support for the cfnRole property.

Patch:
diff --git a/lib/updateAliasStack.js b/lib/updateAliasStack.js
index f9ead72..a14fabc 100644
--- a/lib/updateAliasStack.js
+++ b/lib/updateAliasStack.js
@@ -13,7 +13,7 @@ module.exports = {
 		this._serverless.cli.log('Creating alias stack...');
 
 		const stackName = `${this._provider.naming.getStackName()}-${this._alias}`;
-		let stackTags = { STAGE: this._options.stage };
+		let stackTags = { STAGE: this._options.stage, ALIAS: this._alias };
 		const templateUrl = `https://s3.amazonaws.com/${
 			this.bucketName
 			}/${
@@ -36,6 +36,10 @@ module.exports = {
 			Tags: _.map(_.keys(stackTags), key => ({ Key: key, Value: stackTags[key] })),
 		};
 
+		if (this.serverless.service.provider.cfnRole) {
+			params.RoleARN = this.serverless.service.provider.cfnRole;
+		}
+
 		return this._provider.request('CloudFormation',
 			'createStack',
 			params,
@@ -71,6 +75,10 @@ module.exports = {
 			Tags: _.map(_.keys(stackTags), key => ({ Key: key, Value: stackTags[key] })),
 		};
 
+		if (this.serverless.service.provider.cfnRole) {
+			params.RoleARN = this.serverless.service.provider.cfnRole;
+		}
+
 		// Policy must have at least one statement, otherwise no updates would be possible at all
 		if (this._serverless.service.provider.stackPolicy &&
 				this._serverless.service.provider.stackPolicy.length) {
diff --git a/lib/uploadAliasArtifacts.js b/lib/uploadAliasArtifacts.js
index c2e7cc3..c8e346c 100644
--- a/lib/uploadAliasArtifacts.js
+++ b/lib/uploadAliasArtifacts.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const BbPromise = require('bluebird');
+const _ = require('lodash');
 
 module.exports = {
 	uploadAliasCloudFormationFile() {
@@ -10,13 +11,18 @@ module.exports = {
 
 		const fileName = 'compiled-cloudformation-template-alias.json';
 
-		const params = {
+		let params = {
 			Bucket: this.bucketName,
 			Key: `${this.serverless.service.package.artifactDirectoryName}/${fileName}`,
 			Body: body,
 			ContentType: 'application/json',
 		};
 
+		const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject;
+		if (deploymentBucketObject) {
+			params = setServersideEncryptionOptions(params, deploymentBucketObject);
+		}
+
 		return this.provider.request('S3',
 			'putObject',
 			params,
@@ -34,3 +40,23 @@ module.exports = {
 	},
 
 };
+
+function setServersideEncryptionOptions(putParams, deploymentBucketOptions) {
+	const encryptionFields = {
+		'serverSideEncryption': 'ServerSideEncryption',
+		'sseCustomerAlgorithm': 'SSECustomerAlgorithm',
+		'sseCustomerKey': 'SSECustomerKey',
+		'sseCustomerKeyMD5': 'SSECustomerKeyMD5',
+		'sseKMSKeyId': 'SSEKMSKeyId',
+	};
+
+	const params = putParams;
+
+	_.forOwn(encryptionFields, (value, field) => {
+		if (deploymentBucketOptions[field]) {
+			params[value] = deploymentBucketOptions[field];
+		}
+	});
+
+	return params;
+}


Test patch:
diff --git a/test/updateAliasStack.test.js b/test/updateAliasStack.test.js
new file mode 100644
index 0000000..2424de5
--- /dev/null
+++ b/test/updateAliasStack.test.js
@@ -0,0 +1,339 @@
+'use strict';
+/**
+ * Unit tests for createAliasStack..
+ */
+
+const getInstalledPath = require('get-installed-path');
+const BbPromise = require('bluebird');
+const chai = require('chai');
+const sinon = require('sinon');
+const path = require('path');
+const AWSAlias = require('../index');
+
+const serverlessPath = getInstalledPath.sync('serverless', { local: true });
+const AwsProvider = require(`${serverlessPath}/lib/plugins/aws/provider/awsProvider`);
+const Serverless = require(`${serverlessPath}/lib/Serverless`);
+
+chai.use(require('chai-as-promised'));
+chai.use(require('sinon-chai'));
+const expect = chai.expect;
+
+describe('updateAliasStack', () => {
+	let serverless;
+	let options;
+	let awsAlias;
+	// Sinon and stubs for SLS CF access
+	let sandbox;
+	let providerRequestStub;
+	let monitorStackStub;
+	let logStub;
+
+	before(() => {
+		sandbox = sinon.sandbox.create();
+	});
+
+	beforeEach(() => {
+		serverless = new Serverless();
+		options = {
+			alias: 'myAlias',
+			stage: 'myStage',
+			region: 'us-east-1',
+		};
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
+		serverless.cli = new serverless.classes.CLI(serverless);
+		serverless.service.service = 'testService';
+		serverless.service.provider.compiledCloudFormationAliasTemplate = {};
+		serverless.service.package.artifactDirectoryName = 'myDirectory';
+		awsAlias = new AWSAlias(serverless, options);
+		providerRequestStub = sandbox.stub(awsAlias._provider, 'request');
+		monitorStackStub = sandbox.stub(awsAlias, 'monitorStack');
+		logStub = sandbox.stub(serverless.cli, 'log');
+		awsAlias.bucketName = 'myBucket';
+
+		logStub.returns();
+	});
+
+	afterEach(() => {
+		sandbox.restore();
+	});
+
+	describe('#createAliasFallback()', () => {
+		it('Should call CF with correct default parameters', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				OnFailure: 'DELETE',
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' }
+				],
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			const requestResult = {
+				status: 'ok'
+			};
+			providerRequestStub.returns(BbPromise.resolve(requestResult));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.createAliasFallback()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(monitorStackStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'createStack', expectedCFData, 'myStage', 'us-east-1'),
+				expect(monitorStackStub).to.have.been
+					.calledWithExactly('create', requestResult)
+			]));
+		});
+
+		it('should set stack tags', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				OnFailure: 'DELETE',
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' },
+					{ Key: 'tag1', Value: 'application'},
+					{ Key: 'tag2', Value: 'component' }
+				],
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			providerRequestStub.returns(BbPromise.resolve("done"));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.stackTags = {
+				tag1: 'application',
+				tag2: 'component'
+			};
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.createAliasFallback()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'createStack', expectedCFData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should use CFN role', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				OnFailure: 'DELETE',
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' },
+				],
+				RoleARN: 'myRole',
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			providerRequestStub.returns(BbPromise.resolve("done"));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.cfnRole = 'myRole';
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.createAliasFallback()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'createStack', expectedCFData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should reject with CF error', () => {
+			providerRequestStub.returns(BbPromise.reject(new Error('CF failed')));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			return expect(awsAlias.createAliasFallback()).to.be.rejectedWith('CF failed')
+			.then(() => expect(providerRequestStub).to.have.been.calledOnce);
+		});
+	});
+
+	describe('#updateAlias()', () => {
+		it('Should call CF with correct default parameters', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' }
+				],
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			const requestResult = {
+				status: 'ok'
+			};
+			providerRequestStub.returns(BbPromise.resolve(requestResult));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.updateAlias()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(monitorStackStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'updateStack', expectedCFData, 'myStage', 'us-east-1'),
+				expect(monitorStackStub).to.have.been
+					.calledWithExactly('update', requestResult)
+			]));
+		});
+
+		it('should set stack tags', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' },
+					{ Key: 'tag1', Value: 'application'},
+					{ Key: 'tag2', Value: 'component' }
+				],
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			providerRequestStub.returns(BbPromise.resolve("done"));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.stackTags = {
+				tag1: 'application',
+				tag2: 'component'
+			};
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.updateAlias()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'updateStack', expectedCFData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should use CFN role', () => {
+			const expectedCFData = {
+				StackName: 'testService-myStage-myAlias',
+				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
+				Parameters: [],
+				Tags: [
+					{ Key: 'STAGE', Value: 'myStage' },
+					{ Key: 'ALIAS', Value: 'myAlias' },
+				],
+				RoleARN: 'myRole',
+				TemplateURL: 'https://s3.amazonaws.com/myBucket/myDirectory/compiled-cloudformation-template-alias.json',
+			};
+			providerRequestStub.returns(BbPromise.resolve("done"));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			serverless.service.provider.cfnRole = 'myRole';
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.updateAlias()).to.be.fulfilled)
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('CloudFormation', 'updateStack', expectedCFData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should reject with CF error', () => {
+			providerRequestStub.returns(BbPromise.reject(new Error('CF failed')));
+			monitorStackStub.returns(BbPromise.resolve());
+
+			return expect(awsAlias.updateAlias()).to.be.rejectedWith('CF failed')
+			.then(() => expect(providerRequestStub).to.have.been.calledOnce);
+		});
+
+		it('should resolve in case no updates are performed', () => {
+			providerRequestStub.returns(BbPromise.resolve("done"));
+			monitorStackStub.returns(BbPromise.reject(new Error('No updates are to be performed.')));
+
+			return expect(awsAlias.updateAlias()).to.be.fulfilled
+			.then(() => expect(providerRequestStub).to.have.been.calledOnce);
+		});
+	});
+
+	describe('#updateAliasStack()', () => {
+		let writeAliasUpdateTemplateToDiskStub;
+		let createAliasFallbackStub;
+		let updateAliasStub;
+
+		beforeEach(() => {
+			writeAliasUpdateTemplateToDiskStub = sandbox.stub(awsAlias, 'writeAliasUpdateTemplateToDisk');
+			createAliasFallbackStub = sandbox.stub(awsAlias, 'createAliasFallback');
+			updateAliasStub = sandbox.stub(awsAlias, 'updateAlias');
+
+			writeAliasUpdateTemplateToDiskStub.returns(BbPromise.resolve());
+			createAliasFallbackStub.returns(BbPromise.resolve());
+			updateAliasStub.returns(BbPromise.resolve());
+		});
+
+		it('should write template and update stack', () => {
+			return expect(awsAlias.updateAliasStack()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(writeAliasUpdateTemplateToDiskStub).to.have.been.calledOnce,
+				expect(updateAliasStub).to.have.been.calledOnce,
+			]));
+		});
+
+		it('should create alias if createLater has been set', () => {
+			awsAlias._createLater = true;
+
+			return expect(awsAlias.updateAliasStack()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(writeAliasUpdateTemplateToDiskStub).to.have.been.calledOnce,
+				expect(createAliasFallbackStub).to.have.been.calledOnce,
+				expect(updateAliasStub).to.not.have.been.called,
+			]));
+		});
+
+		it('should resolve with noDeploy', () => {
+			awsAlias._options.noDeploy = true;
+
+			return expect(awsAlias.updateAliasStack()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(writeAliasUpdateTemplateToDiskStub).to.have.been.calledOnce,
+				expect(createAliasFallbackStub).to.not.have.been.called,
+				expect(updateAliasStub).to.not.have.been.called,
+			]));
+		});
+	});
+
+	describe('#writeAliasUpdateTemplateToDisk()', () => {
+		let writeFileSyncStub;
+
+		beforeEach(() => {
+			writeFileSyncStub = sandbox.stub(serverless.utils, 'writeFileSync');
+		});
+
+		it('should write the alias template', () => {
+			const expectedPath = path.join('path-to-service', '.serverless', 'cloudformation-template-update-alias-stack.json');
+			const template = {};
+			writeFileSyncStub.returns();
+
+			serverless.config.servicePath = 'path-to-service';
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = template;
+
+			return expect(awsAlias.writeAliasUpdateTemplateToDisk()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(writeFileSyncStub).to.have.been.calledOnce,
+				expect(writeFileSyncStub).to.have.been.calledWithExactly(expectedPath, template)
+			]));
+		});
+	});
+});
diff --git a/test/uploadAliasArtifacts.test.js b/test/uploadAliasArtifacts.test.js
new file mode 100644
index 0000000..987f9a8
--- /dev/null
+++ b/test/uploadAliasArtifacts.test.js
@@ -0,0 +1,177 @@
+'use strict';
+/**
+ * Unit tests for createAliasStack..
+ */
+
+const getInstalledPath = require('get-installed-path');
+const BbPromise = require('bluebird');
+const chai = require('chai');
+const sinon = require('sinon');
+const AWSAlias = require('../index');
+
+const serverlessPath = getInstalledPath.sync('serverless', { local: true });
+const AwsProvider = require(`${serverlessPath}/lib/plugins/aws/provider/awsProvider`);
+const Serverless = require(`${serverlessPath}/lib/Serverless`);
+
+chai.use(require('chai-as-promised'));
+chai.use(require('sinon-chai'));
+const expect = chai.expect;
+
+describe('uploadAliasArtifacts', () => {
+	let serverless;
+	let options;
+	let awsAlias;
+	// Sinon and stubs for SLS CF access
+	let sandbox;
+	let providerRequestStub;
+	let logStub;
+
+	before(() => {
+		sandbox = sinon.sandbox.create();
+	});
+
+	beforeEach(() => {
+		serverless = new Serverless();
+		options = {
+			alias: 'myAlias',
+			stage: 'myStage',
+			region: 'us-east-1',
+		};
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
+		serverless.cli = new serverless.classes.CLI(serverless);
+		serverless.service.service = 'testService';
+		serverless.service.provider.compiledCloudFormationAliasTemplate = {};
+		serverless.service.package.artifactDirectoryName = 'myDirectory';
+		awsAlias = new AWSAlias(serverless, options);
+		providerRequestStub = sandbox.stub(awsAlias._provider, 'request');
+		logStub = sandbox.stub(serverless.cli, 'log');
+		awsAlias.bucketName = 'myBucket';
+
+		logStub.returns();
+	});
+
+	afterEach(() => {
+		sandbox.restore();
+	});
+
+	describe('#uploadAliasCloudFormationFile()', () => {
+		it('Should call S3 putObject with correct default parameters', () => {
+			const expectedData = {
+				Bucket: 'myBucket',
+				Key: 'myDirectory/compiled-cloudformation-template-alias.json',
+				Body: '{}',
+				ContentType: 'application/json',
+			};
+			const requestResult = {
+				status: 'ok'
+			};
+			providerRequestStub.returns(BbPromise.resolve(requestResult));
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+
+			return expect(awsAlias.uploadAliasCloudFormationFile()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('S3', 'putObject', expectedData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should use SSE configuration and set all supported keys', () => {
+			const expectedData = {
+				Bucket: 'myBucket',
+				Key: 'myDirectory/compiled-cloudformation-template-alias.json',
+				Body: '{}',
+				ContentType: 'application/json',
+				ServerSideEncryption: true,
+				SSEKMSKeyId: 'keyID',
+				SSECustomerAlgorithm: 'AES',
+				SSECustomerKey: 'key',
+				SSECustomerKeyMD5: 'md5',
+			};
+			const requestResult = {
+				status: 'ok'
+			};
+			providerRequestStub.returns(BbPromise.resolve(requestResult));
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+			serverless.service.provider.deploymentBucketObject = {
+				serverSideEncryption: true,
+				sseKMSKeyId: 'keyID',
+				sseCustomerAlgorithm: 'AES',
+				sseCustomerKey: 'key',
+				sseCustomerKeyMD5: 'md5',
+			};
+
+			return expect(awsAlias.uploadAliasCloudFormationFile()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('S3', 'putObject', expectedData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should use SSE configuration and ignore all unsupported keys', () => {
+			const expectedData = {
+				Bucket: 'myBucket',
+				Key: 'myDirectory/compiled-cloudformation-template-alias.json',
+				Body: '{}',
+				ContentType: 'application/json',
+				ServerSideEncryption: true,
+				SSEKMSKeyId: 'keyID',
+			};
+			const requestResult = {
+				status: 'ok'
+			};
+			providerRequestStub.returns(BbPromise.resolve(requestResult));
+
+			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
+			serverless.service.provider.deploymentBucketObject = {
+				serverSideEncryption: true,
+				sseKMSKeyId: 'keyID',
+				sseCustomAlgorithm: 'AES',
+				unknown: 'key',
+				invalid: 'md5',
+			};
+
+			return expect(awsAlias.uploadAliasCloudFormationFile()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(providerRequestStub).to.have.been.calledOnce,
+				expect(providerRequestStub).to.have.been
+					.calledWithExactly('S3', 'putObject', expectedData, 'myStage', 'us-east-1'),
+			]));
+		});
+
+		it('should reject with S3 error', () => {
+			providerRequestStub.returns(BbPromise.reject(new Error('Failed')));
+
+			return expect(awsAlias.uploadAliasCloudFormationFile()).to.be.rejectedWith('Failed')
+			.then(() => expect(providerRequestStub).to.have.been.calledOnce);
+		});
+	});
+
+	describe('#uploadAliasArtifacts()', () => {
+		let uploadAliasCloudFormationFileStub;
+
+		beforeEach(() => {
+			uploadAliasCloudFormationFileStub = sandbox.stub(awsAlias, 'uploadAliasCloudFormationFile');
+			uploadAliasCloudFormationFileStub.returns(BbPromise.resolve());
+		});
+
+		it('should call uploadAliasCloudFormationFile', () => {
+			return expect(awsAlias.uploadAliasArtifacts()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(uploadAliasCloudFormationFileStub).to.have.been.calledOnce,
+			]));
+		});
+
+		it('should resolve with noDeploy', () => {
+			awsAlias._options.noDeploy = true;
+
+			return expect(awsAlias.uploadAliasArtifacts()).to.be.fulfilled
+			.then(() => BbPromise.all([
+				expect(uploadAliasCloudFormationFileStub).to.not.have.been.called,
+			]));
+		});
+	});
+});