diff --git a/.eslintrc.json b/.eslintrc.json index 02bb9db..69af2a5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,23 +1,14 @@ { - "env": { - "browser": true, - "es2021": true - }, "parser": "@typescript-eslint/parser", - "extends": [ - "airbnb-base", - "airbnb-typescript", - "prettier", - "plugin:@typescript-eslint/recommended" - ], "parserOptions": { - "ecmaVersion": 12, + "project": "./tsconfig.json", + "ecmaVersion": 2021, "sourceType": "module" }, + "plugins": ["@typescript-eslint", "promise"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "rules": { - "prettier/prettier": "error", - "no-shadow": "off", - "@typescript-eslint/no-shadow": "error" - }, - "plugins": ["prettier", "@typescript-eslint"] + "@typescript-eslint/dot-notation": "error", + "no-async-promise-executor": "error" + } } diff --git a/src/app.ts b/src/app.ts index 1ac1f71..c1a4748 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,18 +1,16 @@ import { cli, CommandData } from './cli' import { process as processCLI } from './process' -cli() - .then((cmd: CommandData) => { - processCLI(cmd) - .then(() => { - console.error('END') - process.exit() - }) - .catch((e) => { - console.error('An error occurred while processing...') - process.exit(1) - }) - }) - .catch((e: Error) => { - throw new Error(`Cli input error : ${e}`) - }) +const run = async (): Promise => { + try { + const cmd: CommandData = await cli() + await processCLI(cmd) + console.error('END') + process.exit() + } catch (e) { + console.error('An error occurred while processing...') + process.exit(1) + } +} + +run() diff --git a/src/cli.ts b/src/cli.ts index ea5fff9..4fe90e0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -29,18 +29,11 @@ const DEFAULT_NB_PER_SECOND = 100 const DEFAULT_NB_SECONDS = 5 const DEFAULT_TIMEOUT_MS = 1000 -export const cli = async (): Promise => { - const cmd: CommandData = { - method: '', - url: '', - headers: {}, - bodyParams: [], - nbPerSecond: DEFAULT_NB_PER_SECOND, - nbSeconds: DEFAULT_NB_SECONDS, - timeout_ms: DEFAULT_TIMEOUT_MS, - } - - const baseResponse = await prompts([ +const promptBase = async (): Promise<{ + url: string + method: string +}> => { + return await prompts([ { type: 'text', name: 'url', @@ -53,98 +46,94 @@ export const cli = async (): Promise => { message: 'Chose the method.', choices: [ { title: 'GET', description: 'GET method', value: 'GET' }, - { title: 'POST', description: 'POST method', value: 'POST' }, - { title: 'PUT', description: 'PUT method', value: 'PUT' }, + { title: 'POST', description: 'POST method' }, ], - initial: 0, }, ]) - cmd.url = baseResponse.url - cmd.method = baseResponse.method - - // Add Headers section - const addHeader = async () => { - const headerCheckResponse = await prompts({ - type: 'confirm', - name: 'needsHeader', - message: 'Do you want to add a header ?', - initial: false, - }) +} - if (headerCheckResponse.needsHeader) { - const headerResponse = await prompts([ - { - type: 'text', - name: 'name', - message: 'Name of the header (ex: Authorization)', - initial: 'Authorization', - }, - { - type: 'text', - name: 'value', - message: 'Value of the header', - initial: 'Bearer xxxxxxxxxx', - }, - ]) - cmd.headers[headerResponse.name] = headerResponse.value - - await addHeader() - } +const promptHeaders = async (headers: Headers): Promise => { + const headerCheckResponse = await prompts({ + type: 'confirm', + name: 'needsHeader', + message: 'Do you want to add a header ?', + initial: false, + }) + + if (headerCheckResponse.needsHeader) { + const headerResponse = await prompts([ + { + type: 'text', + name: 'name', + message: 'Name of the header (ex: Authorization)', + initial: 'Authorization', + }, + { + type: 'text', + name: 'value', + message: 'Value of the header', + initial: 'Bearer xxxxxxxxxx', + }, + ]) + headers[headerResponse.name] = headerResponse.value + + await promptHeaders(headers) } - await addHeader() - - // Add body section - const addBody = async () => { - const bodyCheckResponse = await prompts({ - type: 'confirm', - name: 'needsBody', - message: 'Do you want to add a body to your request ?', - initial: ['POST', 'PUT', 'PATCH'].includes(baseResponse.method) - ? true - : false, - }) +} - if (bodyCheckResponse.needsBody) { - const headerResponse = await prompts({ - type: 'text', - name: 'body', - message: - 'Copy the body of the request here.\nIf you want dynamic parameters like random strings or increment integer, use the {{myParam}} syntax, they will be configured after.', - initial: '{"foo": "{{bar}}"}', +const promptBody = async (cmd: CommandData): Promise => { + const bodyCheckResponse = await prompts({ + type: 'confirm', + name: 'needsBody', + message: 'Do you want to add a body to your request ?', + initial: ['POST', 'PUT', 'PATCH'].includes(cmd.method) ? true : false, + }) + + if (bodyCheckResponse.needsBody) { + const headerResponse = await prompts({ + type: 'text', + name: 'body', + message: + 'Copy the body of the request here.\nIf you want dynamic parameters like random strings or increment integer, use the {{myParam}} syntax, they will be configured after.', + initial: '{"foo": "{{bar}}"}', + }) + cmd.body = headerResponse.body + + const bodyParams = [...headerResponse.body.match(/{{\w+}}/g)] + + for (let i = 0; i < bodyParams.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + const dynamicParamResponse = await prompts({ + type: 'select', + name: 'type', + message: `What is the type of the dynamic parameter : ${bodyParams[i]}`, + choices: [ + { + title: 'Increment value', + description: 'Generates an integer incremented at each call', + value: BodyParamType.INCREMENT_VALUE, + }, + { + title: 'Random string', + description: 'Generates a random string', + value: BodyParamType.RANDOM_STRING, + }, + ], + }) + cmd.bodyParams?.push({ + name: bodyParams[i], + type: dynamicParamResponse.type, }) - cmd.body = headerResponse.body - - const bodyParams = [...headerResponse.body.match(/{{\w+}}/g)] - - for (let i = 0; i < bodyParams.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - const dynamicParamResponse = await prompts({ - type: 'select', - name: 'type', - message: `What is the type of the dynamic parameter : ${bodyParams[i]}`, - choices: [ - { - title: 'Increment value', - description: 'Generates an integer incremented at each call', - value: BodyParamType.INCREMENT_VALUE, - }, - { - title: 'Random string', - description: 'Generates a random string', - value: BodyParamType.RANDOM_STRING, - }, - ], - }) - cmd.bodyParams?.push({ - name: bodyParams[i], - type: dynamicParamResponse.type, - }) - } } } - await addBody() +} - const finalResponse = await prompts([ +const promptFinal = async (): Promise<{ + nbPerSecond: number + nbSeconds: number + timeoutMs: number +}> => { + return await prompts([ { type: 'number', name: 'nbPerSecond', @@ -173,8 +162,32 @@ export const cli = async (): Promise => { validate: (value) => (value < 0 ? `Should be a positive number` : true), }, ]) +} + +export const cli = async (): Promise => { + const cmd: CommandData = { + method: '', + url: '', + headers: {}, + bodyParams: [], + nbPerSecond: DEFAULT_NB_PER_SECOND, + nbSeconds: DEFAULT_NB_SECONDS, + timeout_ms: DEFAULT_TIMEOUT_MS, + } + + const baseResponse = await promptBase() + cmd.url = baseResponse.url + cmd.method = baseResponse.method + + await promptHeaders(cmd.headers) + + await promptBody(cmd) + + const finalResponse = await promptFinal() + cmd.nbPerSecond = finalResponse.nbPerSecond cmd.nbSeconds = finalResponse.nbSeconds + cmd.timeout_ms = finalResponse.timeoutMs return cmd } diff --git a/src/process.ts b/src/process.ts index c45ef9b..2008bb7 100644 --- a/src/process.ts +++ b/src/process.ts @@ -9,33 +9,15 @@ type DataSet = { [statusCode: string]: number } -// increment value at each request let dynamicParamIncrement = 1 export const process = async (cmd: CommandData): Promise => { - const urlParts = url.parse(cmd.url) - const params = { - hostname: urlParts.hostname!, - port: urlParts.port ?? (urlParts.protocol === 'https:' ? 443 : 80), - method: cmd.method, - path: urlParts.path, - headers: cmd.headers, - timeout: cmd.timeout_ms, - } - - const dataset: DataSet = { - error: 0, - } + const params = createRequestOptions(cmd) + const dataset: DataSet = { error: 0 } const requestPromises: Promise[] = [] console.log(`Sending ${cmd.nbPerSecond} requests each second ...`) - await makePaquetRequestPaquet( - cmd, - cmd.nbSeconds, - requestPromises, - params, - dataset - ) + await sendRequestBatches(cmd, requestPromises, params, dataset) console.log('Processing all requests ...') await Promise.all(requestPromises) @@ -45,40 +27,49 @@ export const process = async (cmd: CommandData): Promise => { return Promise.resolve() } -const makePaquetRequestPaquet = ( +const createRequestOptions = (cmd: CommandData): RequestOptions => { + const urlParts = url.parse(cmd.url) + return { + hostname: urlParts.hostname!, + port: urlParts.port ?? (urlParts.protocol === 'https:' ? 443 : 80), + method: cmd.method, + path: urlParts.path, + headers: cmd.headers, + timeout: cmd.timeout_ms, + } +} + +const sendRequestBatches = ( cmd: CommandData, - nbSecondsLeft: number, requestPromises: Promise[], params: RequestOptions, - dataset: DataSet, - rootResolve?: (value: void | PromiseLike) => void + dataset: DataSet ): Promise => { - return new Promise((resolve) => { - rootResolve = rootResolve || resolve - - for (let i = 0; i < cmd.nbPerSecond; i++) { - requestPromises.push(makeRequest(cmd, params, dataset)) - } - - nbSecondsLeft-- - console.log(`${nbSecondsLeft} second${nbSecondsLeft > 1 ? 's' : ''} left`) - if (nbSecondsLeft > 0) { - setTimeout(async () => { - await makePaquetRequestPaquet( - cmd, - nbSecondsLeft, - requestPromises, - params, - dataset, - rootResolve - ) - }, 1000) - } else { - rootResolve() + return new Promise(async (resolve) => { + for (let i = cmd.nbSeconds; i > 0; i--) { + await sendRequests(cmd, requestPromises, params, dataset) + console.log(`${i - 1} second${i - 1 > 1 ? 's' : ''} left`) + if (i - 1 > 0) { + await new Promise((r) => setTimeout(r, 1000)) + } } + resolve() }) } +const sendRequests = ( + cmd: CommandData, + requestPromises: Promise[], + params: RequestOptions, + dataset: DataSet +): Promise => { + const requests = [] + for (let i = 0; i < cmd.nbPerSecond; i++) { + requests.push(makeRequest(cmd, params, dataset)) + } + return Promise.all(requests) +} + const makeRequest = ( cmd: CommandData, params: RequestOptions, @@ -97,20 +88,28 @@ const makeRequest = ( dataset.error++ resolve() }) - // customise body param + if (cmd.body) { - let body = cmd.body - cmd.bodyParams?.forEach( - (param: BodyParam) => - (body = body.replace(param.name, generateDynamicParams(param.type))) - ) - req.write(body) + const requestBody = customizeRequestBody(cmd) + req.write(requestBody) } req.end() }) } -const generateDynamicParams = (type: number): string => { +const customizeRequestBody = (cmd: CommandData): string => { + if (!cmd.body) { + throw new Error('No request body provided') + } + + let body = cmd.body + cmd.bodyParams?.forEach((param: BodyParam) => { + body = body.replace(param.name, generateDynamicValue(param.type)) + }) + return body +} + +const generateDynamicValue = (type: number): string => { switch (type) { case BodyParamType.INCREMENT_VALUE: dynamicParamIncrement++