From 2324c4244534af4fcc64683526076bb7e0bec91e Mon Sep 17 00:00:00 2001 From: Artem Abzanov Date: Sat, 27 Aug 2022 14:55:51 +0300 Subject: [PATCH 1/3] feat: complete cli code and add bin field in package.json build: remove npmignore and create files field in package.json build: update hook to build cjs docs: update badge style docs: add node version badge --- .husky/post-commit | 2 +- .npmignore | 9 -- README.md | 1 + bin/index.js | 23 ++++ package.json | 16 ++- src/cli.js | 294 ++++++++++++++++++++++++++++++++++++++++++++ src/many.js | 7 +- tests/_main.js | 28 ++++- tests/index.js | 4 +- types/src/cli.d.ts | 32 +++++ types/src/many.d.ts | 3 + 11 files changed, 403 insertions(+), 16 deletions(-) delete mode 100644 .npmignore create mode 100755 bin/index.js create mode 100644 src/cli.js create mode 100644 types/src/cli.d.ts diff --git a/.husky/post-commit b/.husky/post-commit index ce2925a..612c786 100755 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -4,5 +4,5 @@ npm run build git add cjs/index.js -git commit -a -m "build: build cjs" +git status | grep modified | grep 'cjs/index.js' && git commit -a -m "build: build cjs" diff --git a/.npmignore b/.npmignore deleted file mode 100644 index de757d0..0000000 --- a/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -.idea -node_modules -.DS_Store -.husky -.gitignore -.git -tests -cjs/test.js -sandbox diff --git a/README.md b/README.md index a39b896..6ebc798 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![tests](https://img.shields.io/github/workflow/status/JerryCauser/tcp-exists/tests?label=tests&logo=github)](https://github.com/JerryCauser/tcp-exists/actions/workflows/tests.yml) [![LGTM Grade](https://img.shields.io/lgtm/grade/javascript/github/JerryCauser/tcp-exists)](https://lgtm.com/projects/g/JerryCauser/tcp-exists) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) +[![node-current](https://img.shields.io/node/v/tcp-exists)](https://nodejs.org) [![GitHub](https://img.shields.io/github/license/JerryCauser/tcp-exists)](https://github.com/JerryCauser/tcp-exists/blob/master/LICENSE) Check if some tcp endpoint (or many) exists. Can be used as a port scanner diff --git a/bin/index.js b/bin/index.js new file mode 100755 index 0000000..549b165 --- /dev/null +++ b/bin/index.js @@ -0,0 +1,23 @@ +#!/usr/bin/env node + +import events from 'node:events' +import { cmd } from '../src/cli.js' + +const ac = new AbortController() +events.setMaxListeners(0, ac.signal) + +cmd(process.argv.slice(2), ac) + .then(() => { + process.exit(0) + }) + .catch((error) => { + console.error(error) + process.exit(1) + }) + +process.on('exit', () => ac.abort()) +process.on('SIGINT', () => ac.abort()) +process.on('SIGUSR1', () => ac.abort()) +process.on('SIGUSR2', () => ac.abort()) +process.on('uncaughtException', () => ac.abort()) +process.on('SIGTERM', () => ac.abort()) diff --git a/package.json b/package.json index 93a2dd0..61f3b0f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "1.4.6", "description": "Small and fast functions to check if some tcp endpoint exists", "type": "module", + "bin": { + "tcp-exists": "./bin/index.js" + }, "main": "cjs/index.js", "module": "index.js", "exports": { @@ -15,7 +18,7 @@ "node": ">=16.0.0" }, "scripts": { - "build": "esbuild index.js --bundle --platform=node --target=node16 --outfile='./cjs/index.js'", + "build": "esbuild index.js --bundle --platform=node --format=cjs --target=node16 --outfile='./cjs/index.js'", "release:patch": "standard-version --release-as patch", "release:minor": "standard-version --release-as minor", "release:major": "standard-version --release-as major", @@ -65,5 +68,16 @@ "cjs/index.js", "cjs/constants.js", "types" + ], + "files": [ + "/bin/index.js", + "/cjs/index.js", + "/cjs/package.json", + "/src", + "/types", + "index.js", + "LICENSE", + "CHANGELOG.md", + "README.md" ] } diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..32b10f1 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,294 @@ +import fs from 'node:fs' +import tcpExistsMany, { DEFAULT_TIMEOUT, DEFAULT_CHUNK_SIZE } from './many.js' + +const DEFAULT_DELIMITER = '\n' + +const packageJSON = JSON.parse( + fs.readFileSync(new URL('../package.json', import.meta.url)).toString() +) + +/** + * @param {string} str + * @return {string} + */ +const red = (str) => `\x1b[31m${str}\x1b[0m` +/** + * @param {string} str + * @return {string} + */ +const green = (str) => `\x1b[32m${str}\x1b[0m` + +const DEFAULT_PORTS_DICT = { + 21: 'ftp', + 22: 'ssh', + 23: 'telnet', + 25: 'smtp', + 53: 'domain name system', + 80: 'http', + 110: 'pop3', + 111: 'rpcbind', + 135: 'msrpc', + 139: 'netbios-ssn', + 143: 'imap', + 443: 'https', + 445: 'microsoft-ds', + 993: 'imaps', + 995: 'pop3s', + 1723: 'pptp', + 3306: 'mysql', + 3389: 'ms-wbt-server', + 5900: 'vnc', + 8080: 'http-proxy' +} +export const DEFAULT_PORTS = + process.env.DEFAULT_PORT || Object.keys(DEFAULT_PORTS_DICT).join(',') + +/** + * + * @param {HelpOptions} + * @return {string} + */ +const getHelpText = ({ + name, + version, + description, + engines, + homepage, + license +}) => { + name = red(name) + const o = { + v: red('-v'), + verbose: red('--verbose'), + cl: red('-cl'), + colourless: red('--colourless'), + colorless: red('--colorless'), + t: red('-t'), + timeout: red('--timeout'), + s: red('-s'), + size: red('--size'), + d: red('-d'), + delimiter: red('--delimiter'), + h: red('-h'), + help: red('--help') + } + + const v = { + number: green('number'), + string: green('string'), + size: green(DEFAULT_CHUNK_SIZE.toString()), + timeout: green(DEFAULT_TIMEOUT.toString()), + delimiter: green(DEFAULT_DELIMITER.replace(/\n/gm, '\\n')) + } + + return ` +${name} v${version} + ${description} + + +${red('Supported environment')} + ${Object.entries(engines) + .map(([k, v]) => k + ': ' + v) + .join(';\n ')} + + +${red('Usage')} + ${name} ${green('example.com')} + will scan DEFAULT_PORTS for given host + + ${name} ${green('example.com')} ${green('example2.com')} + will scan DEFAULT_PORTS for all given hosts + + ${name} ${green('example.com:80,443')} + will scan 80 and 443 ports for given host + + ${name} ${green('example.com:8000-8999')} + will scan [8000...8999] ports for given host + + ${name} ${green('example.com:80,8000-8999,443')} + Ports declaration can be combined. + Will scan 80, 443, [8000...8999] ports for given host + + ${name} ${green('example.com:1-65535')} ${o.v} ${o.cl} ${o.t} ${green( + '300' + )} ${o.s} ${green('2000')} ${o.d} ${green("'\\n'")} + will scan all ports for given host with next options: + prints all results (${o.v}) + without colors (${o.cl}) + with timeout 300ms (${o.t} ${green('300')}) + with chunk size 2000 (${o.s} ${green('2000')}) + and print each result on new line (${o.d} ${green("'\\n'")}) + + +${red('Arguments')} + ${o.v}, ${o.verbose} + By default, ${name} will print only positive results. + If ${o.v} is passed then it will print all results. + + ${o.cl}, ${o.colourless}, ${o.colorless} + By default, ${name} uses colored output. + You can disable it by by passing this argument. + Useful for terminal piping. + + ${o.t}, ${o.timeout} ${v.number} + Changes default timeout (${v.timeout} ms) to await if endpoint exists. + + ${o.s}, ${o.size} ${v.number} + Changes default Chunk Size (${v.size}) to scan in one timeout period. + + ${o.d}, ${o.delimiter} ${v.string} + Changes default delimiter ('${v.delimiter}') between results. + + ${o.h}, ${o.help} + Prints help. + + +${red('Environment')} + DEFAULT_PORTS ${v.string} + By default, it is a list of the most popular ports separated by commas. + ${green(DEFAULT_PORTS)} + + +${red('Homepage')} ${homepage} +${red('License')} ${license} +` +} + +/** + * @param {string[]} args + * @param {AbortController} ac + * @return {Promise} + */ +export async function cmd (args, ac) { + const parsed = parseArgs(args) + const { help, delimiter, timeout, chunkSize, verbose, colorless, endpoints } = + parsed + + if (help) { + process.stdout.write(getHelpText(packageJSON)) + + return + } + + const generator = tcpExistsMany([...endpoints], { + timeout, + chunkSize, + returnOnlyExisted: !verbose, + signal: ac?.signal + }) + + for await (const chunkResult of generator) { + for (const result of chunkResult) { + process.stdout.write(formatOneResult(result, delimiter, colorless)) + } + } +} + +/** + * @param {string[]} args + * @return {ParsedArguments} + */ +export function parseArgs (args) { + args = args + .map((arg) => arg.toString().split('=')) + .flat(1) + .filter(Boolean) + + const options = { + help: false, + colorless: false, + verbose: false, + delimiter: DEFAULT_DELIMITER, + chunkSize: undefined, + timeout: undefined, + endpoints: new Set() + } + + for (let i = 0; i < args.length; ++i) { + const arg = args[i] + + if (arg === '--help' || arg === '-h') { + options.help = true + + break + } else if (arg === '-d' || arg === '--delimiter') { + options.delimiter = args[++i] ?? '' + + continue + } else if ( + arg === '-cl' || + arg === '--colourless' || + arg === '--colorless' + ) { + options.colorless = true + + continue + } else if (arg === '-v' || arg === '--verbose') { + options.verbose = true + + continue + } else if (arg === '-t' || arg === '--timeout') { + options.timeout = parseInt(args[++i], 10) + + if (isNaN(options.timeout)) options.timeout = undefined + + continue + } else if (arg === '-s' || arg === '--size') { + options.chunkSize = parseInt(args[++i], 10) + + if (isNaN(options.chunkSize)) options.chunkSize = undefined + + continue + } + + let [host, ports] = arg.split(':') + host = host?.trim().toLowerCase() + ports = ports?.trim().toLowerCase() || DEFAULT_PORTS + + if (!host) continue + + for (const portChunk of ports.split(',')) { + if (portChunk.includes('-')) { + let [fromPort, toPort] = portChunk.split('-') + fromPort = Math.max(1, Math.abs(parseInt(fromPort, 10))) + toPort = Math.min(65535, Math.abs(parseInt(toPort, 10))) + + if (isNaN(fromPort) || isNaN(toPort)) continue + + if (fromPort > toPort) { + [fromPort, toPort] = [toPort, fromPort] + } + + for (let p = fromPort; p <= toPort; ++p) { + options.endpoints.add([host, p]) + } + } else { + options.endpoints.add([host, portChunk]) + } + } + } + + return options +} + +/** + * @param {[host:string, port:string|port, exist:boolean]} endpointResult + * @param {string} [delimiter=''] + * @param {boolean} [colorless=false] + * @return {string} + */ +export function formatOneResult ( + endpointResult, + delimiter = '', + colorless = false +) { + const [host, port, exist] = endpointResult + + let str = `${host}:${port}\t${exist ? 'on' : 'off'}` + delimiter + + if (!colorless) { + str = exist ? green(str) : red(str) + } + + return str +} diff --git a/src/many.js b/src/many.js index 6f13daf..de1adaa 100644 --- a/src/many.js +++ b/src/many.js @@ -1,5 +1,8 @@ import tcpExistsChunk from './chunk.js' +export const DEFAULT_CHUNK_SIZE = 1400 +export const DEFAULT_TIMEOUT = 160 + /** * Attention: passed list will be empty after execution * @param {[string, string|number][]} endpoints @@ -12,8 +15,8 @@ import tcpExistsChunk from './chunk.js' */ async function * tcpExistsMany (endpoints, options) { const { - chunkSize = 1400, - timeout = 160, + chunkSize = DEFAULT_CHUNK_SIZE, + timeout = DEFAULT_TIMEOUT, returnOnlyExisted = true, signal } = options || {} diff --git a/tests/_main.js b/tests/_main.js index ed751d2..56b38c3 100644 --- a/tests/_main.js +++ b/tests/_main.js @@ -1,8 +1,7 @@ -/* global AbortController */ import net from 'node:net' import assert from 'node:assert' -async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne }) { +async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne, cli }) { const PORT_FROM = 15400 const PORT_TO = 15500 const servers = [] @@ -157,6 +156,29 @@ async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne }) { console.log('tcpExistsMany Abort tests passed') } + async function testCLI () { + /** cli.parseArgs + * test help option + * test verbose option + * test size option + * test timeout option + * test delimiter option + * test colorless option + * test 1 host + * test 2 hosts + * test 1 port + * test 3 port with commas + * test ports range + * test combine of 2 hosts with mixed ports + */ + /** cli.formatOneResult + * just test format of text + */ + /** cli.cmd + * test print to console verbose true/false with delimiter='\n'|'; ' + */ + } + async function end () { await Promise.all( servers.map((server) => new Promise((resolve) => server.close(resolve))) @@ -172,6 +194,8 @@ async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne }) { await testOneAbort() await testManyAbort() + if (cli) await testCLI() + console.log('All tests are passed') await end() } diff --git a/tests/index.js b/tests/index.js index 02a1d78..835e79b 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,10 +1,12 @@ import { tcpExistsOne, tcpExistsChunk, tcpExistsMany } from '../index.js' +import * as cli from '../src/cli.js' import _main from './_main.js' _main({ tcpExistsOne, tcpExistsChunk, - tcpExistsMany + tcpExistsMany, + cli }).catch((e) => { console.error(e) process.exit(1) diff --git a/types/src/cli.d.ts b/types/src/cli.d.ts new file mode 100644 index 0000000..11a138a --- /dev/null +++ b/types/src/cli.d.ts @@ -0,0 +1,32 @@ +export { cmd, parseArgs, formatOneResult, DEFAULT_PORTS } + +export interface HelpOptions { + name: string + version: string + description: string + engines: { + [name:string]: string + } + homepage: string + license: string +} + +export interface ParsedArguments { + colorless: boolean + help: boolean + delimiter: string + endpoints: Set<[host:string, port:string|number]> + chunkSize?: number, + timeout?: number, + verbose: boolean +} + +declare const DEFAULT_PORTS:string + +declare function cmd (args: string[], ac: AbortController): Promise +declare function parseArgs (args: string[]): ParsedArguments +declare function formatOneResult (endpointResult:[host:string, port:string|number, result:boolean], delimiter:string, colorless:boolean):string + +declare function getHelpText (options:HelpOptions):string +declare function red (string:string):string +declare function green (string:string):string diff --git a/types/src/many.d.ts b/types/src/many.d.ts index c8eaf04..06f1342 100644 --- a/types/src/many.d.ts +++ b/types/src/many.d.ts @@ -1,5 +1,8 @@ export default tcpExistsMany +export const DEFAULT_CHUNK_SIZE:number +export const DEFAULT_TIMEOUT:number + declare function tcpExistsMany( endpoints: [string, string | number][], options?: { From 626586898d190ae56aa02d93b96b78b7516a21c8 Mon Sep 17 00:00:00 2001 From: Artem Abzanov Date: Sat, 27 Aug 2022 14:55:51 +0300 Subject: [PATCH 2/3] build: build cjs --- cjs/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cjs/index.js b/cjs/index.js index 2919f45..1e9cfcb 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -76,10 +76,12 @@ async function tcpExistsChunk(endpoints, options) { var chunk_default = tcpExistsChunk; // src/many.js +var DEFAULT_CHUNK_SIZE = 1400; +var DEFAULT_TIMEOUT = 160; async function* tcpExistsMany(endpoints, options) { const { - chunkSize = 1400, - timeout = 160, + chunkSize = DEFAULT_CHUNK_SIZE, + timeout = DEFAULT_TIMEOUT, returnOnlyExisted = true, signal } = options || {}; From 0dbc093b1191e2346d14bf4c5f5ec28880f66b6d Mon Sep 17 00:00:00 2001 From: Artem Abzanov Date: Sun, 28 Aug 2022 09:24:05 +0300 Subject: [PATCH 3/3] feat: add support for nonTTY mode (useful for shell piping) fix: trailing % fix: special characters delimiters test: add unit tests for cli module --- src/cli.js | 32 ++++--- tests/_main.js | 223 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 222 insertions(+), 33 deletions(-) diff --git a/src/cli.js b/src/cli.js index 32b10f1..39a3cee 100644 --- a/src/cli.js +++ b/src/cli.js @@ -11,12 +11,12 @@ const packageJSON = JSON.parse( * @param {string} str * @return {string} */ -const red = (str) => `\x1b[31m${str}\x1b[0m` +const red = (str) => (process.stdout.isTTY ? `\x1b[31m${str}\x1b[0m` : str) /** * @param {string} str * @return {string} */ -const green = (str) => `\x1b[32m${str}\x1b[0m` +const green = (str) => (process.stdout.isTTY ? `\x1b[32m${str}\x1b[0m` : str) const DEFAULT_PORTS_DICT = { 21: 'ftp', @@ -94,16 +94,16 @@ ${red('Supported environment')} ${red('Usage')} ${name} ${green('example.com')} - will scan DEFAULT_PORTS for given host + Will scan DEFAULT_PORTS for given host ${name} ${green('example.com')} ${green('example2.com')} - will scan DEFAULT_PORTS for all given hosts + Will scan DEFAULT_PORTS for all given hosts ${name} ${green('example.com:80,443')} - will scan 80 and 443 ports for given host + Will scan 80 and 443 ports for given host ${name} ${green('example.com:8000-8999')} - will scan [8000...8999] ports for given host + Will scan [8000...8999] ports for given host ${name} ${green('example.com:80,8000-8999,443')} Ports declaration can be combined. @@ -126,9 +126,8 @@ ${red('Arguments')} If ${o.v} is passed then it will print all results. ${o.cl}, ${o.colourless}, ${o.colorless} - By default, ${name} uses colored output. + By default, ${name} uses colored output in TTY mode. You can disable it by by passing this argument. - Useful for terminal piping. ${o.t}, ${o.timeout} ${v.number} Changes default timeout (${v.timeout} ms) to await if endpoint exists. @@ -141,6 +140,7 @@ ${red('Arguments')} ${o.h}, ${o.help} Prints help. + In TTY mode if zero endpoints recognised also will print help. ${red('Environment')} @@ -164,7 +164,7 @@ export async function cmd (args, ac) { const { help, delimiter, timeout, chunkSize, verbose, colorless, endpoints } = parsed - if (help) { + if (help || (process.stdout.isTTY && endpoints.size === 0)) { process.stdout.write(getHelpText(packageJSON)) return @@ -182,6 +182,7 @@ export async function cmd (args, ac) { process.stdout.write(formatOneResult(result, delimiter, colorless)) } } + process.stdout.write('\n') } /** @@ -214,6 +215,15 @@ export function parseArgs (args) { } else if (arg === '-d' || arg === '--delimiter') { options.delimiter = args[++i] ?? '' + if (/\\/.test(options.delimiter)) { + options.delimiter = options.delimiter + .replace(/\\t/gm, '\t') + .replace(/\\v/gm, '\v') + .replace(/\\f/gm, '\f') + .replace(/\\n/gm, '\n') + .replace(/\\r/gm, '\r') + } + continue } else if ( arg === '-cl' || @@ -273,13 +283,13 @@ export function parseArgs (args) { /** * @param {[host:string, port:string|port, exist:boolean]} endpointResult - * @param {string} [delimiter=''] + * @param {string} [delimiter] * @param {boolean} [colorless=false] * @return {string} */ export function formatOneResult ( endpointResult, - delimiter = '', + delimiter = DEFAULT_DELIMITER, colorless = false ) { const [host, port, exist] = endpointResult diff --git a/tests/_main.js b/tests/_main.js index 56b38c3..bda1fd4 100644 --- a/tests/_main.js +++ b/tests/_main.js @@ -156,27 +156,202 @@ async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne, cli }) { console.log('tcpExistsMany Abort tests passed') } - async function testCLI () { - /** cli.parseArgs - * test help option - * test verbose option - * test size option - * test timeout option - * test delimiter option - * test colorless option - * test 1 host - * test 2 hosts - * test 1 port - * test 3 port with commas - * test ports range - * test combine of 2 hosts with mixed ports - */ - /** cli.formatOneResult - * just test format of text - */ - /** cli.cmd - * test print to console verbose true/false with delimiter='\n'|'; ' - */ + async function testCLIParser () { + const { DEFAULT_PORTS } = cli + const DEFAULT_PORTS_LIST = DEFAULT_PORTS.split(',') + + const { help: helpShort } = cli.parseArgs(['-h']) + const { help: helpLong } = cli.parseArgs(['--help']) + + assert.strictEqual(helpShort, true, '7.1.1 cli parser -h not parsed') + assert.strictEqual(helpLong, true, '7.1.2 cli parser --help not parsed') + + const { verbose: verbShort } = cli.parseArgs(['-v']) + const { verbose: verbLong } = cli.parseArgs(['--verbose']) + + assert.strictEqual(verbShort, true, '7.2.1 cli parser -v not parsed') + assert.strictEqual(verbLong, true, '7.2.2 cli parser --verbose not parsed') + + const { colorless: clShort } = cli.parseArgs(['-cl']) + const { colorless: clLong } = cli.parseArgs(['--colorless']) + const { colorless: clGentle } = cli.parseArgs(['--colourless']) + + assert.strictEqual(clShort, true, '7.3.1 cli parser -cl not parsed') + assert.strictEqual(clLong, true, '7.3.2 cli parser --colorless not parsed') + assert.strictEqual( + clGentle, + true, + '7.3.3 cli parser --colourless not parsed' + ) + + const sizes = [~~(Math.random() * 2000), ~~(Math.random() * 2000)] + const { chunkSize: sizeShort } = cli.parseArgs(['-s', sizes[0].toString()]) + const { chunkSize: sizeLong } = cli.parseArgs([ + '--size', + sizes[1].toString() + ]) + + assert.strictEqual(sizeShort, sizes[0], '7.4.1 cli parser -s not parsed') + assert.strictEqual( + sizeLong, + sizes[1], + '7.4.2 cli parser --size not parsed' + ) + + const timeouts = [~~(Math.random() * 2000), ~~(Math.random() * 2000)] + const { timeout: timeShort } = cli.parseArgs([ + '-t', + timeouts[0].toString() + ]) + const { timeout: timeLong } = cli.parseArgs([ + '--timeout', + timeouts[1].toString() + ]) + + const delimiters = [Math.random().toString(), Math.random().toString()] + assert.strictEqual( + timeShort, + timeouts[0], + '7.5.1 cli parser -t not parsed' + ) + assert.strictEqual( + timeLong, + timeouts[1], + '7.5.2 cli parser --timeout not parsed' + ) + + const { delimiter: delimShort } = cli.parseArgs(['-d', delimiters[0]]) + const { delimiter: delimLong } = cli.parseArgs([ + '--delimiter', + delimiters[1] + ]) + + assert.strictEqual( + delimShort, + delimiters[0], + '7.6.1 cli parser -d not parsed' + ) + assert.strictEqual( + delimLong, + delimiters[1], + '7.6.2 cli parser --delimiter not parsed' + ) + + const toString = (iterable) => [...iterable].sort().join(';') + const generateGoldEndpoints = (host, ports) => + toString(ports.map((p) => [host, p])) + const hosts = ['example.com', 'example2.com'] + const ports = [8090, 8091, 8092] + + const { endpoints: endpointsOne } = cli.parseArgs([hosts[0]]) + + assert.strictEqual( + toString(endpointsOne), + generateGoldEndpoints(hosts[0], DEFAULT_PORTS_LIST), + '7.7.1 cli parser test 1 host with defaults' + ) + + const { endpoints: endpointsTwo } = cli.parseArgs([ + hosts[0] + ':' + ports[0], + hosts[1] + ':' + ports[0] + '-' + ports[2] + ]) + + assert.strictEqual( + toString(endpointsTwo), + toString( + [ + generateGoldEndpoints(hosts[0], [ports[0]]), + generateGoldEndpoints(hosts[1], ports) + ] + .join(';') + .split(';') + .sort() + ), + '7.7.2 cli parser test 2 hosts: first with 1 port, second with 3 ports in range' + ) + + const { endpoints: endpointsComma } = cli.parseArgs([ + hosts[1] + ':' + ports.join(',') + ]) + + assert.strictEqual( + toString(endpointsComma), + generateGoldEndpoints(hosts[1], ports), + '7.7.3 cli parser test 3 ports comma separated' + ) + + console.log('cli.parser tests passed') + } + + async function testCLIFormatter () { + const host = 'example.com' + const port = '2134' + const positiveResult = cli.formatOneResult([host, port, true], '', true) + const negativeResult = cli.formatOneResult( + [host, parseInt(port, 10), false], + ';\n', + true + ) + + assert.strictEqual( + positiveResult, + `${host}:${port}\ton`, + '8.1 cli formatter test positive' + ) + + assert.strictEqual( + negativeResult, + `${host}:${port}\toff;\n`, + '8.2 cli formatter test negative' + ) + + console.log('cli.formatter tests passed') + } + + async function testCLICmd () { + const data = [] + process.stdout.originWrite = process.stdout.write + process.stdout.write = (...args) => { + data.push(...args) + } + + try { + await cli.cmd([ + '-cl', + '-d', + '\\n', + '-t', + '15', + `localhost:${PORT_FROM - 1},${PORT_FROM}` + ]) // should print only positive result + + assert.strictEqual( + data[0], + `localhost:${PORT_FROM}\ton\n`, + '9.1 cli cmd only positive with \\n not correct' + ) + + data.length = 0 + + await cli.cmd([ + '-cl', + '-d', + '; ', + '-v', + '-t', + '15', + `localhost:${PORT_FROM - 1}` + ]) // should print negative result + assert.strictEqual( + data[0], + `localhost:${PORT_FROM - 1}\toff; `, + '9.2 cli cmd all with delimiter="; " not correct' + ) + } finally { + process.stdout.write = process.stdout.originWrite + } + + console.log('cli.cmd tests passed') } async function end () { @@ -194,7 +369,11 @@ async function _main ({ tcpExistsChunk, tcpExistsMany, tcpExistsOne, cli }) { await testOneAbort() await testManyAbort() - if (cli) await testCLI() + if (cli) { + await testCLIParser() + await testCLIFormatter() + await testCLICmd() + } console.log('All tests are passed') await end()