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

Improve core test coverage

This commit is contained in:
Nick O'Leary 2015-03-21 17:42:06 +00:00
parent 78cf310c58
commit 36f299c031
7 changed files with 420 additions and 40 deletions

View File

@ -40,12 +40,11 @@ function start() {
var Tokens = require("./api/auth/tokens"); var Tokens = require("./api/auth/tokens");
var Users = require("./api/auth/users"); var Users = require("./api/auth/users");
var Permissions = require("./api/auth/permissions"); var Permissions = require("./api/auth/permissions");
if (!settings.disableEditor) { if (!settings.disableEditor) {
Users.default().then(function(anonymousUser) { Users.default().then(function(anonymousUser) {
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/"; var path = settings.httpAdminRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + "comms"; path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({server:server,path:path}); wsServer = new ws.Server({server:server,path:path});
wsServer.on('connection',function(ws) { wsServer.on('connection',function(ws) {
@ -127,9 +126,11 @@ function start() {
function stop() { function stop() {
if (heartbeatTimer) { if (heartbeatTimer) {
clearInterval(heartbeatTimer); clearInterval(heartbeatTimer);
heartbeatTimer = null;
} }
if (wsServer) { if (wsServer) {
wsServer.close(); wsServer.close();
wsServer = null;
} }
} }

View File

@ -47,7 +47,6 @@ var ConsoleLogHandler = function(settings) {
this.metricsOn = settings.metrics||false; this.metricsOn = settings.metrics||false;
metricsEnabled = this.metricsOn; metricsEnabled = this.metricsOn;
this.on("log",function(msg) { this.on("log",function(msg) {
/* istanbul ignore else */
if (this.shouldReportMessage(msg.level)) { if (this.shouldReportMessage(msg.level)) {
if (msg.level == log.METRIC) { if (msg.level == log.METRIC) {
util.log("[metric] "+JSON.stringify(msg)); util.log("[metric] "+JSON.stringify(msg));

View File

@ -54,7 +54,7 @@ function start() {
if (log.metric()) { if (log.metric()) {
runtimeMetricInterval = setInterval(function() { runtimeMetricInterval = setInterval(function() {
reportMetrics(); reportMetrics();
}, 15000); }, settings.runtimeMetricInterval||15000);
} }
console.log("\n\nWelcome to Node-RED\n===================\n"); console.log("\n\nWelcome to Node-RED\n===================\n");
if (settings.version) { if (settings.version) {
@ -92,7 +92,7 @@ function start() {
if (missingModules.hasOwnProperty(i)) { if (missingModules.hasOwnProperty(i)) {
log.warn(" - "+i+": "+missingModules[i].join(", ")); log.warn(" - "+i+": "+missingModules[i].join(", "));
if (settings.autoInstallModules && i != "node-red") { if (settings.autoInstallModules && i != "node-red") {
installModule(i).otherwise(function(err) { serverAPI.installModule(i).otherwise(function(err) {
// Error already reported. Need the otherwise handler // Error already reported. Need the otherwise handler
// to stop the error propagating any further // to stop the error propagating any further
}); });
@ -216,22 +216,21 @@ function uninstallModule(module) {
function reportMetrics() { function reportMetrics() {
var memUsage = process.memoryUsage(); var memUsage = process.memoryUsage();
// only need to init these once per report log.log({
var metrics = {}; level: log.METRIC,
metrics.level = log.METRIC; event: "runtime.memory.rss",
value: memUsage.rss
//report it });
metrics.event = "runtime.memory.rss" log.log({
metrics.value = memUsage.rss; level: log.METRIC,
log.log(metrics); event: "runtime.memory.heapTotal",
value: memUsage.heapTotal
metrics.event = "runtime.memory.heapTotal" });
metrics.value = memUsage.heapTotal; log.log({
log.log(metrics); level: log.METRIC,
event: "runtime.memory.heapUsed",
metrics.event = "runtime.memory.heapUsed" value: memUsage.heapUsed
metrics.value = memUsage.heapUsed; });
log.log(metrics);
} }
function stop() { function stop() {
@ -243,7 +242,7 @@ function stop() {
comms.stop(); comms.stop();
} }
module.exports = { var serverAPI = module.exports = {
init: init, init: init,
start: start, start: start,
stop: stop, stop: stop,

View File

@ -135,6 +135,137 @@ describe("comms", function() {
}); });
} }
}); });
describe("disabled editor", function() {
var server;
var url;
var port;
before(function(done) {
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {disableEditor:true});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function() {
comms.stop();
});
it('rejects websocket connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
done(new Error("Socket connection unexpectedly accepted"));
ws.close();
});
ws.on('error', function() {
done();
});
});
});
describe("non-default httpAdminRoot set: /adminPath", function() {
var server;
var url;
var port;
before(function(done) {
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"/adminPath"});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function() {
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.close();
done();
});
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("non-default httpAdminRoot set: /adminPath/", function() {
var server;
var url;
var port;
before(function(done) {
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"/adminPath/"});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function() {
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.close();
done();
});
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("non-default httpAdminRoot set: adminPath", function() {
var server;
var url;
var port;
before(function(done) {
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"adminPath"});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function() {
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.close();
done();
});
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("keep alives", function() { describe("keep alives", function() {
var server; var server;

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 205 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,17 +17,17 @@ var should = require("should");
var sinon = require("sinon"); var sinon = require("sinon");
var util = require("util"); var util = require("util");
var log = require("../../red/log");
describe("red/log", function() { describe("red/log", function() {
it('can be required without errors', function() { it('can be required without errors', function() {
require("../../red/log"); require("../../red/log");
}); });
var log = require("../../red/log");
var sett = {logging: { console: { level: 'metric', metrics: true } } };
log.init(sett);
beforeEach(function () { beforeEach(function () {
var spy = sinon.spy(util, 'log'); var spy = sinon.stub(util, 'log', function(arg){});
var settings = {logging: { console: { level: 'metric', metrics: true } } };
log.init(settings);
}); });
afterEach(function() { afterEach(function() {
@ -36,17 +36,27 @@ describe("red/log", function() {
it('it can raise an error', function() { it('it can raise an error', function() {
var ret = log.error("This is an error"); var ret = log.error("This is an error");
sinon.assert.calledWithMatch(util.log,""); sinon.assert.calledWithMatch(util.log,"[error] This is an error");
}); });
it('it can raise a trace', function() { it('it can raise a trace', function() {
var ret = log.trace("This is a trace"); var ret = log.trace("This is a trace");
sinon.assert.calledWithMatch(util.log,""); sinon.assert.calledWithMatch(util.log,"[trace] This is a trace");
}); });
it('it can raise a debug', function() { it('it can raise a debug', function() {
var ret = log.debug("This is a debug"); var ret = log.debug("This is a debug");
sinon.assert.calledWithMatch(util.log,""); sinon.assert.calledWithMatch(util.log,"[debug] This is a debug");
});
it('it can raise a info', function() {
var ret = log.info("This is an info");
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
});
it('it can raise a warn', function() {
var ret = log.warn("This is a warn");
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
}); });
it('it can raise a metric', function() { it('it can raise a metric', function() {
@ -57,17 +67,81 @@ describe("red/log", function() {
metrics.msgid = "12345"; metrics.msgid = "12345";
metrics.value = "the metric payload"; metrics.value = "the metric payload";
var ret = log.log(metrics); var ret = log.log(metrics);
sinon.assert.calledWithMatch(util.log,""); util.log.calledOnce.should.be.true;
util.log.firstCall.args[0].indexOf("[metric] ").should.equal(0);
var body = JSON.parse(util.log.firstCall.args[0].substring(9));
body.should.have.a.property("nodeid","testid");
body.should.have.a.property("event","node.test.testevent");
body.should.have.a.property("msgid","12345");
body.should.have.a.property("value","the metric payload");
body.should.have.a.property("timestamp");
body.should.have.a.property("level",log.METRIC);
}); });
it('it checks metrics are enabled', function() { it('it checks metrics are enabled', function() {
log.metric().should.equal(true); log.metric().should.equal(true);
var sett = {logging: { console: { level: 'info', metrics: false } } }; var sett = {logging: { console: { level: 'info', metrics: false } } };
log.init(sett); log.init(sett);
});
it('it checks metrics are disabled', function() {
log.metric().should.equal(false); log.metric().should.equal(false);
}); });
it('it logs node type and name if provided',function() {
log.log({level:log.INFO,type:"nodeType",msg:"test",name:"nodeName",id:"nodeId"});
util.log.calledOnce.should.be.true;
util.log.firstCall.args[0].indexOf("[nodeType:nodeName]").should.not.equal(-1);
});
it('it logs node type and id if no name provided',function() {
log.log({level:log.INFO,type:"nodeType",msg:"test",id:"nodeId"});
util.log.calledOnce.should.be.true;
util.log.firstCall.args[0].indexOf("[nodeType:nodeId]").should.not.equal(-1);
});
it('ignores lower level messages and metrics', function() {
var settings = {logging: { console: { level: 'warn', metrics: false } } };
log.init(settings);
log.error("This is an error");
log.warn("This is a warn");
log.info("This is an info");
log.debug("This is a debug");
log.trace("This is a trace");
log.log({level:log.METRIC,msg:"testMetric"});
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
sinon.assert.neverCalledWithMatch(util.log,"[info] This is an info");
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
sinon.assert.neverCalledWithMatch(util.log,"[metric] ");
});
it('ignores lower level messages but accepts metrics', function() {
var settings = {logging: { console: { level: 'log', metrics: true } } };
log.init(settings);
log.error("This is an error");
log.warn("This is a warn");
log.info("This is an info");
log.debug("This is a debug");
log.trace("This is a trace");
log.log({level:log.METRIC,msg:"testMetric"});
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
sinon.assert.calledWithMatch(util.log,"[metric] ");
});
it('default settings set to INFO and metrics off', function() {
log.init({logging:{}});
log.error("This is an error");
log.warn("This is a warn");
log.info("This is an info");
log.debug("This is a debug");
log.trace("This is a trace");
log.log({level:log.METRIC,msg:"testMetric"});
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
sinon.assert.neverCalledWithMatch(util.log,"[metric] ");
});
}); });

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,7 +23,9 @@ var comms = require("../../red/comms");
var redNodes = require("../../red/nodes"); var redNodes = require("../../red/nodes");
var api = require("../../red/api"); var api = require("../../red/api");
var server = require("../../red/server"); var server = require("../../red/server");
var storage = require("../../red/storage");
var settings = require("../../red/settings");
var log = require("../../red/log");
describe("red/server", function() { describe("red/server", function() {
var commsMessages = []; var commsMessages = [];
@ -44,9 +46,8 @@ describe("red/server", function() {
it("initialises components", function() { it("initialises components", function() {
var commsInit = sinon.stub(comms,"init",function() {}); var commsInit = sinon.stub(comms,"init",function() {});
var dummyServer = {}; var dummyServer = {};
server.init(dummyServer,{httpAdminRoot:"/"}); server.init(dummyServer,{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
commsInit.called.should.be.true; commsInit.called.should.be.true;
@ -58,6 +59,173 @@ describe("red/server", function() {
commsInit.restore(); commsInit.restore();
}); });
describe("start",function() {
var commsInit;
var storageInit;
var settingsLoad;
var apiInit;
var logMetric;
var logWarn;
var logInfo;
var logLog;
var redNodesInit;
var redNodesLoad;
var redNodesCleanModuleList;
var redNodesGetNodeList;
var redNodesLoadFlows;
var commsStart;
beforeEach(function() {
commsInit = sinon.stub(comms,"init",function() {});
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();});
apiInit = sinon.stub(api,"init",function() {});
logMetric = sinon.stub(log,"metric",function() { return false; });
logWarn = sinon.stub(log,"warn",function() { });
logInfo = sinon.stub(log,"info",function() { });
logLog = sinon.stub(log,"log",function(m) {});
redNodesInit = sinon.stub(redNodes,"init", function() {});
redNodesLoad = sinon.stub(redNodes,"load", function() {return when.resolve()});
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {});
commsStart = sinon.stub(comms,"start",function(){});
});
afterEach(function() {
commsInit.restore();
storageInit.restore();
apiInit.restore();
logMetric.restore();
logWarn.restore();
logInfo.restore();
logLog.restore();
redNodesInit.restore();
redNodesLoad.restore();
redNodesGetNodeList.restore();
redNodesCleanModuleList.restore();
redNodesLoadFlows.restore();
commsStart.restore();
});
it("reports errored/missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {
return [
{ err:"errored",name:"errName" }, // error
{ module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing
];
});
server.init({},{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() {
try {
apiInit.calledOnce.should.be.true;
storageInit.calledOnce.should.be.true;
redNodesInit.calledOnce.should.be.true;
redNodesLoad.calledOnce.should.be.true;
commsStart.calledOnce.should.be.true;
redNodesLoadFlows.calledOnce.should.be.true;
logWarn.calledWithMatch("Failed to register 1 node type");
logWarn.calledWithMatch("Missing node modules");
logWarn.calledWithMatch(" - module: typeA, typeB");
redNodesCleanModuleList.calledOnce.should.be.true;
done();
} catch(err) {
done(err);
}
});
});
it("initiates load of missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {
return [
{ err:"errored",name:"errName" }, // error
{ err:"errored",name:"errName" }, // error
{ module:"module",enabled:true,loaded:false,types:["typeA","typeB"]}, // missing
{ module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing
];
});
var serverInstallModule = sinon.stub(server,"installModule",function(name) { return when.resolve();});
server.init({},{testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() {
try {
apiInit.calledOnce.should.be.true;
logWarn.calledWithMatch("Failed to register 2 node types");
logWarn.calledWithMatch("Missing node modules");
logWarn.calledWithMatch(" - module: typeA, typeB");
logWarn.calledWithMatch(" - node-red: typeC, typeD");
redNodesCleanModuleList.calledOnce.should.be.false;
serverInstallModule.calledOnce.should.be.true;
serverInstallModule.calledWithMatch("module");
done();
} catch(err) {
done(err);
} finally {
serverInstallModule.restore();
}
});
});
it("reports errored modules when verbose is enabled",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {
return [
{ err:"errored",name:"errName" } // error
];
});
server.init({},{testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() {
try {
apiInit.calledOnce.should.be.true;
logWarn.neverCalledWithMatch("Failed to register 1 node type");
logWarn.calledWithMatch("[errName] errored");
done();
} catch(err) {
done(err);
}
});
});
it("reports runtime metrics",function(done) {
var commsStop = sinon.stub(comms,"stop",function() {} );
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
logMetric.restore();
logMetric = sinon.stub(log,"metric",function() { return true; });
server.init({},{testSettings: true, runtimeMetricInterval:400, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() {
setTimeout(function() {
try {
apiInit.calledOnce.should.be.true;
logLog.args.should.have.lengthOf(3);
logLog.args[0][0].should.have.property("level",log.METRIC);
logLog.args[0][0].should.have.property("event","runtime.memory.rss");
logLog.args[1][0].should.have.property("level",log.METRIC);
logLog.args[1][0].should.have.property("event","runtime.memory.heapTotal");
logLog.args[2][0].should.have.property("level",log.METRIC);
logLog.args[2][0].should.have.property("event","runtime.memory.heapUsed");
done();
} catch(err) {
done(err);
} finally {
server.stop();
commsStop.restore();
stopFlows.restore();
}
},500);
});
});
it("doesn't init api if httpAdminRoot set to false",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
server.init({},{testSettings: true, httpAdminRoot:false, load:function() { return when.resolve();}});
server.start().then(function() {
setTimeout(function() {
try {
apiInit.calledOnce.should.be.false;
done();
} catch(err) {
done(err);
}
},500);
});
});
});
it("stops components", function() { it("stops components", function() {
var commsStop = sinon.stub(comms,"stop",function() {} ); var commsStop = sinon.stub(comms,"stop",function() {} );
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );

View File

@ -80,11 +80,13 @@ describe("red/settings", function() {
c: [1,2,3] c: [1,2,3]
} }
var savedSettings = null; var savedSettings = null;
var saveCount = 0;
var storage = { var storage = {
getSettings: function() { getSettings: function() {
return when.resolve({globalA:789}); return when.resolve({globalA:789});
}, },
saveSettings: function(settings) { saveSettings: function(settings) {
saveCount++;
savedSettings = settings; savedSettings = settings;
return when.resolve(); return when.resolve();
} }
@ -102,8 +104,14 @@ describe("red/settings", function() {
settings.available().should.be.true; settings.available().should.be.true;
settings.get("globalA").should.equal(789); settings.get("globalA").should.equal(789);
settings.set("globalA","abc").then(function() { settings.set("globalA","abc").then(function() {
savedSettings.globalA.should.equal("abc");
saveCount.should.equal(1);
settings.set("globalA","abc").then(function() {
savedSettings.globalA.should.equal("abc"); savedSettings.globalA.should.equal("abc");
// setting to existing value should not trigger save
saveCount.should.equal(1);
done(); done();
});
}); });
}).otherwise(function(err) { }).otherwise(function(err) {
done(err); done(err);