@villedemontreal/scripting
Version:
Scripting core utilities
705 lines (565 loc) • 24.4 kB
text/typescript
/* eslint-disable @typescript-eslint/no-misused-promises */
import { program as caporal, Program } from '@caporal/core';
import { globalConstants, utils } from '@villedemontreal/general-utils';
import { assert } from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as sinon from 'sinon';
import { TestingScript } from '../scripts/testing/testingScript';
import { configs } from './config/configs';
import {
containsText,
isMainHelpDisplayed,
run,
setTestingConfigs,
timeout,
withCustomRunFile,
withLogNodeInstance,
} from './utils/testingUtils';
const nock = require('nock');
describe(`Scripts tests`, function () {
timeout(this, 30000);
before(() => {
setTestingConfigs();
});
describe(`Compilation`, () => {
it(`Default`, async function () {
timeout(this, 60000);
const distDir = path.resolve(`${__dirname}/..`);
await utils.deleteDir(distDir);
assert.isFalse(fs.existsSync(distDir));
const { output, isSuccess } = await run(`testing:testingScript`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Compilation done.`) > -1);
assert.isFalse(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(output.indexOf(`msg: info`) > -1);
// ==========================================
// The "dist" folder has been generated
// ==========================================
assert.isTrue(fs.existsSync(distDir));
});
it(`No compilation`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isTrue(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(output.indexOf(`msg: info`) > -1);
});
it(`Compilation silent option`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--silent`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isFalse(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isFalse(output.indexOf(`msg: info`) > -1);
});
});
describe(`Logs`, () => {
it(`Default is info`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`msg: debug`) > -1);
assert.isTrue(output.indexOf(`msg: info`) > -1);
assert.isTrue(output.indexOf(`msg: warn`) > -1);
assert.isTrue(output.indexOf(`msg: error`) > -1);
});
it(`Silent arg`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--silent`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`msg: debug`) > -1);
assert.isFalse(output.indexOf(`msg: info`) > -1);
assert.isFalse(output.indexOf(`msg: warn`) > -1);
assert.isFalse(output.indexOf(`msg: error`) > -1);
});
it(`Quiet arg`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--quiet`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`msg: debug`) > -1);
assert.isFalse(output.indexOf(`msg: info`) > -1);
assert.isTrue(output.indexOf(`msg: warn`) > -1);
assert.isTrue(output.indexOf(`msg: error`) > -1);
});
it(`Verbose arg`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--verbose`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`msg: debug`) > -1);
assert.isTrue(output.indexOf(`msg: info`) > -1);
assert.isTrue(output.indexOf(`msg: warn`) > -1);
assert.isTrue(output.indexOf(`msg: error`) > -1);
});
it(`Verbose short arg`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `-v`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`msg: debug`) > -1);
assert.isTrue(output.indexOf(`msg: info`) > -1);
assert.isTrue(output.indexOf(`msg: warn`) > -1);
assert.isTrue(output.indexOf(`msg: error`) > -1);
});
});
describe(`Main Help`, () => {
it(`No args at all - compilation is done and main help is displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run();
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Compilation done.`) > -1);
assert.isFalse(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Just "help" and "--nc" - no compilation is done and main help is displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`help`, `--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isTrue(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Just "--help" - compilation is done and main help is displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`--help`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Compilation done.`) > -1);
assert.isFalse(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Just "-h" - compilation is done and main help is displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`-h`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Compilation done.`) > -1);
assert.isFalse(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Just "--nc" - No compilation is done and main help is displayed`, async () => {
const { output, isSuccess } = await run(`--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isTrue(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`"--help" and "--nc" - No compilation is done and main help is displayed`, async () => {
const { output, isSuccess } = await run(`--help`, `--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isTrue(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`"-h" and "--nc" - No compilation is done and main help is displayed`, async () => {
const { output, isSuccess } = await run(`-h`, `--nc`);
assert.isTrue(isSuccess);
assert.isFalse(output.indexOf(`Compilation done.`) > -1);
assert.isTrue(output.indexOf(`Compilation skipped because of the "--nc" parameter...`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Unknown command - Main help is displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`NOPE`, `--nc`);
assert.isFalse(isSuccess);
assert.isTrue(output.indexOf(`Unknown command NOPE`) > -1);
assert.isTrue(isMainHelpDisplayed(output));
});
it(`Unknown command with --silent arg - Main help not displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`NOPE`, `--nc`, `--silent`);
assert.isFalse(isSuccess);
assert.isTrue(output.indexOf(`Unknown command NOPE`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
it(`Unknown command with --quiet arg - Main help not displayed`, async function () {
timeout(this, 60000);
const { output, isSuccess } = await run(`NOPE`, `--nc`, `--quiet`);
assert.isFalse(isSuccess);
assert.isTrue(output.indexOf(`Unknown command NOPE`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
});
describe(`Command Help`, () => {
it(`Using "--help"`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--help`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`A simple testing script`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
it(`Using "-h"`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `-h`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`A simple testing script`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
it(`Help command`, async () => {
const { output, isSuccess } = await run(`help`, `testing:testingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`A simple testing script`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
it(`Command Help for hidden scripts works too`, async () => {
const { output, isSuccess } = await run(
`testing:testingHiddenScript`,
`--nc`,
`--userName`,
`Stromgol`,
`--help`,
);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`A testing hidden script`) > -1);
});
});
describe(`Varia`, () => {
it(`Custom short option`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `-p`, `123`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`port: 123`) > -1);
});
it(`Custom long option`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--port`, `123`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`port: 123`) > -1);
});
it(`Option not specified but required ("--userName" here) - The help of the command is displayed`, async () => {
const { output, isSuccess } = await run(`testing:testingHiddenScript`, `--nc`);
assert.isFalse(isSuccess);
assert.isFalse(output.indexOf(`userName is`) > -1);
assert.isTrue(output.indexOf(`Missing required flag --username`) > -1);
assert.isTrue(output.indexOf(`USAGE — testing:testingHiddenScript`) > -1); // command help
});
it(`Regular Error (the help of the command is not printed!)`, async () => {
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`, `--throwError`);
assert.isFalse(isSuccess);
assert.isTrue(output.indexOf(`This is a regular error`) > -1);
assert.isFalse(output.indexOf(`userName is`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
assert.isFalse(output.indexOf(`USAGE — testing:testingHiddenScript`) > -1); // NO command help!
});
it(`Invalid argument`, async () => {
const { output, isSuccess } = await run(
`testing:testingScript`,
`--nc`,
`--port`,
`notANumber`,
);
assert.isFalse(isSuccess);
assert.isTrue(output.indexOf(`Invalid value for option`) > -1);
assert.isFalse(output.indexOf(`userName is`) > -1);
assert.isTrue(output.indexOf(`USAGE — testing:testingScript`) > -1); // command help
});
it(`Hidden script can still be called`, async () => {
const { output, isSuccess } = await run(
`testing:testingHiddenScript`,
`--nc`,
`--username`,
`Stromgol`,
);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`username is Stromgol`) > -1);
});
it(`We can register a script without passing action parameters`, async () => {
const prog = new Program();
assert.isFalse(
caporal.getCommands().some((command) => command.name === 'testing:testingScript'),
);
const script = new TestingScript(null); // no params!
await script.registerScript(prog);
let found = false;
for (let i = prog.getCommands().length - 1; i >= 0; --i) {
if (prog.getCommands()[i].name === 'testing:testingScript') {
found = true;
prog.getCommands().splice(i, 1);
break;
}
}
assert.isTrue(found);
});
it(`Calling a script using NPM`, async () => {
let output = ``;
await utils.exec(
configs.isWindows ? 'npm.cmd' : 'npm',
[`run`, `lint`, `--`, `--nc`, `--help`],
{
outputHandler: (stdoutData: string, stderrData: string) => {
const newOut = `${stdoutData ? ' ' + stdoutData : ''} ${
stderrData ? ' ' + stderrData : ''
} `;
output += newOut;
},
},
);
assert.isTrue(output.indexOf(`Run the ESLint validation`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
/**
* Note that it is way easier to call a script
* programatically *from another script*, since you
* can simply use `this.invokeScript()`.
*
* Also, if some logic is required in a script
* *and* elsewhere in the application, it may be a good
* idea to move this code in a service or in an utility!
*/
it(`Calling a script programmatically`, async () => {
let output = '';
const logger = new Proxy(
{},
{
get: (target, prop) => {
return function () {
if (prop === 'info') {
// eslint-disable-next-line prefer-rest-params
output += `${arguments[0]}\n`;
}
};
},
},
);
await new TestingScript({
args: {},
options: {
port: 789,
},
program: sinon.stub() as any,
command: sinon.stub() as any,
ddash: sinon.stub() as any,
logger: logger as any,
}).run();
assert.isTrue(output.indexOf(`port: 789`) > -1);
});
it(`OutputName - Regular script`, async () => {
// Core script
const { output, isSuccess } = await run(`testing:testingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Script "testing:testingScript"`) > -1);
});
/**
* Since the `run()` command starts a new process, we
* can't add a global option to the proper Caporal
* instance directly here. We need to tweak the `run`
* file and add it there.
*/
it(`Custom global options`, async () => {
const { output, isSuccess } = await withCustomRunFile(
`const caporal = require('@caporal/core').program;`,
`const caporal = require('@caporal/core').program;
caporal.option('--custom', 'Custom global option', {
global: true
});
`,
`testing:testingScriptGlobalCustomOptions1`,
`--nc`,
`--custom`,
);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`custom #1: true`) > -1);
assert.isTrue(output.indexOf(`custom #2: true`) > -1);
});
it(`"scriptsIndexModule" can be undefined`, async () => {
const { output, isSuccess } = await withCustomRunFile(
'scriptsIndexModule: `./scripts/index`,',
``,
`lint`,
`--help`,
`--nc`,
);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`Run the ESLint validation`) > -1);
assert.isFalse(isMainHelpDisplayed(output));
});
it(`A required dependency is missing`, async () => {
const { output, isSuccess } = await run(`testing:testingDepMissingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isTrue(
output.indexOf(`This script requires some dependencies that are not direct`) > -1,
);
assert.isTrue(output.indexOf(`- _missingDependency`) > -1);
// Script still called
assert.isTrue(output.indexOf(`In TestingDepMissingScript`) > -1);
});
});
describe('Cascading scripts', () => {
it(`Call subscript with defaults`, async () => {
const { output, isSuccess } = await run(
`testing:testingCallingScript`,
`--nc`,
`--foo`,
`55`,
);
assert.isTrue(isSuccess);
const expectedOutput = `info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingScriptWithArgs" starting...
info: Start service MyService on port 55 with delay undefined, --verbose: undefined
info: Script "testing:testingScriptWithArgs" successful
info: Script "testing:testingExampleScript" starting...
info: The lucky number is 55
info: Script "testing:testingExampleScript" successful
info: Script "testing:testingCallingScript" successful`;
assert.isTrue(containsText(output, expectedOutput));
});
it(`Call subscript without defaults, with the "--verbose" global option`, async () => {
const { output, isSuccess } = await run(
`testing:testingCallingScript`,
`--nc`,
`--verbose`,
`--foo`,
`56`,
`--bar`,
`someName`,
`--delay`,
`100`,
);
assert.isTrue(isSuccess);
const expectedOutput = `info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingScriptWithArgs" starting...
info: Start service someName on port 56 with delay 100, --verbose: true
info: Script "testing:testingScriptWithArgs" successful
info: Script "testing:testingExampleScript" starting...
info: The lucky number is 56
info: Script "testing:testingExampleScript" successful
info: Script "testing:testingCallingScript" successful`;
assert.isTrue(containsText(output, expectedOutput));
});
it(`Call subscript without defaults, without the "--verbose" global option`, async () => {
const { output, isSuccess } = await run(
`testing:testingCallingScript`,
`--nc`,
// `--verbose`, // no verbose
`--foo`,
`56`,
`--bar`,
`someName`,
`--delay`,
`100`,
);
assert.isTrue(isSuccess);
const expectedOutput = `info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingScriptWithArgs" starting...
info: Start service someName on port 56 with delay 100, --verbose: undefined
info: Script "testing:testingScriptWithArgs" successful
info: Script "testing:testingExampleScript" starting...
info: The lucky number is 56
info: Script "testing:testingExampleScript" successful
info: Script "testing:testingCallingScript" successful`;
assert.isTrue(containsText(output, expectedOutput));
});
it(`Call subscript without defaults, with the "--verbose" global option but then forced to false`, async () => {
const { output, isSuccess } = await run(
`testing:testingCallingScript`,
`--nc`,
`--verbose`, // verbose
`--forceVerboseToFalse`, // force verbose to false when calling another script
`--foo`,
`56`,
`--bar`,
`someName`,
`--delay`,
`100`,
);
assert.isTrue(isSuccess);
const expectedOutput = `info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingScriptWithArgs" starting...
info: Start service someName on port 56 with delay 100, --verbose: false
info: Script "testing:testingScriptWithArgs" successful
info: Script "testing:testingExampleScript" starting...
info: The lucky number is 56
info: Script "testing:testingExampleScript" successful
info: Script "testing:testingCallingScript" successful`;
assert.isTrue(containsText(output, expectedOutput));
});
it(`Call failing subscript`, async () => {
const { output, isSuccess } = await run(
`testing:testingCallingScript`,
`--nc`,
`--foo`,
`56`,
`--bar`,
`someName`,
`--delay`,
`100`,
`--throwError`,
);
assert.isFalse(isSuccess);
const expectedOutput = `info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingCallingScript" starting...
info: Script "testing:testingScriptWithArgs" starting...
info: Start service someName on port 56 with delay 100
error: Script "testing:testingScriptWithArgs" failed after
warn: Script "testing:testingCallingScript" was aborted after`;
assert.isTrue(containsText(output, expectedOutput));
assert.isTrue(output.indexOf(`with: Some error...`) > -1);
});
});
describe(`NODE_APP_INSTANCE env var`, () => {
let nodeAppInstanceOriginal: string;
before(() => {
nodeAppInstanceOriginal = process.env[globalConstants.envVariables.NODE_APP_INSTANCE];
});
after(() => {
if (nodeAppInstanceOriginal) {
process.env[globalConstants.envVariables.NODE_APP_INSTANCE] = nodeAppInstanceOriginal;
} else {
delete process.env[globalConstants.envVariables.NODE_APP_INSTANCE];
}
});
it(`test script -> set to "tests" automatically`, async () => {
delete process.env[globalConstants.envVariables.NODE_APP_INSTANCE];
const { output, isSuccess } = await withLogNodeInstance(`testing:testingScript`, `--nc`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`MAIN NODE_APP_INSTANCE: tests`) > -1);
});
it(`non test script -> not set to "tests"`, async () => {
delete process.env[globalConstants.envVariables.NODE_APP_INSTANCE];
const { output, isSuccess } = await withLogNodeInstance(`lint`, `--nc`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`MAIN NODE_APP_INSTANCE: undefined`) > -1);
});
it(`non test script with "--testing"`, async () => {
delete process.env[globalConstants.envVariables.NODE_APP_INSTANCE];
const { output, isSuccess } = await withLogNodeInstance(`lint`, `--nc`, `--testing`);
assert.isTrue(isSuccess);
assert.isTrue(output.indexOf(`MAIN NODE_APP_INSTANCE: tests`) > -1);
});
});
describe('Sonar-init script', () => {
it(` should fail when sonar-project.properties is missing`, async () => {
const { output, isSuccess } = await run(`sonar-init`);
assert.isFalse(isSuccess);
const expectedOutput = `info: Script "sonar-init" starting...
error: Script "sonar-init" failed after 0 s with: ENOENT: no such file or directory, open 'sonar-project.properties'
`;
assert.isTrue(containsText(output, expectedOutput));
});
// Skipping this unit test suite: all these tests are integration tests on scripts; scripts are
// executed in another spawned process; therefore, using Nock to stub http calls does not work.
// One solution to make these tests succeed would be to run a sonar server in a side-car container,
// and then use it to test 'sonar' and 'sonar-init' scripts.
describe.skip(' with valid sonar-project.properties file', async () => {
before(async () => {
await fs.copyFile(
'./src/utils/test-sonar-project_url-with-trailing-slash.properties',
'./sonar-project.properties',
);
});
after(async () => {
await fs.unlink('./sonar-project.properties');
});
afterEach(() => {
nock.cleanAll();
});
it(` should do something`, async () => {
nock('https://example.com')
.get('/sonar/api/project_branches/list')
.query({ project: 'my-project-key' })
.reply(200);
nock('https://example.com').get('/sonar/api/another_endpoint').reply(200);
const { output, isSuccess } = await run(`sonar-init`, '-v');
console.info('***** Pending mocks *****');
console.info(nock.pendingMocks());
console.info('*************************');
assert.isTrue(
nock.isDone(),
`There are remaining expected HTTP calls: ${nock.pendingMocks().toString()}`,
);
assert.isTrue(isSuccess);
const expectedOutput = `info: Script "sonar-init" starting...
error: Script "sonar-init" failed after 0 s with: ENOENT: no such file or directory, open 'sonar-project.properties'
`;
assert.isTrue(containsText(output, expectedOutput));
});
});
});
});