1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Validate hook names when they are added

This commit is contained in:
Nick O'Leary 2020-09-29 16:28:52 +01:00
parent 460e1f5563
commit 605177dcf0
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
2 changed files with 107 additions and 52 deletions

View File

@ -1,5 +1,17 @@
const Log = require("@node-red/util").log; const Log = require("@node-red/util").log;
const VALID_HOOKS = [
// Message Routing Path
"onSend",
"preRoute",
"preDeliver",
"postDeliver",
"onReceive",
"postReceive",
"onComplete"
]
// Flags for what hooks have handlers registered // Flags for what hooks have handlers registered
let states = { } let states = { }
@ -34,6 +46,9 @@ let labelledHooks = { }
*/ */
function add(hookId, callback) { function add(hookId, callback) {
let [id, label] = hookId.split("."); let [id, label] = hookId.split(".");
if (VALID_HOOKS.indexOf(id) === -1) {
throw new Error(`Invalid hook '${id}'`);
}
if (label) { if (label) {
if (labelledHooks[label] && labelledHooks[label][id]) { if (labelledHooks[label] && labelledHooks[label][id]) {
throw new Error("Hook "+hookId+" already registered") throw new Error("Hook "+hookId+" already registered")
@ -149,8 +164,17 @@ function clear() {
labelledHooks = {} labelledHooks = {}
states = {} states = {}
} }
function has(hookId) {
let [id, label] = hookId.split(".");
if (label) {
return !!(labelledHooks[label] && labelledHooks[label][id])
}
return !!states[id]
}
module.exports = { module.exports = {
get states() { return states }, has,
clear, clear,
add, add,
remove, remove,

View File

@ -9,47 +9,78 @@ describe("runtime/hooks", function() {
}) })
it("allows a hook to be registered", function(done) { it("allows a hook to be registered", function(done) {
let calledWith = null; let calledWith = null;
should.not.exist(hooks.states.foo); hooks.has("onSend").should.be.false();
hooks.add("foo", function(payload) { calledWith = payload } ) hooks.add("onSend", function(payload) { calledWith = payload } )
hooks.states.foo.should.be.true(); hooks.has("onSend").should.be.true();
let data = { a: 1 }; let data = { a: 1 };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
calledWith.should.equal(data); calledWith.should.equal(data);
done(err); done(err);
}) })
}) })
it("rejects invalid hook id", function(done) {
try {
hooks.add("foo", function(payload) {})
done(new Error("Invalid hook accepted"))
} catch(err) {
done();
}
})
it("calls hooks in the order they were registered", function(done) { it("calls hooks in the order they were registered", function(done) {
hooks.add("foo", function(payload) { payload.order.push("A") } ) hooks.add("onSend", function(payload) { payload.order.push("A") } )
hooks.add("foo", function(payload) { payload.order.push("B") } ) hooks.add("onSend", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A","B"]) data.order.should.eql(["A","B"])
done(err); done(err);
}) })
}) })
it("does not allow multiple hooks with same id.label", function() { it("does not allow multiple hooks with same id.label", function() {
hooks.add("foo.one", function(payload) { payload.order.push("A") } ); hooks.has("onSend.one").should.be.false();
hooks.has("onSend").should.be.false();
hooks.add("onSend.one", function(payload) { payload.order.push("A") } );
hooks.has("onSend.one").should.be.true();
hooks.has("onSend").should.be.true();
(function() { (function() {
hooks.add("foo.one", function(payload) { payload.order.push("B") } ) hooks.add("onSend.one", function(payload) { payload.order.push("B") } )
}).should.throw(); }).should.throw();
}) })
it("removes labelled hook", function(done) { it("removes labelled hook", function(done) {
hooks.add("foo.A", function(payload) { payload.order.push("A") } ) hooks.has("onSend.A").should.be.false();
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.has("onSend.B").should.be.false();
hooks.has("onSend").should.be.false();
hooks.add("onSend.A", function(payload) { payload.order.push("A") } )
hooks.has("onSend.A").should.be.true();
hooks.has("onSend.B").should.be.false();
hooks.has("onSend").should.be.true();
hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
hooks.has("onSend.A").should.be.true();
hooks.has("onSend.B").should.be.true();
hooks.has("onSend").should.be.true();
hooks.remove("onSend.A");
hooks.has("onSend.A").should.be.false();
hooks.has("onSend.B").should.be.true();
hooks.has("onSend").should.be.true();
hooks.remove("foo.A");
hooks.states.foo.should.be.true();
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
try { try {
data.order.should.eql(["B"]) data.order.should.eql(["B"])
hooks.remove("foo.B"); hooks.remove("onSend.B");
should.not.exist(hooks.states.foo);
hooks.has("onSend.A").should.be.false();
hooks.has("onSend.B").should.be.false();
hooks.has("onSend").should.be.false();
done(err); done(err);
} catch(err2) { } catch(err2) {
@ -59,30 +90,30 @@ describe("runtime/hooks", function() {
}) })
it("cannot remove unlabelled hook", function() { it("cannot remove unlabelled hook", function() {
hooks.add("foo", function(payload) { payload.order.push("A") } ); hooks.add("onSend", function(payload) { payload.order.push("A") } );
(function() { (function() {
hooks.remove("foo") hooks.remove("onSend")
}).should.throw(); }).should.throw();
}) })
it("removes all hooks with same label", function(done) { it("removes all hooks with same label", function(done) {
hooks.add("foo.A", function(payload) { payload.order.push("A") } ) hooks.add("onSend.A", function(payload) { payload.order.push("A") } )
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
hooks.add("bar.A", function(payload) { payload.order.push("C") } ) hooks.add("preRoute.A", function(payload) { payload.order.push("C") } )
hooks.add("bar.B", function(payload) { payload.order.push("D") } ) hooks.add("preRoute.B", function(payload) { payload.order.push("D") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A","B"]) data.order.should.eql(["A","B"])
hooks.trigger("bar", data, err => { hooks.trigger("preRoute", data, err => {
data.order.should.eql(["A","B","C","D"]) data.order.should.eql(["A","B","C","D"])
data.order = []; data.order = [];
hooks.remove("*.A"); hooks.remove("*.A");
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["B"]) data.order.should.eql(["B"])
hooks.trigger("bar", data, err => { hooks.trigger("preRoute", data, err => {
data.order.should.eql(["B","D"]) data.order.should.eql(["B","D"])
}) })
done(err); done(err);
@ -93,22 +124,22 @@ describe("runtime/hooks", function() {
it("halts execution on return false", function(done) { it("halts execution on return false", function(done) {
hooks.add("foo.A", function(payload) { payload.order.push("A"); return false } ) hooks.add("onSend.A", function(payload) { payload.order.push("A"); return false } )
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A"]) data.order.should.eql(["A"])
err.should.be.false(); err.should.be.false();
done(); done();
}) })
}) })
it("halts execution on thrown error", function(done) { it("halts execution on thrown error", function(done) {
hooks.add("foo.A", function(payload) { payload.order.push("A"); throw new Error("error") } ) hooks.add("onSend.A", function(payload) { payload.order.push("A"); throw new Error("error") } )
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A"]) data.order.should.eql(["A"])
should.exist(err); should.exist(err);
err.should.not.be.false() err.should.not.be.false()
@ -117,47 +148,47 @@ describe("runtime/hooks", function() {
}) })
it("handler can use callback function", function(done) { it("handler can use callback function", function(done) {
hooks.add("foo.A", function(payload, done) { hooks.add("onSend.A", function(payload, done) {
setTimeout(function() { setTimeout(function() {
payload.order.push("A") payload.order.push("A")
done() done()
},30) },30)
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A","B"]) data.order.should.eql(["A","B"])
done(err); done(err);
}) })
}) })
it("handler can use callback function - halt execution", function(done) { it("handler can use callback function - halt execution", function(done) {
hooks.add("foo.A", function(payload, done) { hooks.add("onSend.A", function(payload, done) {
setTimeout(function() { setTimeout(function() {
payload.order.push("A") payload.order.push("A")
done(false) done(false)
},30) },30)
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A"]) data.order.should.eql(["A"])
err.should.be.false() err.should.be.false()
done(); done();
}) })
}) })
it("handler can use callback function - halt on error", function(done) { it("handler can use callback function - halt on error", function(done) {
hooks.add("foo.A", function(payload, done) { hooks.add("onSend.A", function(payload, done) {
setTimeout(function() { setTimeout(function() {
done(new Error("test error")) done(new Error("test error"))
},30) },30)
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql([]) data.order.should.eql([])
should.exist(err); should.exist(err);
err.should.not.be.false() err.should.not.be.false()
@ -166,7 +197,7 @@ describe("runtime/hooks", function() {
}) })
it("handler be an async function", function(done) { it("handler be an async function", function(done) {
hooks.add("foo.A", async function(payload) { hooks.add("onSend.A", async function(payload) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(function() { setTimeout(function() {
payload.order.push("A") payload.order.push("A")
@ -174,17 +205,17 @@ describe("runtime/hooks", function() {
},30) },30)
}); });
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A","B"]) data.order.should.eql(["A","B"])
done(err); done(err);
}) })
}) })
it("handler be an async function - halt execution", function(done) { it("handler be an async function - halt execution", function(done) {
hooks.add("foo.A", async function(payload) { hooks.add("onSend.A", async function(payload) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(function() { setTimeout(function() {
payload.order.push("A") payload.order.push("A")
@ -192,26 +223,26 @@ describe("runtime/hooks", function() {
},30) },30)
}); });
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql(["A"]) data.order.should.eql(["A"])
done(err); done(err);
}) })
}) })
it("handler be an async function - halt on error", function(done) { it("handler be an async function - halt on error", function(done) {
hooks.add("foo.A", async function(payload) { hooks.add("onSend.A", async function(payload) {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
setTimeout(function() { setTimeout(function() {
reject(new Error("test error")) reject(new Error("test error"))
},30) },30)
}); });
}) })
hooks.add("foo.B", function(payload) { payload.order.push("B") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
let data = { order:[] }; let data = { order:[] };
hooks.trigger("foo",data,err => { hooks.trigger("onSend",data,err => {
data.order.should.eql([]) data.order.should.eql([])
should.exist(err); should.exist(err);
err.should.not.be.false() err.should.not.be.false()