From a6e3bffca29dbed194f328f6333bbff553a6e5f0 Mon Sep 17 00:00:00 2001 From: Frank Schmid Date: Tue, 9 May 2017 14:33:12 +0200 Subject: [PATCH 1/2] Added createAliasStack unit tests --- lib/createAliasStack.js | 2 +- test/createAliasStack.test.js | 274 ++++++++++++++++++++++++++++++++++ test/validate.test.js | 4 +- 3 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 test/createAliasStack.test.js diff --git a/lib/createAliasStack.js b/lib/createAliasStack.js index c7fc605..63e25eb 100644 --- a/lib/createAliasStack.js +++ b/lib/createAliasStack.js @@ -88,7 +88,7 @@ module.exports = { .then(this.createAlias); } - throw new this._serverless.classes.Error(e); + return BbPromise.reject(e); }); }, diff --git a/test/createAliasStack.test.js b/test/createAliasStack.test.js new file mode 100644 index 0000000..acf4b66 --- /dev/null +++ b/test/createAliasStack.test.js @@ -0,0 +1,274 @@ +'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('createAliasStack', () => { + let serverless; + let options; + let awsAlias; + // Sinon and stubs for SLS CF access + let sandbox; + let providerRequestStub; + let monitorStackStub; + + before(() => { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + serverless = new Serverless(); + options = { + alias: 'myAlias', + stage: 'myStage', + region: 'us-east-1', + }; + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.cli = new serverless.classes.CLI(serverless); + serverless.service.service = 'testService'; + serverless.service.provider.compiledCloudFormationAliasTemplate = {}; + awsAlias = new AWSAlias(serverless, options); + providerRequestStub = sandbox.stub(awsAlias._provider, 'request'); + monitorStackStub = sandbox.stub(awsAlias, 'monitorStack'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#createAlias()', () => { + it('Should call CF with correct default parameters', () => { + const expectedCFData = { + StackName: 'testService-dev-myAlias', + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], + OnFailure: 'DELETE', + Parameters: [], + Tags: [ + { Key: 'STAGE', Value: 'myStage' }, + { Key: 'ALIAS', Value: 'myAlias' } + ], + TemplateBody: '{}' + }; + const requestResult = { + status: 'ok' + }; + providerRequestStub.returns(BbPromise.resolve(requestResult)); + monitorStackStub.returns(BbPromise.resolve()); + + return expect(awsAlias.createAlias()).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-dev-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' } + ], + TemplateBody: '{}' + }; + providerRequestStub.returns(BbPromise.resolve("done")); + monitorStackStub.returns(BbPromise.resolve()); + + serverless.service.provider.stackTags = { + tag1: 'application', + tag2: 'component' + }; + + return expect(awsAlias.createAlias()).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.createAlias()).to.be.rejectedWith('CF failed') + .then(() => expect(providerRequestStub).to.have.been.calledOnce); + }); + }); + + describe('#createAliasStack()', () => { + let writeAliasTemplateToDiskStub; + let checkAliasStackStub; + + beforeEach(() => { + writeAliasTemplateToDiskStub = sandbox.stub(awsAlias, 'writeAliasTemplateToDisk'); + checkAliasStackStub = sandbox.stub(awsAlias, 'checkAliasStack'); + }); + + it('should fail with invalid service name', () => { + writeAliasTemplateToDiskStub.returns(BbPromise.resolve()); + checkAliasStackStub.returns(BbPromise.resolve()); + + serverless.service.service = 'testSer?vice'; + return expect(() => awsAlias.createAliasStack()).to.throw('is not valid'); + }); + + it('should fail with invalid alias name', () => { + writeAliasTemplateToDiskStub.returns(BbPromise.resolve()); + checkAliasStackStub.returns(BbPromise.resolve()); + + awsAlias._alias = 'ali!as'; + return expect(() => awsAlias.createAliasStack()).to.throw('is not valid'); + }); + + it('should fail with too long stack name', () => { + writeAliasTemplateToDiskStub.returns(BbPromise.resolve()); + checkAliasStackStub.returns(BbPromise.resolve()); + + awsAlias._alias = Array(513).join('x'); + return expect(() => awsAlias.createAliasStack()).to.throw('is not valid'); + }); + + it('should save template and create stack', () => { + writeAliasTemplateToDiskStub.returns(BbPromise.resolve()); + checkAliasStackStub.returns(BbPromise.resolve()); + + return expect(awsAlias.createAliasStack()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(writeAliasTemplateToDiskStub).to.have.been.calledOnce, + expect(checkAliasStackStub).to.have.been.calledOnce, + expect(writeAliasTemplateToDiskStub).to.have.been.calledBefore(checkAliasStackStub), + ])); + }); + }); + + describe('#checkAliasStack()', () => { + let createAliasStub; + + beforeEach(() => { + createAliasStub = sandbox.stub(awsAlias, 'createAlias'); + }); + + it('should do nothing with --noDeploy', () => { + providerRequestStub.returns(BbPromise.resolve()); + createAliasStub.returns(BbPromise.resolve()); + + awsAlias._options.noDeploy = true; + + return expect(awsAlias.checkAliasStack()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(providerRequestStub).to.not.have.been.called, + expect(createAliasStub).to.not.have.been.called, + ])); + }); + + it('Should call CF describeStackResources and resolve if stack exists', () => { + const expectedCFData = { StackName: 'testService-dev-myAlias' }; + providerRequestStub.returns(BbPromise.resolve()); + monitorStackStub.returns(BbPromise.resolve()); + + awsAlias._aliasStackName = 'testService-dev-myAlias'; + + return expect(awsAlias.checkAliasStack()).to.eventually.equal('alreadyCreated') + .then(() => BbPromise.all([ + expect(providerRequestStub).to.have.been.calledOnce, + expect(createAliasStub).to.not.have.been.called, + expect(providerRequestStub).to.have.been + .calledWithExactly('CloudFormation', 'describeStackResources', expectedCFData, 'myStage', 'us-east-1'), + ])); + }); + + it('Should create stack if it does not exist', () => { + providerRequestStub.returns(BbPromise.reject(new Error('stack does not exist'))); + monitorStackStub.returns(BbPromise.resolve()); + + awsAlias._aliasStackName = 'testService-dev-myAlias'; + awsAlias._createLater = false; + + return expect(awsAlias.checkAliasStack()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(createAliasStub).to.have.been.calledOnce, + expect(awsAlias._createLater).to.be.false + ])); + }); + + it('Should defer stack creation if a deployment bucket has been set', () => { + providerRequestStub.returns(BbPromise.reject(new Error('stack does not exist'))); + monitorStackStub.returns(BbPromise.resolve()); + + serverless.service.provider.deploymentBucket = 'myBucket'; + awsAlias._aliasStackName = 'testService-dev-myAlias'; + awsAlias._createLater = false; + + return expect(awsAlias.checkAliasStack()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(createAliasStub).to.not.have.been.called, + expect(awsAlias._createLater).to.be.true + ])); + }); + + it('should throw on unknown CF error', () => { + providerRequestStub.returns(BbPromise.reject(new Error('invalid CF operation'))); + createAliasStub.returns(BbPromise.resolve()); + + return expect(awsAlias.checkAliasStack()).to.be.rejectedWith('invalid CF operation'); + }); + }); + + describe('#writeAliasTemplateToDisk()', () => { + let writeFileSyncStub; + + beforeEach(() => { + writeFileSyncStub = sandbox.stub(serverless.utils, 'writeFileSync'); + }); + + it('should do nothing if a deployment bucket has been set', () => { + writeFileSyncStub.returns(); + + serverless.service.provider.deploymentBucket = 'myBucket'; + serverless.config.servicePath = 'path-to-service'; + + return expect(awsAlias.writeAliasTemplateToDisk()).to.be.fulfilled + .then(() => expect(writeFileSyncStub).to.not.have.been.called); + }); + + it('should write the alias template', () => { + const expectedPath = path.join('path-to-service', '.serverless', 'cloudformation-template-create-alias-stack.json'); + const template = { stack: 'mystacktemplate' }; + writeFileSyncStub.returns(); + + serverless.config.servicePath = 'path-to-service'; + serverless.service.provider.compiledCloudFormationAliasTemplate = template; + + return expect(awsAlias.writeAliasTemplateToDisk()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(writeFileSyncStub).to.have.been.calledOnce, + expect(writeFileSyncStub).to.have.been.calledWithExactly(expectedPath, template) + ])); + }); + }); +}); diff --git a/test/validate.test.js b/test/validate.test.js index 75bd695..7c00071 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -35,12 +35,12 @@ describe('#validate()', () => { return expect(awsAlias.validate()).to.be.rejectedWith('must be >= 1.12.0'); }); - it('should succeed Serverless version 1.12.0', () => { + it('should succeed with Serverless version 1.12.0', () => { serverless.version = '1.12.0'; return expect(awsAlias.validate()).to.eventually.be.fulfilled; }); - it('should succeed Serverless version 1.13.0', () => { + it('should succeed with Serverless version 1.13.0', () => { serverless.version = '1.13.0'; return expect(awsAlias.validate()).to.eventually.be.fulfilled; }); From 853017ce69c509a52ff70071030c748444fc7812 Mon Sep 17 00:00:00 2001 From: Frank Schmid Date: Tue, 9 May 2017 14:53:01 +0200 Subject: [PATCH 2/2] Stub log function in unit tests --- test/createAliasStack.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/createAliasStack.test.js b/test/createAliasStack.test.js index acf4b66..6a9b1c8 100644 --- a/test/createAliasStack.test.js +++ b/test/createAliasStack.test.js @@ -26,6 +26,7 @@ describe('createAliasStack', () => { let sandbox; let providerRequestStub; let monitorStackStub; + let logStub; before(() => { sandbox = sinon.sandbox.create(); @@ -45,6 +46,9 @@ describe('createAliasStack', () => { awsAlias = new AWSAlias(serverless, options); providerRequestStub = sandbox.stub(awsAlias._provider, 'request'); monitorStackStub = sandbox.stub(awsAlias, 'monitorStack'); + logStub = sandbox.stub(serverless.cli, 'log'); + + logStub.returns(); }); afterEach(() => {