From d1eadd75e73e79fe3c50034f151c6a4e93844c14 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 28 Apr 2021 09:59:10 +0200 Subject: [PATCH] feat: Expose AppRunner.run for easily serving from JS apps * feat: make methods in CliRunner public * change: rename CliRunner to AppRunner * fix: process being passed incorrectly to runCli * feat: expose AppRunner.run for easily serving from JS apps * change: only make run methods on AppRunner public --- bin/server.js | 4 +- src/index.ts | 2 +- src/init/{CliRunner.ts => AppRunner.ts} | 33 ++- test/unit/init/AppRunner.test.ts | 355 ++++++++++++++++++++++++ test/unit/init/CliRunner.test.ts | 308 -------------------- 5 files changed, 387 insertions(+), 315 deletions(-) rename src/init/{CliRunner.ts => AppRunner.ts} (83%) create mode 100644 test/unit/init/AppRunner.test.ts delete mode 100644 test/unit/init/CliRunner.test.ts diff --git a/bin/server.js b/bin/server.js index 67cc27c6f..32a79b07b 100755 --- a/bin/server.js +++ b/bin/server.js @@ -1,4 +1,4 @@ #!/usr/bin/env node // eslint-disable-next-line @typescript-eslint/naming-convention -const { CliRunner } = require('..'); -new CliRunner(process).run(); +const { AppRunner } = require('..'); +new AppRunner().runCli(process); diff --git a/src/index.ts b/src/index.ts index f4062d5cf..64c55f3e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ export * from './authorization/WebAclAuthorizer'; // Init export * from './init/AclInitializer'; -export * from './init/CliRunner'; +export * from './init/AppRunner'; export * from './init/ConfigPodInitializer'; export * from './init/Initializer'; export * from './init/LoggerInitializer'; diff --git a/src/init/CliRunner.ts b/src/init/AppRunner.ts similarity index 83% rename from src/init/CliRunner.ts rename to src/init/AppRunner.ts index dc0599288..769f0b70e 100644 --- a/src/init/CliRunner.ts +++ b/src/init/AppRunner.ts @@ -8,16 +8,32 @@ import { getLoggerFor } from '../logging/LogUtil'; import { absoluteFilePath, ensureTrailingSlash, joinFilePath } from '../util/PathUtil'; import type { Initializer } from './Initializer'; -export class CliRunner { +export class AppRunner { private readonly logger = getLoggerFor(this); /** - * Generic run function for starting the server from a given config + * Generic run function for starting the server from JavaScript for a given config. + * @param loaderProperties - Components.js loader properties. + * @param configFile - Path to the server config file. + * @param variableParams - Variables to pass into the config file. + */ + public async run( + loaderProperties: IComponentsManagerBuilderOptions, + configFile: string, + variableParams: ConfigVariables, + ): Promise { + const variables = this.createVariables(variableParams); + const initializer = await this.createInitializer(loaderProperties, configFile, variables); + await initializer.handleSafe(); + } + + /** + * Generic run function for starting the server on the CLI from a given config * Made run to be non-async to lower the chance of unhandled promise rejection errors in the future. * @param args - Command line arguments. * @param stderr - Standard error stream. */ - public run({ + public runCli({ argv = process.argv, stderr = process.stderr, }: { @@ -103,7 +119,7 @@ export class CliRunner { /** * Translates command-line parameters into configuration variables */ - protected createVariables(params: Record): Record { + protected createVariables(params: ConfigVariables): Record { return { 'urn:solid-server:default:variable:baseUrl': params.baseUrl ? ensureTrailingSlash(params.baseUrl) : `http://localhost:${params.port}/`, @@ -132,3 +148,12 @@ export class CliRunner { return await componentsManager.instantiate(initializer, { variables }); } } + +export interface ConfigVariables { + loggingLevel: string; + port: number; + baseUrl?: string; + rootFilePath?: string; + sparqlEndpoint?: string; + podConfigJson?: string; +} diff --git a/test/unit/init/AppRunner.test.ts b/test/unit/init/AppRunner.test.ts new file mode 100644 index 000000000..b48d6cc19 --- /dev/null +++ b/test/unit/init/AppRunner.test.ts @@ -0,0 +1,355 @@ +import { ComponentsManager } from 'componentsjs'; +import { AppRunner } from '../../../src/init/AppRunner'; +import type { Initializer } from '../../../src/init/Initializer'; +import { joinFilePath } from '../../../src/util/PathUtil'; + +const initializer: jest.Mocked = { + handleSafe: jest.fn(), +} as any; + +const manager: jest.Mocked> = { + instantiate: jest.fn(async(): Promise => initializer), + configRegistry: { + register: jest.fn(), + }, +} as any; + +jest.mock('componentsjs', (): any => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + ComponentsManager: { + build: jest.fn(async(): Promise> => manager), + }, +})); + +jest.spyOn(process, 'cwd').mockReturnValue('/var/cwd'); +const error = jest.spyOn(console, 'error').mockImplementation(jest.fn()); +const write = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); +const exit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any); + +describe('AppRunner', (): void => { + afterEach((): void => { + jest.clearAllMocks(); + }); + + describe('run', (): void => { + it('starts the server with default settings.', async(): Promise => { + await new AppRunner().run( + { + mainModulePath: joinFilePath(__dirname, '../../../'), + dumpErrorState: true, + logLevel: 'info', + }, + joinFilePath(__dirname, '../../../config/config-default.json'), + { + port: 3000, + loggingLevel: 'info', + rootFilePath: '/var/cwd/', + podConfigJson: '/var/cwd/pod-config.json', + }, + ); + + expect(ComponentsManager.build).toHaveBeenCalledTimes(1); + expect(ComponentsManager.build).toHaveBeenCalledWith({ + dumpErrorState: true, + logLevel: 'info', + mainModulePath: joinFilePath(__dirname, '../../../'), + }); + expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); + expect(manager.configRegistry.register) + .toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/config-default.json')); + expect(manager.instantiate).toHaveBeenCalledTimes(1); + expect(manager.instantiate).toHaveBeenCalledWith( + 'urn:solid-server:default:Initializer', + { + variables: { + 'urn:solid-server:default:variable:port': 3000, + 'urn:solid-server:default:variable:baseUrl': 'http://localhost:3000/', + 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/', + 'urn:solid-server:default:variable:sparqlEndpoint': undefined, + 'urn:solid-server:default:variable:loggingLevel': 'info', + 'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json', + }, + }, + ); + expect(initializer.handleSafe).toHaveBeenCalledTimes(1); + expect(initializer.handleSafe).toHaveBeenCalledWith(); + }); + }); + + describe('runCli', (): void => { + it('starts the server with default settings.', async(): Promise => { + new AppRunner().runCli({ + argv: [ 'node', 'script' ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(ComponentsManager.build).toHaveBeenCalledTimes(1); + expect(ComponentsManager.build).toHaveBeenCalledWith({ + dumpErrorState: true, + logLevel: 'info', + mainModulePath: joinFilePath(__dirname, '../../../'), + }); + expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); + expect(manager.configRegistry.register) + .toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/config-default.json')); + expect(manager.instantiate).toHaveBeenCalledTimes(1); + expect(manager.instantiate).toHaveBeenCalledWith( + 'urn:solid-server:default:Initializer', + { + variables: { + 'urn:solid-server:default:variable:port': 3000, + 'urn:solid-server:default:variable:baseUrl': 'http://localhost:3000/', + 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/', + 'urn:solid-server:default:variable:sparqlEndpoint': undefined, + 'urn:solid-server:default:variable:loggingLevel': 'info', + 'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json', + }, + }, + ); + expect(initializer.handleSafe).toHaveBeenCalledTimes(1); + expect(initializer.handleSafe).toHaveBeenCalledWith(); + }); + + it('accepts abbreviated flags.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', + '-b', 'http://pod.example/', + '-c', 'myconfig.json', + '-f', '/root', + '-l', 'debug', + '-m', 'module/path', + '-p', '4000', + '-s', 'http://localhost:5000/sparql', + '--podConfigJson', '/different-path.json', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(ComponentsManager.build).toHaveBeenCalledTimes(1); + expect(ComponentsManager.build).toHaveBeenCalledWith({ + dumpErrorState: true, + logLevel: 'debug', + mainModulePath: '/var/cwd/module/path', + }); + expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); + expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); + expect(manager.instantiate).toHaveBeenCalledWith( + 'urn:solid-server:default:Initializer', + { + variables: { + 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', + 'urn:solid-server:default:variable:loggingLevel': 'debug', + 'urn:solid-server:default:variable:port': 4000, + 'urn:solid-server:default:variable:rootFilePath': '/root', + 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', + }, + }, + ); + }); + + it('accepts full flags.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', + '--baseUrl', 'http://pod.example/', + '--config', 'myconfig.json', + '--loggingLevel', 'debug', + '--mainModulePath', 'module/path', + '--port', '4000', + '--rootFilePath', 'root', + '--sparqlEndpoint', 'http://localhost:5000/sparql', + '--podConfigJson', '/different-path.json', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(ComponentsManager.build).toHaveBeenCalledTimes(1); + expect(ComponentsManager.build).toHaveBeenCalledWith({ + dumpErrorState: true, + logLevel: 'debug', + mainModulePath: '/var/cwd/module/path', + }); + expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); + expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); + expect(manager.instantiate).toHaveBeenCalledWith( + 'urn:solid-server:default:Initializer', + { + variables: { + 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', + 'urn:solid-server:default:variable:loggingLevel': 'debug', + 'urn:solid-server:default:variable:port': 4000, + 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/root', + 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', + }, + }, + ); + }); + + it('uses the default process.argv in case none are provided.', async(): Promise => { + const { argv } = process; + process.argv = [ + 'node', 'script', + '-b', 'http://pod.example/', + '-c', 'myconfig.json', + '-f', '/root', + '-l', 'debug', + '-m', 'module/path', + '-p', '4000', + '-s', 'http://localhost:5000/sparql', + '--podConfigJson', '/different-path.json', + ]; + + new AppRunner().runCli(); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(ComponentsManager.build).toHaveBeenCalledTimes(1); + expect(ComponentsManager.build).toHaveBeenCalledWith({ + dumpErrorState: true, + logLevel: 'debug', + mainModulePath: '/var/cwd/module/path', + }); + expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); + expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); + expect(manager.instantiate).toHaveBeenCalledWith( + 'urn:solid-server:default:Initializer', + { + variables: { + 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', + 'urn:solid-server:default:variable:loggingLevel': 'debug', + 'urn:solid-server:default:variable:port': 4000, + 'urn:solid-server:default:variable:rootFilePath': '/root', + 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', + }, + }, + ); + + process.argv = argv; + }); + + it('exits with output to stderr when instantiation fails.', async(): Promise => { + manager.instantiate.mockRejectedValueOnce(new Error('Fatal')); + new AppRunner().runCli({ + argv: [ 'node', 'script' ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(write).toHaveBeenCalledTimes(2); + expect(write).toHaveBeenNthCalledWith(1, + expect.stringMatching(/^Error: could not instantiate server from .*config-default\.json/u)); + expect(write).toHaveBeenNthCalledWith(2, + expect.stringMatching(/^Error: Fatal/u)); + + expect(exit).toHaveBeenCalledTimes(1); + expect(exit).toHaveBeenCalledWith(1); + }); + + it('exits without output to stderr when initialization fails.', async(): Promise => { + initializer.handleSafe.mockRejectedValueOnce(new Error('Fatal')); + new AppRunner().runCli({ + argv: [ 'node', 'script' ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(write).toHaveBeenCalledTimes(0); + + expect(exit).toHaveBeenCalledWith(1); + }); + + it('exits when unknown options are passed to the main executable.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', '--foo', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(error).toHaveBeenCalledWith('Unknown option: "foo"'); + expect(exit).toHaveBeenCalledTimes(1); + expect(exit).toHaveBeenCalledWith(1); + }); + + it('exits when no value is passed to the main executable for an argument.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', '-s', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(error).toHaveBeenCalledWith('Missing value for argument "s"'); + expect(exit).toHaveBeenCalledTimes(1); + expect(exit).toHaveBeenCalledWith(1); + }); + + it('exits when unknown parameters are passed to the main executable.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + // There seems to be an issue with yargs where the first and last `"` are missing. + expect(error).toHaveBeenCalledWith('Unsupported arguments: foo", "bar", "foo.txt", "bar.txt'); + expect(exit).toHaveBeenCalledTimes(1); + expect(exit).toHaveBeenCalledWith(1); + }); + + it('exits when multiple values for a parameter are passed.', async(): Promise => { + new AppRunner().runCli({ + argv: [ + 'node', 'script', '-ll', + ], + }); + + // Wait until initializer has been called, because we can't await AppRunner.run. + await new Promise((resolve): void => { + setImmediate(resolve); + }); + + expect(error).toHaveBeenCalledWith('Multiple values were provided for: "l", [info,info]'); + expect(exit).toHaveBeenCalledTimes(1); + expect(exit).toHaveBeenCalledWith(1); + }); + }); +}); diff --git a/test/unit/init/CliRunner.test.ts b/test/unit/init/CliRunner.test.ts deleted file mode 100644 index bbb57145a..000000000 --- a/test/unit/init/CliRunner.test.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { ComponentsManager } from 'componentsjs'; -import { CliRunner } from '../../../src/init/CliRunner'; -import type { Initializer } from '../../../src/init/Initializer'; -import { joinFilePath } from '../../../src/util/PathUtil'; - -const initializer: jest.Mocked = { - handleSafe: jest.fn(), -} as any; - -const manager: jest.Mocked> = { - instantiate: jest.fn(async(): Promise => initializer), - configRegistry: { - register: jest.fn(), - }, -} as any; - -jest.mock('componentsjs', (): any => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - ComponentsManager: { - build: jest.fn(async(): Promise> => manager), - }, -})); - -jest.spyOn(process, 'cwd').mockReturnValue('/var/cwd'); -const error = jest.spyOn(console, 'error').mockImplementation(jest.fn()); -const write = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); -const exit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any); - -describe('CliRunner', (): void => { - afterEach((): void => { - jest.clearAllMocks(); - }); - - it('starts the server with default settings.', async(): Promise => { - new CliRunner().run({ - argv: [ 'node', 'script' ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(ComponentsManager.build).toHaveBeenCalledTimes(1); - expect(ComponentsManager.build).toHaveBeenCalledWith({ - dumpErrorState: true, - logLevel: 'info', - mainModulePath: joinFilePath(__dirname, '../../../'), - }); - expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); - expect(manager.configRegistry.register) - .toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/config-default.json')); - expect(manager.instantiate).toHaveBeenCalledTimes(1); - expect(manager.instantiate).toHaveBeenCalledWith( - 'urn:solid-server:default:Initializer', - { - variables: { - 'urn:solid-server:default:variable:port': 3000, - 'urn:solid-server:default:variable:baseUrl': 'http://localhost:3000/', - 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/', - 'urn:solid-server:default:variable:sparqlEndpoint': undefined, - 'urn:solid-server:default:variable:loggingLevel': 'info', - 'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json', - }, - }, - ); - expect(initializer.handleSafe).toHaveBeenCalledTimes(1); - expect(initializer.handleSafe).toHaveBeenCalledWith(); - }); - - it('accepts abbreviated flags.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', - '-b', 'http://pod.example/', - '-c', 'myconfig.json', - '-f', '/root', - '-l', 'debug', - '-m', 'module/path', - '-p', '4000', - '-s', 'http://localhost:5000/sparql', - '--podConfigJson', '/different-path.json', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(ComponentsManager.build).toHaveBeenCalledTimes(1); - expect(ComponentsManager.build).toHaveBeenCalledWith({ - dumpErrorState: true, - logLevel: 'debug', - mainModulePath: '/var/cwd/module/path', - }); - expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); - expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); - expect(manager.instantiate).toHaveBeenCalledWith( - 'urn:solid-server:default:Initializer', - { - variables: { - 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', - 'urn:solid-server:default:variable:loggingLevel': 'debug', - 'urn:solid-server:default:variable:port': 4000, - 'urn:solid-server:default:variable:rootFilePath': '/root', - 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', - 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', - }, - }, - ); - }); - - it('accepts full flags.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', - '--baseUrl', 'http://pod.example/', - '--config', 'myconfig.json', - '--loggingLevel', 'debug', - '--mainModulePath', 'module/path', - '--port', '4000', - '--rootFilePath', 'root', - '--sparqlEndpoint', 'http://localhost:5000/sparql', - '--podConfigJson', '/different-path.json', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(ComponentsManager.build).toHaveBeenCalledTimes(1); - expect(ComponentsManager.build).toHaveBeenCalledWith({ - dumpErrorState: true, - logLevel: 'debug', - mainModulePath: '/var/cwd/module/path', - }); - expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); - expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); - expect(manager.instantiate).toHaveBeenCalledWith( - 'urn:solid-server:default:Initializer', - { - variables: { - 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', - 'urn:solid-server:default:variable:loggingLevel': 'debug', - 'urn:solid-server:default:variable:port': 4000, - 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/root', - 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', - 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', - }, - }, - ); - }); - - it('uses the default process.argv in case none are provided.', async(): Promise => { - const { argv } = process; - process.argv = [ - 'node', 'script', - '-b', 'http://pod.example/', - '-c', 'myconfig.json', - '-f', '/root', - '-l', 'debug', - '-m', 'module/path', - '-p', '4000', - '-s', 'http://localhost:5000/sparql', - '--podConfigJson', '/different-path.json', - ]; - - new CliRunner().run(); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(ComponentsManager.build).toHaveBeenCalledTimes(1); - expect(ComponentsManager.build).toHaveBeenCalledWith({ - dumpErrorState: true, - logLevel: 'debug', - mainModulePath: '/var/cwd/module/path', - }); - expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); - expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); - expect(manager.instantiate).toHaveBeenCalledWith( - 'urn:solid-server:default:Initializer', - { - variables: { - 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', - 'urn:solid-server:default:variable:loggingLevel': 'debug', - 'urn:solid-server:default:variable:port': 4000, - 'urn:solid-server:default:variable:rootFilePath': '/root', - 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', - 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', - }, - }, - ); - - process.argv = argv; - }); - - it('exits with output to stderr when instantiation fails.', async(): Promise => { - manager.instantiate.mockRejectedValueOnce(new Error('Fatal')); - new CliRunner().run({ - argv: [ 'node', 'script' ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(write).toHaveBeenCalledTimes(2); - expect(write).toHaveBeenNthCalledWith(1, - expect.stringMatching(/^Error: could not instantiate server from .*config-default\.json/u)); - expect(write).toHaveBeenNthCalledWith(2, - expect.stringMatching(/^Error: Fatal/u)); - - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits without output to stderr when initialization fails.', async(): Promise => { - initializer.handleSafe.mockRejectedValueOnce(new Error('Fatal')); - new CliRunner().run({ - argv: [ 'node', 'script' ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(write).toHaveBeenCalledTimes(0); - - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits when unknown options are passed to the main executable.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', '--foo', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(error).toHaveBeenCalledWith('Unknown option: "foo"'); - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits when no value is passed to the main executable for an argument.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', '-s', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(error).toHaveBeenCalledWith('Missing value for argument "s"'); - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits when unknown parameters are passed to the main executable.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - // There seems to be an issue with yargs where the first and last `"` are missing. - expect(error).toHaveBeenCalledWith('Unsupported arguments: foo", "bar", "foo.txt", "bar.txt'); - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits when multiple values for a parameter are passed.', async(): Promise => { - new CliRunner().run({ - argv: [ - 'node', 'script', '-ll', - ], - }); - - // Wait until initializer has been called, because we can't await CliRunner.run. - await new Promise((resolve): void => { - setImmediate(resolve); - }); - - expect(error).toHaveBeenCalledWith('Multiple values were provided for: "l", [info,info]'); - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); -});