diff --git a/sonar-project.properties b/sonar-project.properties index 2d8ebb5..dace4fc 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,7 +9,7 @@ sonar.javascript.lcov.reportPaths=coverage/lcov.info # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=src -sonar.exclusions=src/api-docs/**, src/app-server.js, src/helpers/timed-match/match-proc.js, src/helpers/cache/worker.js +sonar.exclusions=src/api-docs/**, src/app-server.js, src/helpers/timed-match/match-proc.js sonar.tests=tests sonar.language=js diff --git a/src/app.js b/src/app.js index 6f8f5b8..08de14d 100644 --- a/src/app.js +++ b/src/app.js @@ -9,11 +9,20 @@ import './db/mongoose.js'; import mongoose from 'mongoose'; import swaggerDocument from './api-docs/swagger-document.js'; import clientApiRouter from './routers/client-api.js'; +import TimedMatch from './helpers/timed-match/index.js'; import schema from './aggregator/schema.js'; import { appAuth, resourcesAuth } from './middleware/auth.js'; import { clientLimiter, defaultLimiter } from './middleware/limiter.js'; import { createServer } from './app-server.js'; +/** + * Initialize TimedMatch Worker + */ +TimedMatch.initializeWorker(); + +/** + * Express app instance + */ const app = express(); app.use(express.json()); @@ -32,7 +41,6 @@ app.use(clientApiRouter); /** * GraphQL Routes */ - const handler = (req, res, next) => createHandler({ schema, context: req })(req, res, next); @@ -42,7 +50,6 @@ app.use('/graphql', appAuth, clientLimiter, handler); /** * API Docs and Health Check */ - app.use('/api-docs', resourcesAuth(), swaggerUi.serve, swaggerUi.setup(swaggerDocument) diff --git a/src/helpers/timed-match/index.js b/src/helpers/timed-match/index.js index 7a70f0c..ef67424 100644 --- a/src/helpers/timed-match/index.js +++ b/src/helpers/timed-match/index.js @@ -1,75 +1,99 @@ -import cp from 'node:child_process'; import path from 'node:path'; +import { Worker } from 'node:worker_threads'; import { fileURLToPath } from 'node:url'; +import tryMatch from '../timed-match/match.js'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** - * This class will run a match operation using a child process. + * This class will run a match operation using a Worker Thread. + * * Workers should be killed given a specified (3000 ms default) time limit. + * * Blacklist caching is available to prevent sequence of matching failures and resource usage. */ export default class TimedMatch { - static _worker = this._createChildProcess(); - static _blacklisted = []; - static _maxBlackListed = process.env.REGEX_MAX_BLACKLIST || 50; - static _maxTimeLimit = process.env.REGEX_MAX_TIMEOUT || 3000; + static #worker = undefined; + static #workerActive = false; + static #blacklisted = []; + static #maxBlackListed = process.env.REGEX_MAX_BLACKLIST || 50; + static #maxTimeLimit = process.env.REGEX_MAX_TIMEOUT || 3000; + + /** + * Initialize Worker process for working with Regex process operators + */ + static initializeWorker() { + this.#worker = this.#createWorker(); + this.#workerActive = true; + } + + /** + * Gracefully terminate worker + */ + static terminateWorker() { + this.#worker?.terminate(); + this.#workerActive = false; + } /** - * Run match using child process + * Executes regex matching operation with timeout protection. + * + * If a worker is initialized and active, the operation runs in a separate worker thread + * with timeout protection to prevent runaway regex operations. Uses SharedArrayBuffer + * for synchronous communication between main thread and worker. + * + * If no worker is available, falls back to direct execution on the main thread. + * + * Failed operations (timeouts, errors) are automatically added to a blacklist to + * prevent repeated attempts with the same problematic patterns. * * @param {*} values array of regular expression to be evaluated * @param {*} input to be matched * @returns match result */ - static async tryMatch(values, input) { - let result = false; - let timer, resolveListener; - - if (this._isBlackListed({ values, input })) { - return false; + static tryMatch(values, input) { + if (this.#worker && this.#workerActive) { + return this.#safeMatch(values, input); } - - const matchPromise = new Promise((resolve) => { - resolveListener = resolve; - this._worker.on('message', resolveListener); - this._worker.send({ values, input }); - }); - - const matchTimer = new Promise((resolve) => { - timer = setTimeout(() => { - this._resetWorker({ values, input }); - resolve(false); - }, this._maxTimeLimit); - }); - - await Promise.race([matchPromise, matchTimer]).then((value) => { - this._worker.off('message', resolveListener); - clearTimeout(timer); - result = value; - }); - - return result; - } + return tryMatch(values, input); + } + /** - * Clear entries from failed matching operations + * Run match using Node.js Worker Threads API. + * + * @param {*} values array of regular expression to be evaluated + * @param {*} input to be matched + * @returns match result */ - static clearBlackList() { - this._blacklisted = []; - } - - static setMaxBlackListed(value) { - this._maxBlackListed = value; - } + static #safeMatch(values, input) { + if (this.#isBlackListed(values, input)) { + return false; + } - static setMaxTimeLimit(value) { - this._maxTimeLimit = value; + // Create a SharedArrayBuffer for communication + const sharedBuffer = new SharedArrayBuffer(4); + const int32Array = new Int32Array(sharedBuffer); + + // Send parameters to worker using postMessage (Worker Threads API) + this.#worker.postMessage({ values, input, sharedBuffer }); + + // Wait for worker to complete or timeout + const result = Atomics.wait(int32Array, 0, 0, this.#maxTimeLimit); + + if (result === 'timed-out') { + this.#resetWorker(values, input); + return false; + } + + // Get the actual result from the shared buffer + return Atomics.load(int32Array, 0) === 1; } - static _isBlackListed({ values, input }) { - const bls = this._blacklisted.filter(bl => + static #isBlackListed(values, input) { + const bls = this.#blacklisted.filter(bl => // input can contain same segment that could fail matching operation (bl.input.includes(input) || input.includes(bl.input)) && // regex order should not affect @@ -85,27 +109,39 @@ export default class TimedMatch { * * @param {*} param0 list of regex and input */ - static _resetWorker({ values, input }) { - this._worker.kill(); - this._worker = this._createChildProcess(); + static #resetWorker(values, input) { + this.#worker.terminate(); + this.#worker = this.#createWorker(); - if (this._blacklisted.length == this._maxBlackListed) { - this._blacklisted.splice(0, 1); + if (this.#blacklisted.length == this.#maxBlackListed) { + this.#blacklisted.splice(0, 1); } - this._blacklisted.push({ + this.#blacklisted.push({ res: values, input }); } - static _createChildProcess() { - const match_proc = cp.fork(`${__dirname}/match-proc.js`, { - stdio: 'ignore' - }); + static #createWorker() { + const match_proc = new Worker(`${__dirname}/match-proc.js`); match_proc.unref(); - match_proc.channel.unref(); return match_proc; } + + /** + * Clear entries from failed matching operations + */ + static clearBlackList() { + this.#blacklisted = []; + } + + static setMaxBlackListed(value) { + this.#maxBlackListed = value; + } + + static setMaxTimeLimit(value) { + this.#maxTimeLimit = value; + } } \ No newline at end of file diff --git a/src/helpers/timed-match/match-proc.js b/src/helpers/timed-match/match-proc.js index dfc641e..5e71a2b 100644 --- a/src/helpers/timed-match/match-proc.js +++ b/src/helpers/timed-match/match-proc.js @@ -1,15 +1,14 @@ -function tryMatch(values, input) { - let result = false; - for (const value of values) { - if (input.match(value)) { - result = true; - break; - } - } +import { parentPort } from 'node:worker_threads'; +import tryMatch from './match.js'; - return result; -} +parentPort.on('message', (e) => { + const { values, input, sharedBuffer } = e; + const int32Array = new Int32Array(sharedBuffer); + const result = tryMatch(values, input); -process.on('message', ({ values, input }) => { - process.send(tryMatch(values, input)); + // Store result in shared buffer (1 for true, 0 for false) + Atomics.store(int32Array, 0, result ? 1 : 0); + + // Notify the main thread that work is complete + Atomics.notify(int32Array, 0); }); \ No newline at end of file diff --git a/src/helpers/timed-match/match.js b/src/helpers/timed-match/match.js new file mode 100644 index 0000000..e39b08a --- /dev/null +++ b/src/helpers/timed-match/match.js @@ -0,0 +1,11 @@ +export default function tryMatch(values, input) { + let result = false; + for (const value of values) { + if (input.match(value)) { + result = true; + break; + } + } + + return result; +} \ No newline at end of file diff --git a/src/models/config-strategy.js b/src/models/config-strategy.js index 0082515..0712782 100644 --- a/src/models/config-strategy.js +++ b/src/models/config-strategy.js @@ -26,7 +26,7 @@ export const OperationsType = Object.freeze({ HAS_ALL: 'HAS_ALL' }); -export async function processOperation(strategy, operation, input, values) { +export function processOperation(strategy, operation, input, values) { switch(strategy) { case StrategiesType.NETWORK: return processNETWORK(operation, input, values); @@ -141,16 +141,16 @@ function processDATE(operation, input, values) { } } -async function processREGEX(operation, input, values) { +function processREGEX(operation, input, values) { switch(operation) { case OperationsType.EXIST: - return await TimedMatch.tryMatch(values, input); + return TimedMatch.tryMatch(values, input); case OperationsType.NOT_EXIST: - return !(await processREGEX(OperationsType.EXIST, input, values)); + return !processREGEX(OperationsType.EXIST, input, values); case OperationsType.EQUAL: - return await TimedMatch.tryMatch([String.raw`\b${values[0]}\b`], input); + return TimedMatch.tryMatch([String.raw`\b${values[0]}\b`], input); case OperationsType.NOT_EQUAL: - return !(await TimedMatch.tryMatch([String.raw`\b${values[0]}\b`], input)); + return !TimedMatch.tryMatch([String.raw`\b${values[0]}\b`], input); } } diff --git a/src/services/criteria.js b/src/services/criteria.js index 2a644c0..34e88ba 100644 --- a/src/services/criteria.js +++ b/src/services/criteria.js @@ -39,7 +39,7 @@ export async function evaluateCriteria(config, context, strategyFilter) { } // Check strategy - if (!(await checkStrategy(context.entry, environment, response))) { + if (!checkStrategy(context.entry, environment, response)) { return addMetricsAndReturn(context, config, environment, response); } @@ -84,7 +84,7 @@ function checkFlags(config, environment, response) { return true; } -async function checkStrategy(entry, environment, response) { +function checkStrategy(entry, environment, response) { const { strategies } = response; if (strategies) { @@ -93,7 +93,7 @@ async function checkStrategy(entry, environment, response) { continue; } - if (!(await checkStrategyInput(entry, strategy, response))) { + if (!checkStrategyInput(entry, strategy, response)) { return false; } } @@ -102,7 +102,7 @@ async function checkStrategy(entry, environment, response) { return true; } -async function checkStrategyInput(entry, { strategy, operation, values }, response) { +function checkStrategyInput(entry, { strategy, operation, values }, response) { if (!entry?.length) { response.result = false; response.reason = `Strategy '${strategy}' did not receive any input`; @@ -110,7 +110,7 @@ async function checkStrategyInput(entry, { strategy, operation, values }, respon } const strategyEntry = entry.filter(e => e.strategy === strategy); - if (strategyEntry.length == 0 || !(await processOperation(strategy, operation, strategyEntry[0].input, values))) { + if (strategyEntry.length == 0 || !processOperation(strategy, operation, strategyEntry[0].input, values)) { response.result = false; response.reason = `Strategy '${strategy}' does not agree`; return false; diff --git a/tests/unit-test/config-strategy.test.js b/tests/unit-test/config-strategy.test.js index 931ff8e..ab99dff 100644 --- a/tests/unit-test/config-strategy.test.js +++ b/tests/unit-test/config-strategy.test.js @@ -1,4 +1,5 @@ import mongoose from 'mongoose'; +import TimedMatch from '../../src/helpers/timed-match'; import { payloadReader } from '../../src/helpers'; import { processOperation, @@ -8,9 +9,15 @@ import { import '../../src/app'; +beforeAll(() => { + TimedMatch.initializeWorker(); + TimedMatch.clearBlackList(); +}); + afterAll(async () => { await new Promise(resolve => setTimeout(resolve, 1000)); await mongoose.disconnect(); + TimedMatch.terminateWorker(); }); describe('Processing strategy: NETWORK', () => { @@ -29,44 +36,44 @@ describe('Processing strategy: NETWORK', () => { '192.168.56.58' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when input range EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input range EXIST', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.EXIST, '10.0.0.3', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input range DOES NOT EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input range DOES NOT EXIST', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.EXIST, '10.0.0.4', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input NOT_EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input NOT_EXIST', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.NOT_EXIST, '10.0.0.4', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input IP EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input IP EXIST', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.EXIST, '192.168.56.58', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input IP DOES NOT EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input IP DOES NOT EXIST', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.NOT_EXIST, '192.168.56.50', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input range EXIST for multiple ranges', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input range EXIST for multiple ranges', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.EXIST, '192.168.0.3', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input range DOES NOT EXIST for multiple ranges', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input range DOES NOT EXIST for multiple ranges', () => { + const result = processOperation( StrategiesType.NETWORK, OperationsType.NOT_EXIST, '127.0.0.0', fixture_values2); expect(result).toBe(true); }); @@ -82,44 +89,44 @@ describe('Processing strategy: VALUE', () => { 'USER_1', 'USER_2' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.EXIST, 'USER_1', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input DOES NOT EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input DOES NOT EXIST', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.EXIST, 'USER_123', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input DOES NOT EXIST', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input DOES NOT EXIST', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.NOT_EXIST, 'USER_123', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is EQUAL', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is EQUAL', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.EQUAL, 'USER_1', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT EQUAL', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT EQUAL', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.EQUAL, 'USER_2', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is NOT EQUAL', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is NOT EQUAL', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.NOT_EQUAL, 'USER_123', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT EQUAL', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT EQUAL', () => { + const result = processOperation( StrategiesType.VALUE, OperationsType.NOT_EQUAL, 'USER_2', fixture_values2); expect(result).toBe(false); }); @@ -138,104 +145,104 @@ describe('Processing strategy: NUMERIC', () => { '1.5' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST in values - String type', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST in values - String type', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.EXIST, '3', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST in values - Number type', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input EXIST in values - Number type', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.EXIST, 3, fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input exist but test as DOES NOT EXIST ', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input exist but test as DOES NOT EXIST ', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.NOT_EXIST, '1', fixture_values2); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input DOES NOT EXIST in values', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input DOES NOT EXIST in values', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.NOT_EXIST, '2', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is EQUAL to value', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is EQUAL to value', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.EQUAL, '1', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is not equal but test as EQUAL', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is not equal but test as EQUAL', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.EQUAL, '2', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is NOT EQUAL to value', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is NOT EQUAL to value', () => { + const result = processOperation( StrategiesType.NUMERIC, OperationsType.NOT_EQUAL, '2', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER than value', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER than value', () => { + let result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '2', fixture_values1); expect(result).toBe(true); // test decimal - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '1.01', fixture_values1); expect(result).toBe(true); - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '1.55', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is lower but tested as GREATER than value', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is lower but tested as GREATER than value', () => { + let result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '0', fixture_values1); expect(result).toBe(false); // test decimal - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '0.99', fixture_values1); expect(result).toBe(false); - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.GREATER, '1.49', fixture_values3); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER than value', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER than value', () => { + let result = processOperation( StrategiesType.NUMERIC, OperationsType.LOWER, '0', fixture_values1); expect(result).toBe(true); // test decimal - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.LOWER, '0.99', fixture_values1); expect(result).toBe(true); - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.LOWER, '1.49', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is BETWEEN values', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is BETWEEN values', () => { + let result = processOperation( StrategiesType.NUMERIC, OperationsType.BETWEEN, '1', fixture_values2); expect(result).toBe(true); // test decimal - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.BETWEEN, '2.99', fixture_values2); expect(result).toBe(true); - result = await processOperation( + result = processOperation( StrategiesType.NUMERIC, OperationsType.BETWEEN, '1.001', fixture_values2); expect(result).toBe(true); }); @@ -251,50 +258,50 @@ describe('Processing strategy: TIME', () => { '08:00', '10:00' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.LOWER, '06:00', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER or SAME', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER or SAME', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.LOWER, '08:00', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.LOWER, '10:00', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.GREATER, '10:00', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER or SAME', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER or SAME', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.GREATER, '08:00', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT GREATER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT GREATER', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.GREATER, '06:00', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is in BETWEEN', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is in BETWEEN', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.BETWEEN, '09:00', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT in BETWEEN', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT in BETWEEN', () => { + const result = processOperation( StrategiesType.TIME, OperationsType.BETWEEN, '07:00', fixture_values2); expect(result).toBe(false); }); @@ -313,68 +320,68 @@ describe('Processing strategy: DATE', () => { '2019-12-01T08:30' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.LOWER, '2019-11-26', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER or SAME', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER or SAME', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.LOWER, '2019-12-01', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.LOWER, '2019-12-02', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.GREATER, '2019-12-02', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER or SAME', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER or SAME', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.GREATER, '2019-12-01', fixture_values1); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT GREATER', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT GREATER', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.GREATER, '2019-11-10', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is in BETWEEN', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is in BETWEEN', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.BETWEEN, '2019-12-03', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT in BETWEEN', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT in BETWEEN', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.BETWEEN, '2019-12-12', fixture_values2); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER including time', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is LOWER including time', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.LOWER, '2019-12-01T07:00', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER including time', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when input is NOT LOWER including time', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.LOWER, '2019-12-01T07:00', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER including time', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when input is GREATER including time', () => { + const result = processOperation( StrategiesType.DATE, OperationsType.GREATER, '2019-12-01T08:40', fixture_values3); expect(result).toBe(true); }); @@ -393,69 +400,69 @@ describe('Processing strategy: REGEX', () => { 'USER_[0-9]{1,2}' ]; - test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to exist using EXIST operation', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to exist using EXIST operation', () => { + let result = processOperation( StrategiesType.REGEX, OperationsType.EXIST, 'USER_1', fixture_values1); expect(result).toBe(true); - result = await processOperation( + result = processOperation( StrategiesType.REGEX, OperationsType.EXIST, 'user-01', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to exist using EXIST operation', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to exist using EXIST operation', () => { + let result = processOperation( StrategiesType.REGEX, OperationsType.EXIST, 'USER_123', fixture_values1); expect(result).toBe(false); //fixture_values3 does not require exact match - result = await processOperation( + result = processOperation( StrategiesType.REGEX, OperationsType.EXIST, 'USER_123', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to not exist using NOT_EXIST operation', async () => { - let result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to not exist using NOT_EXIST operation', () => { + let result = processOperation( StrategiesType.REGEX, OperationsType.NOT_EXIST, 'USER_123', fixture_values1); expect(result).toBe(true); - result = await processOperation( + result = processOperation( StrategiesType.REGEX, OperationsType.NOT_EXIST, 'user-123', fixture_values2); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to not exist using NOT_EXIST operation', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to not exist using NOT_EXIST operation', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.NOT_EXIST, 'USER_12', fixture_values1); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to be equal using EQUAL operation', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to be equal using EQUAL operation', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.EQUAL, 'USER_11', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to be equal using EQUAL operation', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to be equal using EQUAL operation', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.EQUAL, 'user-11', fixture_values3); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to not be equal using NOT_EQUAL operation', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return TRUE when expect to not be equal using NOT_EQUAL operation', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.NOT_EQUAL, 'USER_123', fixture_values3); expect(result).toBe(true); }); - test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to not be equal using NOT_EQUAL operation', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should return FALSE when expect to not be equal using NOT_EQUAL operation', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.NOT_EQUAL, 'USER_1', fixture_values3); expect(result).toBe(false); }); - test('UNIT_STRATEGY_SUITE - Should fail for reDoS attempt', async () => { - const result = await processOperation( + test('UNIT_STRATEGY_SUITE - Should fail for reDoS attempt', () => { + const result = processOperation( StrategiesType.REGEX, OperationsType.EXIST, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!', ['^(([a-z])+.)+[A-Z]([a-z])+$']); @@ -538,36 +545,36 @@ describe('Processing strategy: PAYLOAD', () => { ]); }); - test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has field', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has field', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ONE, fixture_1, ['login']) ).toBe(true); }); - test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload does not have field', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload does not have field', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ONE, fixture_1, ['user']) ).toBe(false); }); - test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has nested field', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has nested field', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ONE, fixture_values2, [ 'order.qty', 'order.total' ]) ).toBe(true); }); - test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has nested field with arrays', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has nested field with arrays', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ONE, fixture_values2, [ 'order.deliver.tracking.status' ]) ).toBe(true); }); - test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has all', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return TRUE when payload has all', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ALL, fixture_values2, [ 'product', 'order', @@ -581,8 +588,8 @@ describe('Processing strategy: PAYLOAD', () => { ).toBe(true); }); - test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload does not have all', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload does not have all', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ALL, fixture_values2, [ 'product', 'order', @@ -591,8 +598,8 @@ describe('Processing strategy: PAYLOAD', () => { ).toBe(false); }); - test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload is not a JSON string', async () => { - expect(await processOperation( + test('UNIT_PAYLOAD_SUITE - Should return FALSE when payload is not a JSON string', () => { + expect(processOperation( StrategiesType.PAYLOAD, OperationsType.HAS_ALL, 'NOT_JSON', []) ).toBe(false); }); diff --git a/tests/unit-test/try-match.test.js b/tests/unit-test/try-match.test.js index 6fabe89..7fad8bb 100644 --- a/tests/unit-test/try-match.test.js +++ b/tests/unit-test/try-match.test.js @@ -7,30 +7,33 @@ describe('Test tryMatch', () => { const evilInput2 = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; beforeEach(() => { - TimedMatch.setMaxBlackListed(50); - TimedMatch.setMaxTimeLimit(3000); + TimedMatch.initializeWorker(); TimedMatch.clearBlackList(); }); - test('UNIT_TRY_MATCH - Should match value', async () => { - const result = await TimedMatch.tryMatch(['USER_[0-9]{1,2}'], 'USER_1'); + afterEach(() => { + TimedMatch.terminateWorker(); + }); + + test('UNIT_TRY_MATCH - Should match value', () => { + const result = TimedMatch.tryMatch(['USER_[0-9]{1,2}'], 'USER_1'); expect(result).toBe(true); }); - test('UNIT_TRY_MATCH - Should fail for reDoS attempt - default 3000 ms', async () => { + test('UNIT_TRY_MATCH - Should fail for reDoS attempt - default 3000 ms', () => { let timer = Date.now(); - const result = await TimedMatch.tryMatch([evilRE], evilInput1); + const result = TimedMatch.tryMatch([evilRE], evilInput1); timer = Date.now() - timer; expect(timer).not.toBeLessThan(3000); expect(result).toBe(false); }); - test('UNIT_TRY_MATCH - Should fail for reDoS attempt - 2000 ms', async () => { + test('UNIT_TRY_MATCH - Should fail for reDoS attempt - 2000 ms', () => { TimedMatch.setMaxTimeLimit(2000); let timer = Date.now(); - const result = await TimedMatch.tryMatch([evilRE], evilInput1); + const result = TimedMatch.tryMatch([evilRE], evilInput1); timer = Date.now() - timer; expect(timer).not.toBeLessThan(2000); @@ -38,12 +41,12 @@ describe('Test tryMatch', () => { expect(result).toBe(false); }); - test('UNIT_TRY_MATCH - Should return from black list after fail to perfom match', async () => { + test('UNIT_TRY_MATCH - Should return from black list after fail to perfom match', () => { TimedMatch.setMaxTimeLimit(1000); let timer, result; timer = Date.now(); - result = await TimedMatch.tryMatch([evilRE], evilInput1); + result = TimedMatch.tryMatch([evilRE], evilInput1); timer = Date.now() - timer; expect(timer).not.toBeLessThan(1000); @@ -51,20 +54,20 @@ describe('Test tryMatch', () => { expect(result).toBe(false); timer = Date.now(); - result = await TimedMatch.tryMatch([evilRE], evilInput1); + result = TimedMatch.tryMatch([evilRE], evilInput1); timer = Date.now() - timer; expect(timer).not.toBeGreaterThan(100); expect(result).toBe(false); }); - test('UNIT_TRY_MATCH - Should replace black listed failed match', async () => { + test('UNIT_TRY_MATCH - Should replace black listed failed match', () => { TimedMatch.setMaxTimeLimit(500); TimedMatch.setMaxBlackListed(1); let timer, result; timer = Date.now(); - result = await TimedMatch.tryMatch([evilRE], evilInput1); + result = TimedMatch.tryMatch([evilRE], evilInput1); timer = Date.now() - timer; expect(timer).not.toBeLessThan(500); @@ -72,7 +75,7 @@ describe('Test tryMatch', () => { expect(result).toBe(false); timer = Date.now(); - result = await TimedMatch.tryMatch([evilRE], evilInput2); + result = TimedMatch.tryMatch([evilRE], evilInput2); timer = Date.now() - timer; expect(timer).not.toBeLessThan(500); @@ -80,11 +83,27 @@ describe('Test tryMatch', () => { expect(result).toBe(false); timer = Date.now(); - result = await TimedMatch.tryMatch([evilRE], evilInput2); + result = TimedMatch.tryMatch([evilRE], evilInput2); timer = Date.now() - timer; expect(timer).not.toBeGreaterThan(100); expect(result).toBe(false); }); +}); + +describe('Test tryMatch (unsafe)', () => { + beforeAll(() => { + TimedMatch.terminateWorker(); + }); + + test('UNIT_TRY_MATCH - Should return true when using unsafe match', () => { + const result = TimedMatch.tryMatch(['USER_[0-9]{1,2}'], 'USER_1'); + expect(result).toBe(true); + }); + + test('UNIT_TRY_MATCH - Should return false when using unsafe match', () => { + const result = TimedMatch.tryMatch(['USER_[0-9]{1,2}'], 'USER_ABC'); + expect(result).toBe(false); + }); }); \ No newline at end of file