UNPKG

node-red-contrib-prib-functions

Version:
201 lines (190 loc) 7.31 kB
const logger = new (require("node-red-contrib-logger"))("test").sendInfo("Copyright 2020 Jaroslav Peter Prib"); const assert = require('node:assert') if(String.prototype.escapeSpecialChars) logger.warn("String.prototype.escapeSpecialChars already defined"); else String.prototype.escapeSpecialChars=function() { return this.replace(/\\n/g,"\n") .replace(/\\'/g,"\'") .replace(/\\"/g,'\"') .replace(/\\&/g,"\&") .replace(/\\r/g,"\r") .replace(/\\t/g,"\t") .replace(/\\b/g,"\b") .replace(/\\f/g,"\f"); }; function escapeSpecialChars(s){ try{ return s?s.escapeSpecialChars():s; } catch(e) { const error="escapeSpecialChars expecting string found "+(typeof s); logger.error(error); throw Error(error); } } function setError(msg,node,err) { msg._test.error=err node.error(err) node.status({fill:"red",shape:"ring",text:"error"}) node.send([null,msg]) } function assertObjects(obj1,obj2,errorFactor,callEquals=()=>true,callNotEquals=()=>false) { try{ node.assert(obj1,obj2).then(callEquals) } catch(ex) { logger.error(ex.message); callNotEquals(ex.message) } /* assert.deepStrictEqual(actual, expected[, message]) assert.deepEqual(actual, expected[, message]) assert.notDeepEqual(actual, expected[, message]) assert.partialDeepStrictEqual(actual, expected[, message]) assert.equal(actual, expected[, message]) assert.notEqual(actual, expected[, message]) assert.strictEqual(actual, expected[, message]) assert.notStrictEqual(actual, expected[, message]) assert.doesNotMatch(string, regexp[, message]) assert.match(string, regexp[, message]) assert.doesNotReject(asyncFn[, error][, message]) assert.doesNotThrow(fn[, error][, message]) assert.throws(fn[, error][, message]) assert.ifError(value) assert.ok(value[, message]) assert.rejects(asyncFn[, error][, message]) */ } function equalObjects(obj1,obj2,errorFactor,callEquals=()=>true,callNotEquals=()=>false) { if(obj1 == obj2) return callEquals() if(obj1 instanceof Buffer) return Buffer.compare(obj1, obj2) === 0?callEquals():callNotEquals("buffers not equal") if(obj1 === Number.POSITIVE_INFINITY && obj2==="Infinity") return callEquals() if(obj1 === Number.NEGATIVE_INFINITY && obj2==="-Infinity") return callEquals() if(Number.isNaN(obj1) && obj2==="NaN") return callEquals() const obj1type=typeof obj1 if(obj1type != typeof obj2) return callNotEquals("different data types, "+obj1type+" vs "+typeof obj2) if(obj1type=="boolean") callNotEquals("boolean not equal") if(errorFactor && obj1type=="number") return (Math.abs(obj2-obj1)/obj2)<errorFactor?callEquals():callNotEquals("number outside bounds") if( !(obj1 instanceof Object) ) return callNotEquals("different object types") const keys1=Object.keys(obj1).sort() const keys2=Object.keys(obj2).sort() if( keys1.length !== keys2.length ) return callNotEquals("different number of properties") for(const key1 of keys1){ try{ if( !equalObjects(obj1[key1],obj2[key1],errorFactor,undefined,err=>{throw Error(err)}) ) return callNotEquals() } catch(ex){ return callNotEquals(ex.message) } } return callEquals(); } const testedOK=(node,msg)=>{ node.status({fill:"green",shape:"ring",text:"Success"}); delete msg._test; node.send([null,null,msg]); } const testedFailed=(node,msg,err)=>{ msg._test.testedValue=node.getData(msg,node) msg._test.errorDetails=err setError(msg,node,"Test failed") } module.exports = function(RED) { "use strict"; function testNode(n) { RED.nodes.createNode(this,n); let node=Object.assign(this,n); try{ node.isJSONata=node.resultType=="jsonata" if(node.escapeString && node.resultType=="str") { node.getData=eval("((msg,node)=>escapeSpecialChars("+(node.resultProperty||"msg.payload")+"))"); } else{ if(node.isJSONata) { if(!node.result.startsWith("$boolean")) throw Error("JSONata must have $boolean outcome, found: "+node.resultProperty.substr(0,8)) node.resultExpression=RED.util.prepareJSONataExpression(node.result, node) node.resultExpression.assign('node', node); } node.getData=eval("((msg,node)=>"+(node.resultProperty||"msg.payload")+")"); } node.status({fill:"green",shape:"ring",text:"Ready"}); } catch(e) { node.error(e); node.status({fill:"red",shape:"ring",text:"Invalid setup "+e.toString()}); } node.payloadEscape=(node.payloadType=="str"&&node.escapeString); node.equalObjects=node.resultType=="re"? (value,regex,callEquals,callNotEquals)=>(RegExp(regex).test(value)?callEquals():callNotEquals()): (obj1,obj2,errorFactor,callEquals,callNotEquals)=>equalObjects(obj1,obj2,errorFactor,callEquals,callNotEquals); node.on("input",function(msg) { if(msg._test) { try{ const testData=node.getData(msg,node) if(msg._test.id!==node.id) return setError(msg,node,"Sent by another test "+msg._test.id); if(node.isJSONata) return RED.util.evaluateJSONataExpression(node.resultExpression,testData,(err,data)=>{ if(err) return testedFailed(node,msg,err) if(data==null) testedFailed(node,msg,"no data, expecting boolean") return data==true ? testedOK(node,msg) : testedFailed(node,msg,"jsonata not true") }) node.equalObjects(testData,msg._test.result,node.errorFactor, ()=>testedOK(node,msg), err=>testedFailed(node,msg,"equal object test failure "+err) ); } catch(ex){ setError(msg,node,"Test failed on get data "+ex.message); } return; } node.status({fill:"yellow",shape:"ring",text:"waiting on response"}); let result=RED.util.evaluateNodeProperty(node.result,node.resultType,node,msg,(err,data)=>{ if(err) node.error(err,msg) msg._test={ id:node.id, result:node.escapeString&&node.resultType=="str"?escapeSpecialChars(data):data }; msg.topic=node.topic; if(["flow","global"].includes(node.payloadType)) { RED.util.evaluateNodeProperty(node.payload,node.payloadType,node,msg, (err,res)=>{ if (err) { node.error(err,msg); } else { msg.payload=res; node.send(msg); } }); } else { try { if ( (node.payloadType == null && node.payload === "") || node.payloadType === "date") { msg.payload = Date.now(); } else if (node.payloadType == null) { msg.payload = node.payload; } else if (node.payloadType === 'none') { msg.payload = ""; } else { msg.payload=RED.util.evaluateNodeProperty(this.payload,node.payloadType,node,msg); if(node.payloadEscape) msg.payload=msg.payload.escapeSpecialChars(); } node.send(msg); msg = null; } catch(err) { node.error(err,msg); } } }); }); } RED.nodes.registerType(logger.label,testNode); RED.httpAdmin.post("/test/:id", RED.auth.needsPermission("test.write"), function(req,res) { let node = RED.nodes.getNode(req.params.id); if (node) { try { node.receive(); res.sendStatus(200); } catch(err) { res.sendStatus(500); node.error(RED._("inject.failed",{error:err.toString()})); } } else { res.sendStatus(404); } }); }