'use strict'; const expect = require('chai').expect; const sinon = require('sinon'); const CLI = require('../../lib/classes/CLI'); const os = require('os'); const fse = require('fs-extra'); const exec = require('child_process').exec; const path = require('path'); const Serverless = require('../../lib/Serverless'); const testUtils = require('../../tests/utils'); describe('CLI', () => { let cli; let serverless; beforeEach(() => { serverless = new Serverless({}); }); describe('#constructor()', () => { it('should set the serverless instance', () => { cli = new CLI(serverless); expect(cli.serverless).to.deep.equal(serverless); }); it('should set an empty loadedPlugins array', () => { cli = new CLI(serverless); expect(cli.loadedPlugins.length).to.equal(0); }); it('should set a null inputArray when none is provided', () => expect(new CLI(serverless).inputArray).to.be.null); it('should set the inputObject when provided', () => { cli = new CLI(serverless, ['foo', 'bar', '--baz', '-qux']); expect(cli.inputArray[0]).to.equal('foo'); expect(cli.inputArray[1]).to.equal('bar'); expect(cli.inputArray[2]).to.equal('--baz'); expect(cli.inputArray[3]).to.equal('-qux'); }); }); describe('#setLoadedPlugins()', () => { it('should set the loadedPlugins array with the given plugin instances', () => { class PluginMock {} const pluginMock = new PluginMock(); const plugins = [pluginMock]; cli = new CLI(serverless); cli.setLoadedPlugins(plugins); expect(cli.loadedPlugins[0]).to.equal(pluginMock); }); }); describe('#displayHelp()', () => { it('should return true when no command is given', () => { cli = new CLI(serverless, []); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "help" parameter is given', () => { cli = new CLI(serverless, ['help']); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "--help" parameter is given', () => { cli = new CLI(serverless, ['--help']); class PluginMock { constructor() { this.commands = { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, provider: { usage: 'test', }, }, commands: { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, provider: { usage: 'test', }, }, }, }, }, }; } } serverless.pluginManager.addPlugin(PluginMock); cli.setLoadedPlugins(serverless.pluginManager.getPlugins()); cli.setLoadedCommands(serverless.pluginManager.getCommands()); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "-h" parameter is given', () => { cli = new CLI(serverless, ['-h']); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "version" parameter is given', () => { cli = new CLI(serverless, ['version']); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "--version" parameter is given', () => { cli = new CLI(serverless, ['--version']); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "-v" parameter is given', () => { cli = new CLI(serverless, ['-v']); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "-h" parameter is given with a command', () => { cli = new CLI(serverless, ['test', '-h']); class PluginMock { constructor() { this.commands = { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, provider: { usage: 'test', }, }, }, }; } } serverless.pluginManager.addPlugin(PluginMock); cli.setLoadedPlugins(serverless.pluginManager.getPlugins()); cli.setLoadedCommands(serverless.pluginManager.getCommands()); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return true when the "-h" parameter is given with a deep command', () => { cli = new CLI(serverless, ['test', 'test', '-h']); class PluginMock { constructor() { this.commands = { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, provider: { usage: 'test', }, }, commands: { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, provider: { usage: 'test', }, }, }, }, }, }; } } serverless.pluginManager.addPlugin(PluginMock); cli.setLoadedPlugins(serverless.pluginManager.getPlugins()); cli.setLoadedCommands(serverless.pluginManager.getCommands()); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(true); }); it('should return false if no "help" or "version" related command / option is given', () => { cli = new CLI(serverless, ['test']); class PluginMock { constructor() { this.commands = { test: { usage: 'test', lifecycleEvents: [ 'test', ], options: { name: { usage: 'test', }, }, }, }; } } serverless.pluginManager.addPlugin(PluginMock); cli.setLoadedPlugins(serverless.pluginManager.getPlugins()); cli.setLoadedCommands(serverless.pluginManager.getCommands()); const processedInput = cli.processInput(); const helpDisplayed = cli.displayHelp(processedInput); expect(helpDisplayed).to.equal(false); }); }); describe('#generateCommandsHelp()', () => { let getCommandsStub; let consoleLogStub; let displayCommandUsageStub; let displayCommandOptionsStub; const commands = { package: { usage: 'Packages a Serverless service', lifecycleEvents: ['cleanup', 'initialize'], options: {}, key: 'package', pluginName: 'Package', }, deploy: { usage: 'Deploy a Serverless service', lifecycleEvents: ['cleanup', 'initialize'], options: {}, key: 'deploy', pluginName: 'Deploy', commands: {}, }, }; beforeEach(() => { cli = new CLI(serverless); getCommandsStub = sinon.stub(cli.serverless.pluginManager, 'getCommands') .returns(commands); consoleLogStub = sinon.stub(cli, 'consoleLog').returns(); displayCommandUsageStub = sinon.stub(cli, 'displayCommandUsage').returns(); displayCommandOptionsStub = sinon.stub(cli, 'displayCommandOptions').returns(); }); afterEach(() => { cli.serverless.pluginManager.getCommands.restore(); cli.consoleLog.restore(); cli.displayCommandUsage.restore(); cli.displayCommandOptions.restore(); }); it('should gather and generate the commands help info if the command can be found', () => { const commandsArray = ['package']; cli.inputArray = commandsArray; cli.generateCommandsHelp(commandsArray); expect(getCommandsStub.calledOnce).to.equal(true); expect(consoleLogStub.called).to.equal(true); expect(displayCommandUsageStub.calledOnce).to.equal(true); expect(displayCommandUsageStub.calledWithExactly( commands.package, 'package' )).to.equal(true); expect(displayCommandOptionsStub.calledOnce).to.equal(true); expect(displayCommandOptionsStub.calledWithExactly( commands.package )).to.equal(true); }); it('should throw an error if the command could not be found', () => { const commandsArray = ['invalid-command']; cli.inputArray = commandsArray; expect(() => { cli.generateCommandsHelp(commandsArray); }) .to.throw(Error, 'not found'); expect(getCommandsStub.calledOnce).to.equal(true); expect(consoleLogStub.called).to.equal(false); expect(displayCommandUsageStub.calledOnce).to.equal(false); expect(displayCommandOptionsStub.calledOnce).to.equal(false); }); }); describe('#processInput()', () => { it('should only return the commands when only commands are given', () => { cli = new CLI(serverless, ['deploy', 'functions']); const inputToBeProcessed = cli.processInput(); const expectedObject = { commands: ['deploy', 'functions'], options: {} }; expect(inputToBeProcessed).to.deep.equal(expectedObject); }); it('should only return the options when only options are given', () => { cli = new CLI(serverless, ['-f', 'function1', '-r', 'resource1']); const inputToBeProcessed = cli.processInput(); const expectedObject = { commands: [], options: { f: 'function1', r: 'resource1' } }; expect(inputToBeProcessed).to.deep.equal(expectedObject); }); it('should only return numbers like strings when numbers are given on options', () => { cli = new CLI(serverless, ['-f', 'function1', '-k', 123]); const inputToBeProcessed = cli.processInput(); const expectedObject = { commands: [], options: { f: 'function1', k: '123' } }; expect(inputToBeProcessed).to.deep.equal(expectedObject); }); it('should return commands and options when both are given', () => { cli = new CLI(serverless, ['deploy', 'functions', '-f', 'function1']); const inputToBeProcessed = cli.processInput(); const expectedObject = { commands: ['deploy', 'functions'], options: { f: 'function1' } }; expect(inputToBeProcessed).to.deep.equal(expectedObject); }); }); describe('Integration tests', function () { this.timeout(0); const that = this; before(() => { const tmpDir = testUtils.getTmpDirPath(); that.cwd = process.cwd(); fse.mkdirsSync(tmpDir); process.chdir(tmpDir); serverless = new Serverless(); serverless.init(); // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. const execPrefix = os.platform() === 'win32' ? 'node ' : ''; that.serverlessExec = execPrefix + path.join(serverless.config.serverlessPath, '..', 'bin', 'serverless'); }); after(() => { process.chdir(that.cwd); }); it('should print general --help to stdout', (done) => { exec(`${this.serverlessExec} --help`, (err, stdout) => { if (err) { done(err); return; } expect(stdout).to.contain('contextual help'); done(); }); }); it('should print command --help to stdout', (done) => { exec(`${this.serverlessExec} deploy --help`, (err, stdout) => { if (err) { done(err); return; } expect(stdout).to.contain('deploy'); expect(stdout).to.contain('--stage'); done(); }); }); it('should print help --verbose to stdout', (done) => { exec(`${this.serverlessExec} help --verbose`, (err, stdout) => { if (err) { done(err); return; } expect(stdout).to.contain('Commands by plugin'); done(); }); }); }); });