diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 3fb829212..af4fee885 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -23,6 +23,7 @@ "./aws/metrics/awsMetrics.js", "./aws/remove/index.js", "./aws/rollback/index.js", + "./aws/rollbackFunction/index.js", "./aws/package/compile/functions/index.js", "./aws/package/compile/events/schedule/index.js", "./aws/package/compile/events/s3/index.js", diff --git a/lib/plugins/aws/deployList/index.js b/lib/plugins/aws/deployList/index.js index 210744da6..2ecb925ae 100644 --- a/lib/plugins/aws/deployList/index.js +++ b/lib/plugins/aws/deployList/index.js @@ -19,10 +19,15 @@ class AwsDeployList { this.hooks = { 'before:deploy:list:log': () => BbPromise.bind(this) - .then(this.validate), + .then(this.validate), + 'before:deploy:list:functions:log': () => BbPromise.bind(this) + .then(this.validate), + 'deploy:list:log': () => BbPromise.bind(this) .then(this.setBucketName) .then(this.listDeployments), + 'deploy:list:functions:log': () => BbPromise.bind(this) + .then(this.listFunctions), }; } @@ -61,6 +66,77 @@ class AwsDeployList { return BbPromise.resolve(); }); } + + // list all functions and their versions + listFunctions() { + return BbPromise.resolve().bind(this) + .then(this.getFunctions) + .then(this.getFunctionVersions) + .then(this.displayFunctions); + } + + getFunctions() { + const params = { + MaxItems: 200, + }; + + return this.provider.request('Lambda', + 'listFunctions', + params, + this.options.stage, + this.options.region) + .then((result) => { + const allFuncs = result.Functions; + + const serviceName = `${this.serverless.service.service}-${this.options.stage}`; + const regex = new RegExp(serviceName); + const serviceFuncs = allFuncs.filter((func) => func.FunctionName.match(regex)); + + return BbPromise.resolve(serviceFuncs); + }); + } + + getFunctionVersions(funcs) { + const requestPromises = []; + + funcs.forEach((func) => { + const params = { + FunctionName: func.FunctionName, + MaxItems: 5, + }; + + const request = this.provider.request('Lambda', + 'listVersionsByFunction', + params, + this.options.stage, + this.options.region); + + requestPromises.push(request); + }); + + return BbPromise.all(requestPromises); + } + + displayFunctions(funcs) { + this.serverless.cli.log('Listing functions and their versions:'); + this.serverless.cli.log('-------------'); + + funcs.forEach((func) => { + let message = ''; + + let name = func.Versions[0].FunctionName; + name = name.replace(`${this.serverless.service.service}-`, ''); + name = name.replace(`${this.options.stage}-`, ''); + + message += `${name}: `; + const versions = func.Versions.map((funcEntry) => funcEntry.Version); + + message += versions.join(', '); + this.serverless.cli.log(message); + }); + + return BbPromise.resolve(); + } } module.exports = AwsDeployList; diff --git a/lib/plugins/aws/deployList/index.test.js b/lib/plugins/aws/deployList/index.test.js index 6cab27eb3..4b80bb32c 100644 --- a/lib/plugins/aws/deployList/index.test.js +++ b/lib/plugins/aws/deployList/index.test.js @@ -8,7 +8,7 @@ const Serverless = require('../../../Serverless'); describe('AwsDeployList', () => { let serverless; - let awsDeploy; + let awsDeployList; let s3Key; beforeEach(() => { @@ -20,9 +20,9 @@ describe('AwsDeployList', () => { region: 'us-east-1', }; s3Key = `serverless/${serverless.service.service}/${options.stage}`; - awsDeploy = new AwsDeployList(serverless, options); - awsDeploy.bucketName = 'deployment-bucket'; - awsDeploy.serverless.cli = { + awsDeployList = new AwsDeployList(serverless, options); + awsDeployList.bucketName = 'deployment-bucket'; + awsDeployList.serverless.cli = { log: sinon.spy(), }; }); @@ -33,29 +33,29 @@ describe('AwsDeployList', () => { Contents: [], }; const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(s3Response); + .stub(awsDeployList.provider, 'request').resolves(s3Response); - return awsDeploy.listDeployments().then(() => { + return awsDeployList.listDeployments().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); expect(listObjectsStub.calledWithExactly( 'S3', 'listObjectsV2', { - Bucket: awsDeploy.bucketName, + Bucket: awsDeployList.bucketName, Prefix: `${s3Key}`, }, - awsDeploy.options.stage, - awsDeploy.options.region + awsDeployList.options.stage, + awsDeployList.options.region )).to.be.equal(true); const infoText = 'Couldn\'t find any existing deployments.'; - expect(awsDeploy.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); + expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); const verifyText = 'Please verify that stage and region are correct.'; - expect(awsDeploy.serverless.cli.log.calledWithExactly(verifyText)).to.be.equal(true); - awsDeploy.provider.request.restore(); + expect(awsDeployList.serverless.cli.log.calledWithExactly(verifyText)).to.be.equal(true); + awsDeployList.provider.request.restore(); }); }); - it('should all available deployments', () => { + it('should display all available deployments', () => { const s3Response = { Contents: [ { Key: `${s3Key}/113304333331-2016-08-18T13:40:06/artifact.zip` }, @@ -66,31 +66,170 @@ describe('AwsDeployList', () => { }; const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(s3Response); + .stub(awsDeployList.provider, 'request').resolves(s3Response); - return awsDeploy.listDeployments().then(() => { + return awsDeployList.listDeployments().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); expect(listObjectsStub.calledWithExactly( 'S3', 'listObjectsV2', { - Bucket: awsDeploy.bucketName, + Bucket: awsDeployList.bucketName, Prefix: `${s3Key}`, }, - awsDeploy.options.stage, - awsDeploy.options.region + awsDeployList.options.stage, + awsDeployList.options.region )).to.be.equal(true); const infoText = 'Listing deployments:'; - expect(awsDeploy.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); + expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); const timestampOne = 'Timestamp: 113304333331'; const datetimeOne = 'Datetime: 2016-08-18T13:40:06'; - expect(awsDeploy.serverless.cli.log.calledWithExactly(timestampOne)).to.be.equal(true); - expect(awsDeploy.serverless.cli.log.calledWithExactly(datetimeOne)).to.be.equal(true); + expect(awsDeployList.serverless.cli.log.calledWithExactly(timestampOne)).to.be.equal(true); + expect(awsDeployList.serverless.cli.log.calledWithExactly(datetimeOne)).to.be.equal(true); const timestampTow = 'Timestamp: 903940390431'; const datetimeTwo = 'Datetime: 2016-08-18T23:42:08'; - expect(awsDeploy.serverless.cli.log.calledWithExactly(timestampTow)).to.be.equal(true); - expect(awsDeploy.serverless.cli.log.calledWithExactly(datetimeTwo)).to.be.equal(true); - awsDeploy.provider.request.restore(); + expect(awsDeployList.serverless.cli.log.calledWithExactly(timestampTow)).to.be.equal(true); + expect(awsDeployList.serverless.cli.log.calledWithExactly(datetimeTwo)).to.be.equal(true); + awsDeployList.provider.request.restore(); + }); + }); + }); + + describe('#listFunctions()', () => { + let getFunctionsStub; + let getFunctionVersionsStub; + let displayFunctionsStub; + + beforeEach(() => { + getFunctionsStub = sinon.stub(awsDeployList, 'getFunctions').resolves(); + getFunctionVersionsStub = sinon.stub(awsDeployList, 'getFunctionVersions').resolves(); + displayFunctionsStub = sinon.stub(awsDeployList, 'displayFunctions').resolves(); + }); + + afterEach(() => { + awsDeployList.getFunctions.restore(); + awsDeployList.getFunctionVersions.restore(); + awsDeployList.displayFunctions.restore(); + }); + + it('should run promise chain in order', () => awsDeployList + .listFunctions().then(() => { + expect(getFunctionsStub.calledOnce).to.equal(true); + expect(getFunctionVersionsStub.calledAfter(getFunctionsStub)).to.equal(true); + expect(displayFunctionsStub.calledAfter(getFunctionVersionsStub)).to.equal(true); + }) + ); + }); + + describe('#getFunctions()', () => { + let listFunctionsStub; + + beforeEach(() => { + listFunctionsStub = sinon + .stub(awsDeployList.provider, 'request') + .resolves({ + Functions: [ + { FunctionName: 'listDeployments-dev-func1' }, + { FunctionName: 'listDeployments-dev-func2' }, + { FunctionName: 'unrelatedService-dev-func3' }, + { FunctionName: 'unrelatedService-dev-func4' }, + ], + }); + }); + + afterEach(() => { + awsDeployList.provider.request.restore(); + }); + + it('should get all functions and filter out the service related ones', () => { + const expectedResult = [ + { FunctionName: 'listDeployments-dev-func1' }, + { FunctionName: 'listDeployments-dev-func2' }, + ]; + + return awsDeployList.getFunctions().then((result) => { + expect(listFunctionsStub.calledOnce).to.equal(true); + expect(listFunctionsStub.calledWithExactly( + 'Lambda', + 'listFunctions', + { + MaxItems: 200, + }, + awsDeployList.options.stage, + awsDeployList.options.region + )).to.equal(true); + expect(result).to.deep.equal(expectedResult); + }); + }); + }); + + describe('#getFunctionVersions()', () => { + let listVersionsByFunctionStub; + + beforeEach(() => { + listVersionsByFunctionStub = sinon + .stub(awsDeployList.provider, 'request') + .resolves({ + Versions: [ + { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, + ], + }); + }); + + afterEach(() => { + awsDeployList.provider.request.restore(); + }); + + it('should return the versions for the provided functions', () => { + const funcs = [ + { FunctionName: 'listDeployments-dev-func1' }, + { FunctionName: 'listDeployments-dev-func2' }, + ]; + + return awsDeployList.getFunctionVersions(funcs).then((result) => { + const expectedResult = [ + { + Versions: [ + { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, + ], + }, + { + Versions: [ + { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, + ], + }, + ]; + + expect(listVersionsByFunctionStub.calledTwice).to.equal(true); + expect(result).to.deep.equal(expectedResult); + }); + }); + }); + + describe('#displayFunctions()', () => { + const funcs = [ + { + Versions: [ + { FunctionName: 'listDeployments-dev-func-1', Version: '$LATEST' }, + ], + }, + { + Versions: [ + { FunctionName: 'listDeployments-dev-func-2', Version: '$LATEST' }, + { FunctionName: 'listDeployments-dev-func-2', Version: '4711' }, + ], + }, + ]; + + it('should display all the functions in the service together with their versions', () => { + const log = awsDeployList.serverless.cli.log; + + return awsDeployList.displayFunctions(funcs).then(() => { + expect(log.calledWithExactly('Listing functions and their versions:')).to.be.equal(true); + expect(log.calledWithExactly('-------------')).to.be.equal(true); + + expect(log.calledWithExactly('func-1: $LATEST')).to.be.equal(true); + expect(log.calledWithExactly('func-2: $LATEST, 4711')).to.be.equal(true); }); }); }); diff --git a/lib/plugins/aws/rollbackFunction/index.js b/lib/plugins/aws/rollbackFunction/index.js new file mode 100644 index 000000000..49cc1d19d --- /dev/null +++ b/lib/plugins/aws/rollbackFunction/index.js @@ -0,0 +1,123 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const validate = require('../lib/validate'); +const fetch = require('node-fetch'); + +class AwsRollbackFunction { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options || {}; + this.provider = this.serverless.getProvider('aws'); + + Object.assign(this, validate); + + this.commands = { + rollback: { + commands: { + function: { + usage: 'Rollback the function to a specific version', + lifecycleEvents: [ + 'rollback', + ], + options: { + function: { + usage: 'Name of the function', + shortcut: 'f', + required: true, + }, + version: { + usage: 'Version of the function', + shortcut: 'v', + required: true, + }, + stage: { + usage: 'Stage of the function', + shortcut: 's', + }, + region: { + usage: 'Region of the function', + shortcut: 'r', + }, + }, + }, + }, + }, + }; + + this.hooks = { + 'rollback:function:rollback': () => BbPromise.bind(this) + .then(this.validate) + .then(this.getFunctionToBeRestored) + .then(this.fetchFunctionCode) + .then(this.restoreFunction), + }; + } + + getFunctionToBeRestored() { + const funcName = this.options.function; + let funcVersion = this.options.version; + + // versions need to be string so that AWS understands it + funcVersion = String(this.options.version); + + this.serverless.cli.log(`Rolling back function "${funcName}" to version "${funcVersion}"...`); + + const funcObj = this.serverless.service.getFunction(funcName); + + const params = { + FunctionName: funcObj.name, + Qualifier: funcVersion, + }; + + return this.provider.request( + 'Lambda', + 'getFunction', + params, + this.options.stage, this.options.region + ) + .then((func) => func) + .catch((error) => { + if (error.message.match(/not found/)) { + const errorMessage = [ + `Function "${funcName}" with version "${funcVersion}" not found.`, + ` Please check if you've deployed "${funcName}"`, + ` and version "${funcVersion}" is available for this function.`, + ' Please check the docs for more info.', + ].join(''); + throw new Error(errorMessage); + } + throw new Error(error.message); + }); + } + + fetchFunctionCode(func) { + const codeUrl = func.Code.Location; + + return fetch(codeUrl).then((response) => response.buffer()); + } + + restoreFunction(zipBuffer) { + const funcName = this.options.function; + + this.serverless.cli.log('Restoring function...'); + + const funcObj = this.serverless.service.getFunction(funcName); + + const params = { + FunctionName: funcObj.name, + ZipFile: zipBuffer, + }; + + return this.provider.request( + 'Lambda', + 'updateFunctionCode', + params, + this.options.stage, this.options.region + ).then(() => { + this.serverless.cli.log(`Successfully rolled back function: "${this.options.function}"`); + }); + } +} + +module.exports = AwsRollbackFunction; diff --git a/lib/plugins/aws/rollbackFunction/index.test.js b/lib/plugins/aws/rollbackFunction/index.test.js new file mode 100644 index 000000000..8822c0700 --- /dev/null +++ b/lib/plugins/aws/rollbackFunction/index.test.js @@ -0,0 +1,244 @@ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const Serverless = require('../../../Serverless'); +const AwsProvider = require('../provider/awsProvider'); +const CLI = require('../../../classes/CLI'); +const proxyquire = require('proxyquire'); + +describe('AwsRollbackFunction', () => { + let serverless; + let awsRollbackFunction; + let consoleLogStub; + let fetchStub; + let AwsRollbackFunction; + + beforeEach(() => { + fetchStub = sinon.stub().resolves({ buffer: () => {} }); + AwsRollbackFunction = proxyquire('./index.js', { + 'node-fetch': fetchStub, + }); + serverless = new Serverless(); + serverless.servicePath = true; + serverless.service.functions = { + hello: { + handler: true, + name: 'service-dev-hello', + }, + }; + const options = { + stage: 'dev', + region: 'us-east-1', + function: 'hello', + }; + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.cli = new CLI(serverless); + awsRollbackFunction = new AwsRollbackFunction(serverless, options); + consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns(); + }); + + afterEach(() => { + serverless.cli.consoleLog.restore(); + }); + + describe('#constructor()', () => { + let validateStub; + let getFunctionToBeRestoredStub; + let fetchFunctionCodeStub; + let restoreFunctionStub; + + beforeEach(() => { + validateStub = sinon + .stub(awsRollbackFunction, 'validate').resolves(); + getFunctionToBeRestoredStub = sinon + .stub(awsRollbackFunction, 'getFunctionToBeRestored').resolves(); + fetchFunctionCodeStub = sinon + .stub(awsRollbackFunction, 'fetchFunctionCode').resolves(); + restoreFunctionStub = sinon + .stub(awsRollbackFunction, 'restoreFunction').resolves(); + }); + + afterEach(() => { + awsRollbackFunction.validate.restore(); + awsRollbackFunction.getFunctionToBeRestored.restore(); + awsRollbackFunction.fetchFunctionCode.restore(); + awsRollbackFunction.restoreFunction.restore(); + }); + + it('should have hooks', () => expect(awsRollbackFunction.hooks).to.be.not.empty); + + it('should have commands', () => expect(awsRollbackFunction.commands).to.be.not.empty); + + it('should set the provider variable to an instance of AwsProvider', () => + expect(awsRollbackFunction.provider).to.be.instanceof(AwsProvider)); + + it('should set an empty options object if no options are given', () => { + const awsRollbackFunctionWithEmptyOptions = new AwsRollbackFunction(serverless); + expect(awsRollbackFunctionWithEmptyOptions.options).to.deep.equal({}); + }); + + it('should run promise chain in order', () => awsRollbackFunction + .hooks['rollback:function:rollback']().then(() => { + expect(validateStub.calledOnce).to.equal(true); + expect(getFunctionToBeRestoredStub.calledAfter(validateStub)).to.equal(true); + expect(fetchFunctionCodeStub.calledAfter(getFunctionToBeRestoredStub)).to.equal(true); + expect(restoreFunctionStub.calledAfter(fetchFunctionCodeStub)).to.equal(true); + }) + ); + }); + + describe('#getFunctionToBeRestored()', () => { + describe('when function and version can be found', () => { + let getFunctionStub; + + beforeEach(() => { + getFunctionStub = sinon + .stub(awsRollbackFunction.provider, 'request') + .resolves({ function: 'hello' }); + }); + + afterEach(() => { + awsRollbackFunction.provider.request.restore(); + }); + + it('should return the requested function', () => { + awsRollbackFunction.options.function = 'hello'; + awsRollbackFunction.options.version = '4711'; + + return awsRollbackFunction.getFunctionToBeRestored().then((result) => { + expect(getFunctionStub.calledOnce).to.equal(true); + expect(getFunctionStub.calledWithExactly( + 'Lambda', + 'getFunction', + { + FunctionName: 'service-dev-hello', + Qualifier: '4711', + }, + awsRollbackFunction.options.stage, + awsRollbackFunction.options.region + )).to.equal(true); + expect(consoleLogStub.called).to.equal(true); + expect(result).to.deep.equal({ function: 'hello' }); + }); + }); + }); + + describe('when function or version could not be found', () => { + let getFunctionStub; + + beforeEach(() => { + getFunctionStub = sinon + .stub(awsRollbackFunction.provider, 'request') + .rejects(new Error('function hello not found')); + }); + + afterEach(() => { + awsRollbackFunction.provider.request.restore(); + }); + + it('should translate the error message to a custom error message', () => { + awsRollbackFunction.options.function = 'hello'; + awsRollbackFunction.options.version = '4711'; + + return awsRollbackFunction.getFunctionToBeRestored().catch((error) => { + expect(error.message.match(/Function "hello" with version "4711" not found/)); + expect(getFunctionStub.calledOnce).to.equal(true); + expect(getFunctionStub.calledWithExactly( + 'Lambda', + 'getFunction', + { + FunctionName: 'service-dev-hello', + Qualifier: '4711', + }, + awsRollbackFunction.options.stage, + awsRollbackFunction.options.region + )).to.equal(true); + expect(consoleLogStub.called).to.equal(true); + }); + }); + }); + + describe('when other error occurred', () => { + let getFunctionStub; + + beforeEach(() => { + getFunctionStub = sinon + .stub(awsRollbackFunction.provider, 'request') + .rejects(new Error('something went wrong')); + }); + + afterEach(() => { + awsRollbackFunction.provider.request.restore(); + }); + + it('should re-throw the error without translating it to a custom error message', () => { + awsRollbackFunction.options.function = 'hello'; + awsRollbackFunction.options.version = '4711'; + + return awsRollbackFunction.getFunctionToBeRestored().catch((error) => { + expect(error.message.match(/something went wrong/)); + expect(getFunctionStub.calledOnce).to.equal(true); + expect(getFunctionStub.calledWithExactly( + 'Lambda', + 'getFunction', + { + FunctionName: 'service-dev-hello', + Qualifier: '4711', + }, + awsRollbackFunction.options.stage, + awsRollbackFunction.options.region + )).to.equal(true); + expect(consoleLogStub.called).to.equal(true); + }); + }); + }); + }); + + describe('#fetchFunctionCode()', () => { + it('should fetch the zip file content of the previously requested function', () => { + const func = { + Code: { + Location: 'https://foo.com/bar', + }, + }; + + return awsRollbackFunction.fetchFunctionCode(func).then(() => { + expect(fetchStub.called).to.equal(true); + }); + }); + }); + + describe('#restoreFunction()', () => { + let updateFunctionCodeStub; + + beforeEach(() => { + updateFunctionCodeStub = sinon + .stub(awsRollbackFunction.provider, 'request').resolves(); + }); + + afterEach(() => { + awsRollbackFunction.provider.request.restore(); + }); + + it('should restore the provided function', () => { + awsRollbackFunction.options.function = 'hello'; + const zipBuffer = new Buffer(''); + + return awsRollbackFunction.restoreFunction(zipBuffer).then(() => { + expect(updateFunctionCodeStub.calledOnce).to.equal(true); + expect(updateFunctionCodeStub.calledWithExactly( + 'Lambda', + 'updateFunctionCode', + { + FunctionName: 'service-dev-hello', + ZipFile: zipBuffer, + }, + awsRollbackFunction.options.stage, + awsRollbackFunction.options.region + )).to.equal(true); + expect(consoleLogStub.called).to.equal(true); + }); + }); + }); +}); diff --git a/lib/plugins/deploy/deploy.js b/lib/plugins/deploy/deploy.js index 1313a010a..dde9ce0de 100644 --- a/lib/plugins/deploy/deploy.js +++ b/lib/plugins/deploy/deploy.js @@ -67,6 +67,14 @@ class Deploy { lifecycleEvents: [ 'log', ], + commands: { + functions: { + usage: 'List all the deployed functions and their versions', + lifecycleEvents: [ + 'log', + ], + }, + }, }, }, }, diff --git a/lib/plugins/rollback/index.js b/lib/plugins/rollback/index.js index d388d331c..0170f9c7d 100644 --- a/lib/plugins/rollback/index.js +++ b/lib/plugins/rollback/index.js @@ -22,6 +22,29 @@ class Rollback { shortcut: 'v', }, }, + commands: { + function: { + usage: 'Rollback the function to the previous version', + lifecycleEvents: [ + 'rollback', + ], + options: { + function: { + usage: 'Name of the function', + shortcut: 'f', + required: true, + }, + stage: { + usage: 'Stage of the function', + shortcut: 's', + }, + region: { + usage: 'Region of the function', + shortcut: 'r', + }, + }, + }, + }, }, }; } diff --git a/lib/plugins/rollback/index.test.js b/lib/plugins/rollback/index.test.js index d04bd8e20..1ff84bb47 100644 --- a/lib/plugins/rollback/index.test.js +++ b/lib/plugins/rollback/index.test.js @@ -14,21 +14,41 @@ describe('Rollback', () => { }); describe('#constructor()', () => { - it('should have the command rollback', () => { - // eslint-disable-next-line no-unused-expressions - expect(rollback.commands.rollback).to.not.be.undefined; + describe('when dealing with normal rollbacks', () => { + it('should have the command "rollback"', () => { + // eslint-disable-next-line no-unused-expressions + expect(rollback.commands.rollback).to.not.be.undefined; + }); + + it('should have a lifecycle events initialize and rollback', () => { + expect(rollback.commands.rollback.lifecycleEvents).to.deep.equal([ + 'initialize', + 'rollback', + ]); + }); + + it('should have a required option timestamp', () => { + // eslint-disable-next-line no-unused-expressions + expect(rollback.commands.rollback.options.timestamp.required).to.be.true; + }); }); - it('should have a lifecycle events initialize and rollback', () => { - expect(rollback.commands.rollback.lifecycleEvents).to.deep.equal([ - 'initialize', - 'rollback', - ]); - }); + describe('when dealing with function rollbacks', () => { + it('should have the command "rollback function"', () => { + // eslint-disable-next-line no-unused-expressions + expect(rollback.commands.rollback.commands.function).to.not.be.undefined; + }); - it('should have a required option timestamp', () => { - // eslint-disable-next-line no-unused-expressions - expect(rollback.commands.rollback.options.timestamp.required).to.be.true; + it('should have a lifecycle event rollback', () => { + expect(rollback.commands.rollback.commands.function.lifecycleEvents).to.deep.equal([ + 'rollback', + ]); + }); + + it('should have a required option function', () => { + // eslint-disable-next-line no-unused-expressions + expect(rollback.commands.rollback.commands.function.options.function.required).to.be.true; + }); }); }); }); diff --git a/package.json b/package.json index e22f1e5c4..a70ba8ead 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "lodash": "^4.13.1", "minimist": "^1.2.0", "moment": "^2.13.0", - "node-fetch": "^1.5.3", + "node-fetch": "^1.6.0", "replaceall": "^0.1.6", "resolve-from": "^2.0.0", "semver": "^5.0.3",