UNPKG

node-red-contrib-flow-asserter

Version:

This module is a node module that supports flow testing on the Node-RED editor UI. You can test the flow with multiple test-cases and without any changes when switching between test and production.

215 lines (200 loc) 8.2 kB
module.exports = function(RED) { 'use strict'; const equal = require('fast-deep-equal'); const operatorFunctions = { 'eq': function (actual, expected) { return (actual == expected); }, 'neq': function (actual, expected) { return (actual != expected); }, 'lt': function (actual, expected) { return (actual < expected); }, 'lte': function (actual, expected) { return (actual <= expected); }, 'gt': function (actual, expected) { return (actual > expected); }, 'gte': function (actual, expected) { return (actual >= expected); }, 'equal': function (actual, expected) { return equal(actual, expected); }, 'cont': function (actual, expected) { return ((actual + '').indexOf(expected) != -1); }, 'regex': function (actual, expected) { return expected.test(actual + ''); }, 'null': function (actual) { return (typeof actual == 'undefined' || actual === null); }, 'nnull': function (actual) { return (typeof actual != 'undefined' && actual !== null); }, 'empty': function (actual) { if (typeof actual === 'string' || Array.isArray(actual) || Buffer.isBuffer(actual)) { return (actual.length === 0); } else if (typeof actual === 'object' && actual !== null) { return (Object.keys(actual).length === 0); } else { return false; } }, 'nempty': function (actual) { if (typeof actual === 'string' || Array.isArray(actual) || Buffer.isBuffer(actual)) { return (actual.length !== 0); } else if (typeof actual === 'object' && actual !== null) { return (Object.keys(actual).length !== 0); } else { return false; } }, 'istype': function (actual, expected) { switch (expected) { case 'array': return Array.isArray(actual); case 'buffer': return Buffer.isBuffer(actual); case 'json': try { JSON.parse(actual); return true; } catch(e) { return false; } case 'null': return (actual === null); default: return (typeof actual === expected && !Array.isArray(actual) && !Buffer.isBuffer(actual) && actual !== null); } }, 'jsonata': function (actual, expected, msg, node) { try { return (RED.util.evaluateJSONataExpression(expected, msg) === true); } catch (e) { node.error(RED._('flow-asserter-in.errors.invalid-jsonata', {error: e.message})); return false; } }, }; const prepareResultMessage = function (node) { if (node.testId == 0) { return null; } if (node.onlyfail && node.testcases[node.testId - 1].result == 'Success') { return null; } return node.testId == 0 ? null: {'payload': node.testcases[node.testId - 1]}; }; function FlowAsserterInNode(n) { RED.nodes.createNode(this, n); var node = this; node.onlyfail = n.onlyfail; const onlySelectOperators = ['null', 'nnull', 'empty', 'nempty']; node.testcases = []; for (let i in n.testcases) { let tc = n.testcases[i]; if (tc.inputType == 'fixed') { switch (tc.input) { case 'null': tc.input = null; break; case 'undefined': tc.input = undefined; break; default: node.error(RED._('flow-asserter-in.errors.invalid-input', {input: tc.input})); } } else { tc.input = RED.util.evaluateNodeProperty(tc.input, tc.inputType); } if (tc.operator == 'jsonata') { tc.assert = RED.util.prepareJSONataExpression(tc.assert, node); } else if (onlySelectOperators.includes(tc.operator)) { tc.assert = undefined; tc.assertType = undefined; } else { tc.assert = RED.util.evaluateNodeProperty(tc.assert, tc.assertType); } tc.flowAsserterIds = {'input': n.id, 'output': n.flowasserterout}; node.testcases.push(tc); } node.testId = 0; node.failedCase = 0; var event = 'node:' + n.id; var handler = function(msg) { let tc = msg._testcase; let result; try { result = operatorFunctions[tc.operator](msg.payload, tc.assert, msg, node); } catch (e) { node.error(RED._('flow-asserter-in.errors.invalid-operator', {operator: tc.operator})); } tc.result = result ? 'Success': 'Failure'; node.failedCase += result ? 0 : 1; node.testcases[node.testId].result = tc.result; node.testId++; node.receive(msg); } RED.events.on(event, handler); this.on('input', function(msg) { this.status({ fill: (node.failedCase == 0 ? 'green' : 'red'), shape: 'ring', text: RED._('flow-asserter-in.status.running', {currentnum: (node.testId + 1), testcasenum: node.testcases.length}) }); const resultMsg = prepareResultMessage(node); if (node.testId < node.testcases.length) { // testcase remains msg._testcase = node.testcases[node.testId]; msg.payload = msg._testcase['input']; this.send([msg, resultMsg]); } else { // no testcase is left this.status({ fill: (node.failedCase == 0 ? 'green' : 'red'), shape: 'dot', text: RED._('flow-asserter-in.status.finished', {failedcase: node.failedCase}) }); this.send([null, resultMsg]) node.testId = 0; node.failedCase = 0; } }); this.on('close',function() { this.status({}); RED.events.removeListener(event, handler); }); } RED.nodes.registerType('flow-asserter in', FlowAsserterInNode); RED.httpAdmin.post('/flow-asserter-in/:id', RED.auth.needsPermission('flow-asserter in.write'), function(req,res) { var node = RED.nodes.getNode(req.params.id); if (node != null) { try { node.receive(); res.sendStatus(200); } catch(err) { res.sendStatus(500); node.error(RED._('flow-asserter-in.notification.failed',{error:err.toString()})); } } else { res.sendStatus(404); } }); function FlowAsserterOutNode(n) { RED.nodes.createNode(this, n); var node = this; node.id = n.id; this.on('input', function(msg) { if (msg._testcase !== undefined && msg._testcase.flowAsserterIds.output == node.id) { let event = 'node:' + msg._testcase.flowAsserterIds.input; RED.events.emit(event, msg); } else { this.send(msg); } }); } RED.nodes.registerType('flow-asserter out', FlowAsserterOutNode); }