UNPKG

firmament-yargs

Version:

Typescript classes for building CLI node applications

615 lines (603 loc) 18.8 kB
import 'reflect-metadata'; import kernel from '../inversify.config'; import {expect} from 'chai'; import * as sinon from 'sinon'; import * as _ from 'lodash'; import path = require('path'); import {Spawn} from "../interfaces/spawn"; import {SpawnOptions2} from "../custom-typings"; import {ChildProcessSpawn} from "../interfaces/child-process-spawn"; import {MockChildProcessSpawnImpl} from "./injectable-mocks/mock-child-process-spawn-impl"; import {ChildProcessSpawnImpl} from "../implementations/child-process-spawn-impl"; const pathToScripts = path.resolve(__dirname, '../../ts/test/shell-scripts'); const testScript = path.resolve(pathToScripts, 'kitchen-sink.sh'); const testArgs = [ 'arg1', 'arg2' ]; function checkError(error, code: number = 0, signal: string = '', stdOut: string = '', stdErr: string = '') { expect(error).to.exist; expect(error.message).to.be.not.empty; checkResult(error.message, code, signal, stdOut, stdErr); } function checkResult(result, code: number = 0, signal: string = '', stdOut: string = '', stdErr: string = '') { const resultObj = JSON.parse(result); expect(resultObj.code).to.equal(code); expect(resultObj.signal).to.equal(signal); expect(resultObj.stderrText).to.equal(stdErr); expect(resultObj.stdoutText).to.equal(stdOut); } function getNewSpawnOptions(): SpawnOptions2 { return _.clone({ preSpawnMessage: 'PreSpawn Message', postSpawnMessage: 'PostSpawn Message', suppressDiagnostics: true, suppressStdErr: true, suppressStdOut: true, cacheStdErr: true, cacheStdOut: true, suppressResult: true, suppressFinalError: true, sudoUser: '', sudoPassword: '', forceNullChildProcess: false }); } function getArgArray(): string[] { return [ testScript, 'writeToStdOutErrExitWithErrCode', '0', 'writeToStdOut', 'writeToStdErr', ...testArgs ]; } describe('Testing Spawn Creation/Force Error', () => { before(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(MockChildProcessSpawnImpl); }); after(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(ChildProcessSpawnImpl); }); it('should be created by kernel', (done) => { const spawn = kernel.get<Spawn>('Spawn'); expect(spawn).to.exist; done(); }); it('should have final callback with error', (done) => { const spawn = kernel.get<Spawn>('Spawn'); spawn.forceError = true; spawn.spawnShellCommandAsync(null, null, null, (err) => { expect(err).to.exist; expect(err.message).to.equal('force error: spawnShellCommandAsync'); done(); }); }); }); describe('Testing Spawn ', () => { let spawn: Spawn; let argArray: string[]; let spawnOptions: SpawnOptions2; let sinonSandbox; let cbStdOutSpy; let cbStdErrSpy; let cbDiagnosticSpy; function cbStatusMock(err, result) { if(err) { cbStdErrSpy(err, result); } else { cbStdOutSpy(err, result); } } const o = { cbStdOutCall: () => { }, cbStdErrCall: () => { }, cbStatus: () => { }, cbDiagnostic: () => { } }; before(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(MockChildProcessSpawnImpl); }); after(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(ChildProcessSpawnImpl); }); beforeEach(() => { //kernel.bind<ChildProcessSpawn>('ChildProcessSpawn').to(MockChildProcessSpawnImpl); spawn = kernel.get<Spawn>('Spawn'); argArray = getArgArray(); spawnOptions = getNewSpawnOptions(); sinonSandbox = sinon.createSandbox(); cbDiagnosticSpy = sinonSandbox.spy(o, 'cbDiagnostic'); cbStdOutSpy = sinonSandbox.spy(o, 'cbStdOutCall'); cbStdErrSpy = sinonSandbox.spy(o, 'cbStdErrCall'); }); afterEach(() => { sinonSandbox.restore(); }); it('spawnShellCommandAsync: undefined options, exit with code 0', (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions = undefined; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; checkResult(result); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: null options, exit with code 0', (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions = null; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; checkResult(result); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: null options, undefined arguments exit with code 0', (done) => { argArray = undefined; spawnOptions = null; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.exist; expect(err.message).to.equal('"file" argument must be a non-empty string'); expect(result).to.not.exist; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: null options, null arguments exit with code 0', (done) => { argArray = null; spawnOptions = null; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.exist; expect(err.message).to.equal('"file" argument must be a non-empty string'); expect(result).to.not.exist; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: no result, exit with code 0', (done) => { argArray[1] = 'exitWithErrCode'; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; expect(result).to.be.empty; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: no result, yes diagnostics, exit with code 0', (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.suppressDiagnostics = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; expect(result).to.be.empty; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: yes result, exit with code 0', (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.suppressResult = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; checkResult(result); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: no result, no finalError, exit with code 3', (done) => { argArray[1] = 'exitWithErrCode'; argArray[2] = '3'; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; expect(result).to.be.empty; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: no result, yes finalError, exit with code 3', (done) => { argArray[1] = 'exitWithErrCode'; argArray[2] = '3'; spawnOptions.suppressFinalError = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { checkError(err, 3); expect(result).to.be.empty; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: yes result, yes finalError, exit with code 3', (done) => { argArray[1] = 'exitWithErrCode'; argArray[2] = '3'; spawnOptions.suppressFinalError = false; spawnOptions.suppressResult = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { checkError(err, 3); checkResult(result, 3); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: yes result, no finalError, exit with code 3', (done) => { argArray[1] = 'exitWithErrCode'; argArray[2] = '3'; spawnOptions.suppressResult = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; checkResult(result, 3); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: yes result, no finalError, yes stdout, yes stderr, exit with code 0', (done) => { spawnOptions.suppressStdOut = false; spawnOptions.suppressStdErr = false; spawnOptions.suppressResult = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, cbStatusMock, (err, result) => { expect(err).to.not.exist; const concatArgs = testArgs.join(''); checkResult(result, 0, '', concatArgs, concatArgs); expect(cbStdErrSpy.called).to.be.true; expect(cbStdOutSpy.called).to.be.true; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it('spawnShellCommandAsync: edge: yes suppressStdErr, no cacheStdErr', (done) => { spawnOptions.suppressResult = false; spawnOptions.cacheStdOut = false; spawnOptions.cacheStdErr = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, cbStatusMock, (err, result) => { expect(err).to.not.exist; checkResult(result); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it(`spawnShellCommandAsync:edge: fire 'error' event on child process`, (done) => { spawnOptions.suppressResult = false; spawnOptions.suppressFinalError = false; const cp = spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { checkResult(result, 30, 'SIGUSR1'); checkError(err, 30, 'SIGUSR1'); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); cp.emit('error', 30, 'SIGUSR1'); }); it(`spawnShellCommandAsync:edge: EACCES on bad cmd`, (done) => { argArray[0] = '/'; spawnOptions.suppressResult = false; spawnOptions.suppressFinalError = false; spawn.spawnShellCommandAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(result).to.not.exist; expect(err['code']).to.equal('EACCES'); expect(err['errno']).to.equal('EACCES'); expect(err.message).to.equal('spawn EACCES'); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); }); describe('Testing SudoSpawn ', () => { let spawn: Spawn; let argArray: string[]; let spawnOptions: SpawnOptions2; let sinonSandbox; let cbStdOutSpy; let cbStdErrSpy; let cbDiagnosticSpy; const o = { cbStdOutCall: () => { }, cbStdErrCall: () => { }, cbStatus: () => { }, cbDiagnostic: () => { } }; before(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(MockChildProcessSpawnImpl); }); after(() => { //kernel.rebind<ChildProcessSpawn>('ChildProcessSpawn').to(ChildProcessSpawnImpl); }); beforeEach(() => { //kernel.bind<ChildProcessSpawn>('ChildProcessSpawn').to(MockChildProcessSpawnImpl); spawn = kernel.get<Spawn>('Spawn'); argArray = getArgArray(); spawnOptions = getNewSpawnOptions(); sinonSandbox = sinon.createSandbox(); cbDiagnosticSpy = sinonSandbox.spy(o, 'cbDiagnostic'); cbStdOutSpy = sinonSandbox.spy(o, 'cbStdOutCall'); cbStdErrSpy = sinonSandbox.spy(o, 'cbStdErrCall'); }); afterEach(() => { sinonSandbox.restore(); }); it(`sudoSpawnAsync: forceError `, (done) => { spawn.forceError = true; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.exist; expect(err.message).to.equal('force error: sudoSpawnAsync'); expect(result).to.not.exist; expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: spawnOptions is undefined, `, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions = undefined; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { checkError(err, 1); checkResult(result, 1); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: spawnOptions is null, `, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions = null; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { checkError(err, 1); checkResult(result, 1); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.true; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: non-existent user should return error`, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.sudoUser = 'poopuser'; spawnOptions.sudoPassword = 'pooppassword'; spawnOptions.suppressFinalError = false; spawnOptions.suppressStdErr = false; spawnOptions.suppressResult = false; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { const stdErr = 'sudo: unknown user: poopuser\nsudo: unable to initialize policy plugin\n'; checkError(err, 1, '', '', stdErr); checkResult(result, 1, '', '', stdErr); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: existing user but bad password should return error`, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.sudoUser = 'vagrant'; spawnOptions.sudoPassword = 'pooppassword'; spawnOptions.suppressFinalError = false; spawnOptions.suppressStdErr = false; spawnOptions.suppressResult = false; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { const errStdErr = '#login-prompt#Sorry, try again.\n' + '#login-prompt#Sorry, try again.\n' + '#login-prompt#sudo: 3 incorrect password attempts\n'; const resultStdErr = 'Sorry, try again.\n' + 'Sorry, try again.\n' + 'sudo: 3 incorrect password attempts\n'; checkError(err, 1, '', '', errStdErr); checkResult(result, 1, '', '', resultStdErr); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: forceNullChildProcess should return error`, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.sudoUser = 'vagrant'; spawnOptions.sudoPassword = 'pooppassword'; spawnOptions.suppressFinalError = false; spawnOptions.suppressStdErr = false; spawnOptions.suppressResult = false; spawnOptions.forceNullChildProcess = true; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(result).to.not.exist; expect(err.message).to.be.equal('error: forceNullChildProcess'); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); it(`sudoSpawnAsync: existing user and good password`, (done) => { argArray[1] = 'exitWithErrCode'; spawnOptions.sudoUser = 'vagrant'; spawnOptions.sudoPassword = 'password'; spawnOptions.suppressFinalError = false; spawnOptions.suppressStdErr = false; spawnOptions.suppressResult = false; spawn.sudoSpawnAsync( argArray, spawnOptions, o.cbStatus, (err, result) => { expect(err).to.not.exist; checkResult(result); expect(cbStdErrSpy.called).to.be.false; expect(cbStdOutSpy.called).to.be.false; expect(cbDiagnosticSpy.called).to.be.false; done(); }, o.cbDiagnostic ); }); });