Remove known unused files

This commit is contained in:
andrew.greene
2021-12-08 18:01:31 -07:00
parent 419a81034c
commit b1daa8932a
367 changed files with 1 additions and 75791 deletions

View File

@@ -1,234 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var context = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/context");
describe("api/admin/context", function () {
var app = undefined;
before(function () {
app = express();
app.use(bodyParser.json());
app.get("/context/:scope(global)", context.get);
app.get("/context/:scope(global)/*", context.get);
app.get("/context/:scope(node|flow)/:id", context.get);
app.get("/context/:scope(node|flow)/:id/*", context.get);
app.delete("/context/:scope(global)/*", context.delete);
app.delete("/context/:scope(node|flow)/:id/*", context.delete);
});
describe("get", function () {
var gContext = {
default: { abc: { msg: '111', format: 'number' } },
file: { abc: { msg: '222', format: 'number' } }
};
var fContext = {
default: { bool: { msg: 'true', format: 'boolean' } },
file: { string: { msg: 'aaaa', format: 'string[7]' } }
};
var nContext = { msg: "1", format: "number" };
var stub = sinon.stub();
before(function () {
context.init({
context: {
getValue: stub
}
});
});
afterEach(function () {
stub.reset();
});
it('should call context.getValue to get global contexts', function (done) {
stub.returns(Promise.resolve(gContext));
request(app)
.get('/context/global')
.set('Accept', 'application/json')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'global');
stub.args[0][0].should.have.property('id', undefined);
stub.args[0][0].should.have.property('key', undefined);
stub.args[0][0].should.have.property('store', undefined);
var body = res.body;
body.should.eql(gContext);
done();
});
});
it('should call context.getValue to get flow contexts', function (done) {
stub.returns(Promise.resolve(fContext));
request(app)
.get('/context/flow/1234/')
.set('Accept', 'application/json')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'flow');
stub.args[0][0].should.have.property('id', '1234');
stub.args[0][0].should.have.property('key', undefined);
stub.args[0][0].should.have.property('store', undefined);
var body = res.body;
body.should.eql(fContext);
done();
});
});
it('should call context.getValue to get a node context', function (done) {
stub.returns(Promise.resolve(nContext));
request(app)
.get('/context/node/5678/foo?store=file')
.set('Accept', 'application/json')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'node');
stub.args[0][0].should.have.property('id', '5678');
stub.args[0][0].should.have.property('key', 'foo');
stub.args[0][0].should.have.property('store', 'file');
var body = res.body;
body.should.eql(nContext);
done();
});
});
it('should handle error which context.getValue causes', function (done) {
var stubbedResult = Promise.reject('error');
stubbedResult.catch(function() {});
stub.returns(stubbedResult);
request(app)
.get('/context/global')
.set('Accept', 'application/json')
.expect(400)
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('code', 'unexpected_error');
res.body.should.has.a.property('message', 'error');
done();
});
});
});
describe("delete", function () {
var stub = sinon.stub();
before(function () {
context.init({
context: {
delete: stub
}
});
});
afterEach(function () {
stub.reset();
});
it('should call context.delete to delete a global context', function (done) {
stub.returns(Promise.resolve());
request(app)
.delete('/context/global/abc?store=default')
.expect(204)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'global');
stub.args[0][0].should.have.property('id', undefined);
stub.args[0][0].should.have.property('key', 'abc');
stub.args[0][0].should.have.property('store', 'default');
done();
});
});
it('should call context.delete to delete a flow context', function (done) {
stub.returns(Promise.resolve());
request(app)
.delete('/context/flow/1234/abc?store=file')
.expect(204)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'flow');
stub.args[0][0].should.have.property('id', '1234');
stub.args[0][0].should.have.property('key', 'abc');
stub.args[0][0].should.have.property('store', 'file');
done();
});
});
it('should call context.delete to delete a node context', function (done) {
stub.returns(Promise.resolve());
request(app)
.delete('/context/node/5678/foo?store=file')
.expect(204)
.end(function (err, res) {
if (err) {
return done(err);
}
stub.args[0][0].should.have.property('user', undefined);
stub.args[0][0].should.have.property('scope', 'node');
stub.args[0][0].should.have.property('id', '5678');
stub.args[0][0].should.have.property('key', 'foo');
stub.args[0][0].should.have.property('store', 'file');
done();
});
});
it('should handle error which context.delete causes', function (done) {
var stubbedResult = Promise.reject('error');
stubbedResult.catch(function() {});
stub.returns(stubbedResult);
request(app)
.delete('/context/global/abc?store=default')
.expect(400)
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('code', 'unexpected_error');
res.body.should.has.a.property('message', 'error');
done();
});
});
});
});

View File

@@ -1,248 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var flow = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flow");
describe("api/admin/flow", function() {
var app;
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/flow/:id",flow.get);
app.post("/flow",flow.post);
app.put("/flow/:id",flow.put);
app.delete("/flow/:id",flow.delete);
});
describe("get", function() {
before(function() {
var opts;
flow.init({
flows: {
getFlow: function(_opts) {
opts = _opts;
if (opts.id === '123') {
return Promise.resolve({id:'123'});
} else {
var err = new Error("message");
err.code = "not_found";
err.status = 404;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
});
})
it('gets a known flow', function(done) {
request(app)
.get('/flow/123')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('id','123');
done();
});
})
it('404s an unknown flow', function(done) {
request(app)
.get('/flow/456')
.set('Accept', 'application/json')
.expect(404)
.end(done);
})
});
describe("add", function() {
var opts;
before(function() {
flow.init({
flows: {
addFlow: function(_opts) {
opts = _opts;
if (opts.flow.id === "123") {
return Promise.resolve('123')
} else {
var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
});
})
it('adds a new flow', function(done) {
request(app)
.post('/flow')
.set('Accept', 'application/json')
.send({id:'123'})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('id','123');
done();
});
})
it('400 an invalid flow', function(done) {
request(app)
.post('/flow')
.set('Accept', 'application/json')
.send({id:'error'})
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','random error');
done();
});
})
})
describe("update", function() {
var opts;
before(function() {
flow.init({
flows: {
updateFlow: function(_opts) {
opts = _opts;
if (opts.id === "123") {
return Promise.resolve('123')
} else {
var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
});
})
it('updates an existing flow', function(done) {
request(app)
.put('/flow/123')
.set('Accept', 'application/json')
.send({id:'123'})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('id','123');
opts.should.have.property('id','123');
opts.should.have.property('flow',{id:'123'})
done();
});
})
it('400 an invalid flow', function(done) {
request(app)
.put('/flow/456')
.set('Accept', 'application/json')
.send({id:'456'})
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','random error');
done();
});
})
})
describe("delete", function() {
var opts;
before(function() {
flow.init({
flows: {
deleteFlow: function(_opts) {
opts = _opts;
if (opts.id === "123") {
return Promise.resolve()
} else {
var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
});
})
it('deletes an existing flow', function(done) {
request(app)
.del('/flow/123')
.set('Accept', 'application/json')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('id','123');
done();
});
})
it('400 an invalid flow', function(done) {
request(app)
.del('/flow/456')
.set('Accept', 'application/json')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','random error');
done();
});
})
})
});

View File

@@ -1,211 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var flows = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flows");
describe("api/admin/flows", function() {
var app;
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/flows",flows.get);
app.post("/flows",flows.post);
});
it('returns flow - v1', function(done) {
flows.init({
flows:{
getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
}
});
request(app)
.get('/flows')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('returns flow - v2', function(done) {
flows.init({
flows:{
getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
}
});
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('rev','123');
res.body.should.have.a.property('flows');
res.body.flows.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('returns flow - bad version', function(done) {
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('code','invalid_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('sets flows - default - v1', function(done) {
var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({
flows:{
setFlows: setFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[0].should.have.property('deploymentType','full');
done();
});
});
it('sets flows - non-default - v1', function(done) {
var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({
flows:{
setFlows: setFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-Deployment-Type','nodes')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[0].should.have.property('deploymentType','nodes');
done();
});
});
it('set flows - rejects mismatched revision - v2', function(done) {
flows.init({
flows:{
setFlows: function() {
var err = new Error("mismatch");
err.code = "version_mismatch";
err.status = 409;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:456,flows:[4,5,6]})
.expect(409)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("code","version_mismatch");
done();
});
});
it('sets flow - bad version', function(done) {
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('code','invalid_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('reloads flows', function(done) {
var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({
flows:{
setFlows: setFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-Deployment-Type','reload')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.not.have.property('flows');
done();
});
});
});

View File

@@ -1,458 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var NR_TEST_UTILS = require("nr-test-utils");
var adminApi = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin");
var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth");
var nodes = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/nodes");
var flows = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flows");
var flow = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flow");
var context = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/context");
/**
* Ensure all API routes are correctly mounted, with the expected permissions checks
*/
describe("api/admin/index", function() {
describe("Ensure all API routes are correctly mounted, with the expected permissions checks", function() {
var app;
var mockList = [
flows,flow,nodes,context
];
var permissionChecks = {};
var lastRequest;
var stubApp = function(req,res,next) {
lastRequest = req;
res.status(200).end();
};
before(function() {
mockList.forEach(function(m) {
sinon.stub(m,"init").callsFake(function(){});
});
sinon.stub(auth,"needsPermission").callsFake(function(permission) {
return function(req,res,next) {
permissionChecks[permission] = (permissionChecks[permission]||0)+1;
next();
};
});
sinon.stub(flows,"get").callsFake(stubApp);
sinon.stub(flows,"post").callsFake(stubApp);
sinon.stub(flow,"get").callsFake(stubApp);
sinon.stub(flow,"post").callsFake(stubApp);
sinon.stub(flow,"delete").callsFake(stubApp);
sinon.stub(flow,"put").callsFake(stubApp);
sinon.stub(nodes,"getAll").callsFake(stubApp);
sinon.stub(nodes,"post").callsFake(stubApp);
sinon.stub(nodes,"getModule").callsFake(stubApp);
sinon.stub(nodes,"putModule").callsFake(stubApp);
sinon.stub(nodes,"delete").callsFake(stubApp);
sinon.stub(nodes,"getSet").callsFake(stubApp);
sinon.stub(nodes,"putSet").callsFake(stubApp);
sinon.stub(nodes,"getModuleCatalog").callsFake(stubApp);
sinon.stub(nodes,"getModuleCatalogs").callsFake(stubApp);
sinon.stub(context,"get").callsFake(stubApp);
sinon.stub(context,"delete").callsFake(stubApp);
});
after(function() {
mockList.forEach(function(m) {
m.init.restore();
});
auth.needsPermission.restore();
flows.get.restore();
flows.post.restore();
flow.get.restore();
flow.post.restore();
flow.delete.restore();
flow.put.restore();
nodes.getAll.restore();
nodes.post.restore();
nodes.getModule.restore();
nodes.putModule.restore();
nodes.delete.restore();
nodes.getSet.restore();
nodes.putSet.restore();
nodes.getModuleCatalog.restore();
nodes.getModuleCatalogs.restore();
context.get.restore();
context.delete.restore();
});
before(function() {
app = adminApi.init({},{});
});
beforeEach(function() {
permissionChecks = {};
});
it('GET /flows', function(done) {
request(app).get("/flows").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.read',1);
done();
});
});
it('POST /flows', function(done) {
request(app).post("/flows").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.write',1);
done();
});
});
it('GET /flow/1234', function(done) {
request(app).get("/flow/1234").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.read',1);
lastRequest.params.should.have.property('id','1234');
done();
});
});
it('POST /flow', function(done) {
request(app).post("/flow").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.write',1);
done();
});
});
it('DELETE /flow/1234', function(done) {
request(app).del("/flow/1234").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.write',1);
lastRequest.params.should.have.property('id','1234');
done();
});
});
it('PUT /flow/1234', function(done) {
request(app).put("/flow/1234").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('flows.write',1);
lastRequest.params.should.have.property('id','1234');
done();
});
});
it('GET /nodes', function(done) {
request(app).get("/nodes").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
done();
});
});
it('POST /nodes', function(done) {
request(app).post("/nodes").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
done();
});
});
it('GET /nodes/module', function(done) {
request(app).get("/nodes/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'module');
done();
});
});
it('GET /nodes/@scope/module', function(done) {
request(app).get("/nodes/@scope/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'@scope/module');
done();
});
});
it('PUT /nodes/module', function(done) {
request(app).put("/nodes/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'module');
done();
});
});
it('PUT /nodes/@scope/module', function(done) {
request(app).put("/nodes/@scope/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'@scope/module');
done();
});
});
it('DELETE /nodes/module', function(done) {
request(app).del("/nodes/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'module');
done();
});
});
it('DELETE /nodes/@scope/module', function(done) {
request(app).del("/nodes/@scope/module").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'@scope/module');
done();
});
});
it('GET /nodes/module/set', function(done) {
request(app).get("/nodes/module/set").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'module');
lastRequest.params.should.have.property(2,'set');
done();
});
});
it('GET /nodes/@scope/module/set', function(done) {
request(app).get("/nodes/@scope/module/set").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'@scope/module');
lastRequest.params.should.have.property(2,'set');
done();
});
});
it('PUT /nodes/module/set', function(done) {
request(app).put("/nodes/module/set").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'module');
lastRequest.params.should.have.property(2,'set');
done();
});
});
it('PUT /nodes/@scope/module/set', function(done) {
request(app).put("/nodes/@scope/module/set").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.write',1);
lastRequest.params.should.have.property(0,'@scope/module');
lastRequest.params.should.have.property(2,'set');
done();
});
});
it('GET /nodes/messages', function(done) {
request(app).get("/nodes/messages").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
done();
});
});
it('GET /nodes/module/set/messages', function(done) {
request(app).get("/nodes/module/set/messages").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'module/set');
done();
});
});
it('GET /nodes/@scope/module/set/messages', function(done) {
request(app).get("/nodes/@scope/module/set/messages").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('nodes.read',1);
lastRequest.params.should.have.property(0,'@scope/module/set');
done();
});
});
it('GET /context/global', function(done) {
request(app).get("/context/global").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','global');
done();
});
});
it('GET /context/global/key?store=memory', function(done) {
request(app).get("/context/global/key?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','global');
lastRequest.params.should.have.property(0,'key');
lastRequest.query.should.have.property('store','memory');
done();
});
});
it('GET /context/flow/1234', function(done) {
request(app).get("/context/flow/1234").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','flow');
lastRequest.params.should.have.property('id','1234');
done();
});
});
it('GET /context/flow/1234/key?store=memory', function(done) {
request(app).get("/context/flow/1234/key?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','flow');
lastRequest.params.should.have.property('id','1234');
lastRequest.params.should.have.property(0,'key');
lastRequest.query.should.have.property('store','memory');
done();
});
});
it('GET /context/node/5678', function(done) {
request(app).get("/context/node/5678").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','node');
lastRequest.params.should.have.property('id','5678');
done();
});
});
it('GET /context/node/5678/foo?store=memory', function(done) {
request(app).get("/context/node/5678/foo?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.read',1);
lastRequest.params.should.have.property('scope','node');
lastRequest.params.should.have.property('id','5678');
lastRequest.params.should.have.property(0,'foo');
lastRequest.query.should.have.property('store','memory');
done();
});
});
it('DELETE /context/global/key?store=memory', function(done) {
request(app).del("/context/global/key?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.write',1);
lastRequest.params.should.have.property('scope','global');
lastRequest.params.should.have.property(0,'key');
lastRequest.query.should.have.property('store','memory');
done();
});
});
it('DELETE /context/flow/1234/key?store=memory', function(done) {
request(app).del("/context/flow/1234/key?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.write',1);
lastRequest.params.should.have.property('scope','flow');
lastRequest.params.should.have.property('id','1234');
lastRequest.params.should.have.property(0,'key');
lastRequest.query.should.have.property('store','memory');
done();
});
});
it('DELETE /context/node/5678/foo?store=memory', function(done) {
request(app).del("/context/node/5678/foo?store=memory").expect(200).end(function(err,res) {
if (err) {
return done(err);
}
permissionChecks.should.have.property('context.write',1);
lastRequest.params.should.have.property('scope','node');
lastRequest.params.should.have.property('id','5678');
lastRequest.params.should.have.property(0,'foo');
lastRequest.query.should.have.property('store','memory');
done();
});
});
});
});

View File

@@ -1,497 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var nodes = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/nodes");
var apiUtil = NR_TEST_UTILS.require("@node-red/editor-api/lib/util");
describe("api/admin/nodes", function() {
var app;
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/nodes",nodes.getAll);
app.post("/nodes",nodes.post);
app.get(/\/nodes\/messages/,nodes.getModuleCatalogs);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,nodes.getModuleCatalog);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.getModule);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
app.get("/getIcons",nodes.getIcons);
app.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.delete);
sinon.stub(apiUtil,"determineLangFromHeaders").callsFake(function() {
return "en-US";
});
});
after(function() {
apiUtil.determineLangFromHeaders.restore();
})
describe('get nodes', function() {
it('returns node list', function(done) {
nodes.init({
settings: {},
nodes:{
getNodeList: function() {
return Promise.resolve([1,2,3]);
}
}
});
request(app)
.get('/nodes')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.be.an.Array();
res.body.should.have.lengthOf(3);
done();
});
});
it('returns node configs', function(done) {
nodes.init({
settings: {},
nodes:{
getNodeConfigs: function() {
return Promise.resolve("<script></script>");
}
},
i18n: {
determineLangFromHeaders: function(){}
}
});
request(app)
.get('/nodes')
.set('Accept', 'text/html')
.expect(200)
.expect("<script></script>")
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns node module info', function(done) {
nodes.init({
settings: {},
nodes:{
getModuleInfo: function(opts) {
return Promise.resolve({"node-red":{name:"node-red"}}[opts.module]);
}
}
});
request(app)
.get('/nodes/node-red')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
done();
});
});
it('returns 404 for unknown module', function(done) {
nodes.init({
settings: {},
nodes:{
getModuleInfo: function(opts) {
var errInstance = new Error("Not Found");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
}
}
});
request(app)
.get('/nodes/node-blue')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns individual node info', function(done) {
nodes.init({
settings: {},
nodes:{
getNodeInfo: function(opts) {
return Promise.resolve({"node-red/123":{id:"node-red/123"}}[opts.id]);
}
}
});
request(app)
.get('/nodes/node-red/123')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("id","node-red/123");
done();
});
});
it('returns individual node configs', function(done) {
nodes.init({
settings: {},
nodes:{
getNodeConfig: function(opts) {
return Promise.resolve({"node-red/123":"<script></script>"}[opts.id]);
}
},
i18n: {
determineLangFromHeaders: function(){}
}
});
request(app)
.get('/nodes/node-red/123')
.set('Accept', 'text/html')
.expect(200)
.expect("<script></script>")
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 404 for unknown node', function(done) {
nodes.init({
settings: {},
nodes:{
getNodeInfo: function(opts) {
var errInstance = new Error("Not Found");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
}
}
});
request(app)
.get('/nodes/node-red/456')
.set('Accept', 'application/json')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
});
describe('install', function() {
it('installs the module and returns module info', function(done) {
var opts;
nodes.init({
settings: {},
nodes:{
addModule: function(_opts) {
opts = _opts;
return Promise.resolve({
name:"foo",
nodes:[{id:"123"}]
});
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","foo");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("id","123");
opts.should.have.property("module","foo");
opts.should.have.property("version","1.2.3");
opts.should.have.property("url","https://example/foo-1.2.3.tgz");
done();
});
});
it('returns error', function(done) {
nodes.init({
settings: {},
nodes:{
addModule: function(opts) {
var errInstance = new Error("Message");
errInstance.code = "random_error";
errInstance.status = 400;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.a.property('code','random_error');
done();
});
});
});
describe('delete', function() {
it('uninstalls the module', function(done) {
var opts;
nodes.init({
settings: {},
nodes:{
removeModule: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app)
.del('/nodes/123')
.expect(204)
.end(function(err,res) {
if (err) {
throw err;
}
opts.should.have.property("module","123");
done();
});
});
it('returns error', function(done) {
nodes.init({
settings: {},
nodes:{
removeModule: function(opts) {
var errInstance = new Error("Message");
errInstance.code = "random_error";
errInstance.status = 400;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
}
}
});
request(app)
.del('/nodes/123')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.a.property('code','random_error');
done();
});
});
});
describe('enable/disable node set', function() {
it('returns 400 for invalid request payload', function(done) {
nodes.init({
settings: {},
nodes:{
setNodeSetState: function(opts) {return Promise.resolve()}
}
});
request(app)
.put('/nodes/node-red/foo')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("code","invalid_request");
res.body.should.have.property("message","Invalid request");
done();
});
});
it('sets node state and returns node info', function(done) {
var opts;
nodes.init({
settings: {},
nodes:{
setNodeSetState: function(_opts) {
opts = _opts;
return Promise.resolve({id:"123",enabled: true });
}
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",true);
opts.should.have.property("enabled",true);
opts.should.have.property("id","node-red/foo");
done();
});
});
});
describe('enable/disable module' ,function() {
it('returns 400 for invalid request payload', function(done) {
nodes.init({
settings: {},
nodes:{
setModuleState: function(opts) {return Promise.resolve()}
}
});
request(app)
.put('/nodes/node-red')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("code","invalid_request");
res.body.should.have.property("message","Invalid request");
done();
});
});
it('sets module state and returns module info', function(done) {
var opts;
nodes.init({
settings: {},
nodes:{
setModuleState: function(_opts) {
opts = _opts;
return Promise.resolve({name:"node-red"});
}
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
opts.should.have.property("enabled",true);
opts.should.have.property("module","node-red");
done();
});
});
});
describe('get icons', function() {
it('returns icon list', function(done) {
nodes.init({
settings: {},
nodes:{
getIconList: function() {
return Promise.resolve({module:[1,2,3]});
}
}
});
request(app)
.get('/getIcons')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("module");
res.body.module.should.be.an.Array();
res.body.module.should.have.lengthOf(3);
done();
});
});
});
describe('get module messages', function() {
it('returns message catalog', function(done) {
nodes.init({
settings: {},
nodes:{
getModuleCatalog: function(opts) {
return Promise.resolve({a:123});
}
}
});
request(app)
.get('/nodes/module/set/messages')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.eql({a:123});
done();
});
});
it('returns all node catalogs', function(done) {
nodes.init({
settings: {},
nodes:{
getModuleCatalogs: function(opts) {
return Promise.resolve({a:1});
}
}
});
request(app)
.get('/nodes/messages')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.eql({a:1});
done();
});
});
})
});

View File

@@ -1,111 +0,0 @@
const should = require("should");
const request = require('supertest');
const express = require('express');
const bodyParser = require("body-parser");
var app;
var NR_TEST_UTILS = require("nr-test-utils");
var plugins = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/plugins");
describe("api/editor/plugins", function() {
const pluginList = [
{
"id": "test-module/test-set",
"enabled": true,
"local": false,
"plugins": [
{
"type": "foo",
"id": "a-plugin",
"module": "test-module"
},
{
"type": "bar",
"id": "a-plugin2",
"module": "test-module"
},
{
"type": "foo",
"id": "a-plugin3",
"module": "test-module"
}
]
},
{
"id": "test-module/test-disabled-set",
"enabled": false,
"local": false,
"plugins": []
}
];
const pluginConfigs = `
<!-- --- [red-plugin:test-module/test-set] --- -->
test-module-config`;
const pluginCatalogs = { "test-module": {"foo": "bar"}};
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/plugins",plugins.getAll);
app.get("/plugins/messages",plugins.getCatalogs);
plugins.init({
plugins: {
getPluginList: async function() { return pluginList },
getPluginConfigs: async function() { return pluginConfigs },
getPluginCatalogs: async function() { return pluginCatalogs }
}
})
});
it('returns the list of plugins', function(done) {
request(app)
.get("/plugins")
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
JSON.stringify(res.body).should.eql(JSON.stringify(pluginList));
done();
} catch(err) {
done(err)
}
});
});
it('returns the plugin configs', function(done) {
request(app)
.get("/plugins")
.set('Accept', 'text/html')
.expect(200)
.expect(pluginConfigs)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('returns the plugin catalogs', function(done) {
request(app)
.get("/plugins/messages")
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
JSON.stringify(res.body).should.eql(JSON.stringify(pluginCatalogs));
done();
} catch(err) {
done(err)
}
});
});
});

View File

@@ -1,93 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require("body-parser");
var sinon = require('sinon');
var app;
var NR_TEST_UTILS = require("nr-test-utils");
var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/settings");
var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme");
describe("api/editor/settings", function() {
before(function() {
sinon.stub(theme,"settings").callsFake(function() { return { existing: 123, test: 456 };});
app = express();
app.use(bodyParser.json());
app.get("/settings",info.runtimeSettings);
});
after(function() {
theme.settings.restore();
});
it('returns the runtime settings', function(done) {
info.init({},{
settings: {
getRuntimeSettings: function(opts) {
return Promise.resolve({
a:1,
b:2,
editorTheme: { existing: 789 }
})
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("a",1);
res.body.should.have.property("b",2);
res.body.should.have.property("editorTheme",{existing: 789, test:456});
done();
});
});
it('returns the runtime settings - disableEditor true', function(done) {
info.init({disableEditor: true},{
settings: {
getRuntimeSettings: function(opts) {
return Promise.resolve({
a:1,
b:2
})
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("a",1);
res.body.should.have.property("b",2);
// no editorTheme if disabledEditor true
res.body.should.not.have.property("editorTheme");
done();
});
});
});

View File

@@ -1,47 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var NR_TEST_UTILS = require("nr-test-utils");
var Clients = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/clients");
describe("api/auth/clients", function() {
it('finds the known editor client',function(done) {
Clients.get("node-red-editor").then(function(client) {
client.should.have.property("id","node-red-editor");
client.should.have.property("secret","not_available");
done();
});
});
it('finds the known admin client',function(done) {
Clients.get("node-red-admin").then(function(client) {
client.should.have.property("id","node-red-admin");
client.should.have.property("secret","not_available");
done();
}).catch(function(err) {
done(err);
});
});
it('returns null for unknown client',function(done) {
Clients.get("unknown-client").then(function(client) {
should.not.exist(client);
done();
}).catch(function(err) {
done(err);
});
});
});

View File

@@ -1,216 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var passport = require("passport");
var NR_TEST_UTILS = require("nr-test-utils");
var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth");
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
var Permissions = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/permissions");
describe("api/auth/index",function() {
describe("ensureClientSecret", function() {
before(function() {
auth.init({},{})
});
it("leaves client_secret alone if not present",function(done) {
var req = {
body: {
client_secret: "test_value"
}
};
auth.ensureClientSecret(req,null,function() {
req.body.should.have.a.property("client_secret","test_value");
done();
})
});
it("applies a default client_secret if not present",function(done) {
var req = {
body: { }
};
auth.ensureClientSecret(req,null,function() {
req.body.should.have.a.property("client_secret","not_available");
done();
})
});
});
describe("revoke", function() {
it("revokes a token", function(done) {
var revokeToken = sinon.stub(Tokens,"revoke").callsFake(function() {
return Promise.resolve();
});
var req = { body: { token: "abcdef" } };
var res = { status: function(resp) {
revokeToken.restore();
resp.should.equal(200);
return {
end: done
}
}};
auth.revoke(req,res);
});
});
describe("login", function() {
beforeEach(function() {
sinon.stub(Tokens,"init").callsFake(function(){});
sinon.stub(Users,"init").callsFake(function(){});
});
afterEach(function() {
Tokens.init.restore();
Users.init.restore();
});
it("returns login details - credentials", function(done) {
auth.init({adminAuth:{type:"credentials"}},{})
auth.login(null,{json: function(resp) {
resp.should.have.a.property("type","credentials");
resp.should.have.a.property("prompts");
resp.prompts.should.have.a.lengthOf(2);
done();
}});
});
it("returns login details - none", function(done) {
auth.init({},{})
auth.login(null,{json: function(resp) {
resp.should.eql({});
done();
}});
});
it("returns login details - strategy", function(done) {
auth.init({adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},{})
auth.login(null,{json: function(resp) {
resp.should.have.a.property("type","strategy");
resp.should.have.a.property("prompts");
resp.prompts.should.have.a.lengthOf(1);
resp.prompts[0].should.have.a.property("type","button");
resp.prompts[0].should.have.a.property("label","test-strategy");
resp.prompts[0].should.have.a.property("icon","test-icon");
done();
}});
});
});
describe("needsPermission", function() {
beforeEach(function() {
sinon.stub(Tokens,"init").callsFake(function(){});
sinon.stub(Users,"init").callsFake(function(){});
});
afterEach(function() {
Tokens.init.restore();
Users.init.restore();
if (passport.authenticate.restore) {
passport.authenticate.restore();
}
if (Permissions.hasPermission.restore) {
Permissions.hasPermission.restore();
}
});
it('no-ops if adminAuth not set', function(done) {
sinon.stub(passport,"authenticate").callsFake(function(scopes,opts) {
return function(req,res,next) {
}
});
auth.init({});
var func = auth.needsPermission("foo");
func({},{},function() {
passport.authenticate.called.should.be.false();
done();
})
});
it('skips auth if req.user undefined', function(done) {
sinon.stub(passport,"authenticate").callsFake(function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission").callsFake(function(perm) { return true });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:null},{},function() {
try {
passport.authenticate.called.should.be.true();
Permissions.hasPermission.called.should.be.false();
done();
} catch(err) {
done(err);
}
})
});
it('passes for valid user permission', function(done) {
sinon.stub(passport,"authenticate").callsFake(function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission").callsFake(function(perm) { return true });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:true,authInfo: { scope: "read"}},{},function() {
try {
passport.authenticate.called.should.be.true();
Permissions.hasPermission.called.should.be.true();
Permissions.hasPermission.lastCall.args[0].should.eql("read");
Permissions.hasPermission.lastCall.args[1].should.eql("foo");
done();
} catch(err) {
done(err);
}
})
});
it('rejects for invalid user permission', function(done) {
sinon.stub(passport,"authenticate").callsFake(function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission").callsFake(function(perm) { return false });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:true,authInfo: { scope: "read"}},{
status: function(status) {
return { end: function() {
try {
status.should.eql(401);
done();
} catch(err) {
done(err);
}
}}
}
},function() {
done(new Error("hasPermission unexpected passed"))
});
});
});
});

View File

@@ -1,59 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var NR_TEST_UTILS = require("nr-test-utils");
var permissions = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/permissions");
describe("api/auth/permissions", function() {
describe("hasPermission", function() {
it('a user with no permissions',function() {
permissions.hasPermission([],"*").should.be.false();
});
it('a user with global permissions',function() {
permissions.hasPermission("*","read").should.be.true();
permissions.hasPermission(["*"],"write").should.be.true();
});
it('a user with read permissions',function() {
permissions.hasPermission(["read"],"read").should.be.true();
permissions.hasPermission(["read"],"node.read").should.be.true();
permissions.hasPermission(["read"],"write").should.be.false();
permissions.hasPermission(["read"],"node.write").should.be.false();
permissions.hasPermission(["*.read"],"read").should.be.true();
permissions.hasPermission(["*.read"],"node.read").should.be.true();
permissions.hasPermission(["*.read"],"write").should.be.false();
permissions.hasPermission(["*.read"],"node.write").should.be.false();
});
it('a user with foo permissions',function() {
permissions.hasPermission("foo","foo").should.be.true();
});
it('an array of permissions', function() {
permissions.hasPermission(["*"],["foo.read","foo.write"]).should.be.true();
permissions.hasPermission("read",["foo.read","foo.write"]).should.be.false();
permissions.hasPermission("read",["foo.read","bar.read"]).should.be.true();
permissions.hasPermission(["flows.read"],["flows.read"]).should.be.true();
permissions.hasPermission(["flows.read"],["flows.write"]).should.be.false();
permissions.hasPermission(["flows.read","nodes.write"],["flows.write"]).should.be.false();
permissions.hasPermission(["flows.read","nodes.write"],["nodes.write"]).should.be.true();
});
it('permits an empty permission', function() {
permissions.hasPermission("*","").should.be.true();
permissions.hasPermission("read",[""]).should.be.true();
});
});
});

View File

@@ -1,327 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var strategies = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/strategies");
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
var Clients = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/clients");
describe("api/auth/strategies", function() {
describe("Password Token Exchange", function() {
var userAuthentication;
afterEach(function() {
if (userAuthentication) {
userAuthentication.restore();
userAuthentication = null;
}
});
it('Handles authentication failure',function(done) {
userAuthentication = sinon.stub(Users,"authenticate").callsFake(function(username,password) {
return Promise.resolve(null);
});
strategies.passwordTokenExchange({},"user","password","scope",function(err,token) {
try {
should.not.exist(err);
token.should.be.false();
done();
} catch(e) {
done(e);
}
});
});
it('Handles scope overreach',function(done) {
userAuthentication = sinon.stub(Users,"authenticate").callsFake(function(username,password) {
return Promise.resolve({username:"user",permissions:"read"});
});
strategies.passwordTokenExchange({},"user","password","*",function(err,token) {
try {
should.not.exist(err);
token.should.be.false();
done();
} catch(e) {
done(e);
}
});
});
it('Creates new token on authentication success',function(done) {
userAuthentication = sinon.stub(Users,"authenticate").callsFake(function(username,password) {
return Promise.resolve({username:"user",permissions:"*"});
});
var tokenDetails = {};
var tokenCreate = sinon.stub(Tokens,"create").callsFake(function(username,client,scope) {
tokenDetails.username = username;
tokenDetails.client = client;
tokenDetails.scope = scope;
return Promise.resolve({accessToken: "123456"});
});
strategies.passwordTokenExchange({id:"myclient"},"user","password","read",function(err,token) {
try {
should.not.exist(err);
token.should.equal("123456");
tokenDetails.should.have.property("username","user");
tokenDetails.should.have.property("client","myclient");
tokenDetails.should.have.property("scope","read");
done();
} catch(e) {
done(e);
} finally {
tokenCreate.restore();
}
});
});
});
describe("Anonymous Strategy", function() {
it('Succeeds if anon user enabled',function(done) {
var userDefault = sinon.stub(Users,"default").callsFake(function() {
return Promise.resolve("anon");
});
strategies.anonymousStrategy._success = strategies.anonymousStrategy.success;
strategies.anonymousStrategy.success = function(user) {
user.should.equal("anon");
strategies.anonymousStrategy.success = strategies.anonymousStrategy._success;
delete strategies.anonymousStrategy._success;
done();
};
strategies.anonymousStrategy.authenticate({});
});
it('Fails if anon user not enabled',function(done) {
var userDefault = sinon.stub(Users,"default").callsFake(function() {
return Promise.resolve(null);
});
strategies.anonymousStrategy._fail = strategies.anonymousStrategy.fail;
strategies.anonymousStrategy.fail = function(err) {
err.should.equal(401);
strategies.anonymousStrategy.fail = strategies.anonymousStrategy._fail;
delete strategies.anonymousStrategy._fail;
done();
};
strategies.anonymousStrategy.authenticate({});
});
afterEach(function() {
Users.default.restore();
})
});
describe("Tokens Strategy", function() {
it('Succeeds if tokens user enabled custom header',function(done) {
var userTokens = sinon.stub(Users,"tokens").callsFake(function(token) {
return Promise.resolve("tokens-"+token);
});
var userTokenHeader = sinon.stub(Users,"tokenHeader").callsFake(function(token) {
return "x-test-token";
});
strategies.tokensStrategy._success = strategies.tokensStrategy.success;
strategies.tokensStrategy.success = function(user) {
user.should.equal("tokens-1234");
strategies.tokensStrategy.success = strategies.tokensStrategy._success;
delete strategies.tokensStrategy._success;
done();
};
strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}});
});
it('Succeeds if tokens user enabled default header',function(done) {
var userTokens = sinon.stub(Users,"tokens").callsFake(function(token) {
return Promise.resolve("tokens-"+token);
});
var userTokenHeader = sinon.stub(Users,"tokenHeader").callsFake(function(token) {
return "authorization";
});
strategies.tokensStrategy._success = strategies.tokensStrategy.success;
strategies.tokensStrategy.success = function(user) {
user.should.equal("tokens-1234");
strategies.tokensStrategy.success = strategies.tokensStrategy._success;
delete strategies.tokensStrategy._success;
done();
};
strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}});
});
it('Fails if tokens user not enabled',function(done) {
var userTokens = sinon.stub(Users,"tokens").callsFake(function() {
return Promise.resolve(null);
});
var userTokenHeader = sinon.stub(Users,"tokenHeader").callsFake(function(token) {
return "authorization";
});
strategies.tokensStrategy._fail = strategies.tokensStrategy.fail;
strategies.tokensStrategy.fail = function(err) {
err.should.equal(401);
strategies.tokensStrategy.fail = strategies.tokensStrategy._fail;
delete strategies.tokensStrategy._fail;
done();
};
strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}});
});
afterEach(function() {
Users.tokens.restore();
Users.tokenHeader.restore();
})
});
describe("Bearer Strategy", function() {
it('Rejects invalid token',function(done) {
var getToken = sinon.stub(Tokens,"get").callsFake(function(token) {
return Promise.resolve(null);
});
strategies.bearerStrategy("1234",function(err,user) {
try {
should.not.exist(err);
user.should.be.false();
done();
} catch(e) {
done(e);
} finally {
getToken.restore();
}
});
});
it('Accepts valid token',function(done) {
var getToken = sinon.stub(Tokens,"get").callsFake(function(token) {
return Promise.resolve({user:"user",scope:"scope"});
});
var getUser = sinon.stub(Users,"get").callsFake(function(username) {
return Promise.resolve("aUser");
});
strategies.bearerStrategy("1234",function(err,user,opts) {
try {
should.not.exist(err);
user.should.equal("aUser");
opts.should.have.a.property("scope","scope");
done();
} catch(e) {
done(e);
} finally {
getToken.restore();
getUser.restore();
}
});
});
it('Fail if no user for token',function(done) {
var getToken = sinon.stub(Tokens,"get").callsFake(function(token) {
return Promise.resolve({user:"user",scope:"scope"});
});
var getUser = sinon.stub(Users,"get").callsFake(function(username) {
return Promise.resolve(null);
});
strategies.bearerStrategy("1234",function(err,user,opts) {
try {
should.not.exist(err);
user.should.equal(false);
should.not.exist(opts);
done();
} catch(e) {
done(e);
} finally {
getToken.restore();
getUser.restore();
}
});
});
});
describe("Client Password Strategy", function() {
it('Accepts valid client',function(done) {
var testClient = {id:"node-red-editor",secret:"not_available"};
var getClient = sinon.stub(Clients,"get").callsFake(function(client) {
return Promise.resolve(testClient);
});
strategies.clientPasswordStrategy(testClient.id,testClient.secret,function(err,client) {
try {
should.not.exist(err);
client.should.eql(testClient);
done();
} catch(e) {
done(e);
} finally {
getClient.restore();
}
});
});
it('Rejects invalid client secret',function(done) {
var testClient = {id:"node-red-editor",secret:"not_available"};
var getClient = sinon.stub(Clients,"get").callsFake(function(client) {
return Promise.resolve(testClient);
});
strategies.clientPasswordStrategy(testClient.id,"invalid_secret",function(err,client) {
try {
should.not.exist(err);
client.should.be.false();
done();
} catch(e) {
done(e);
} finally {
getClient.restore();
}
});
});
it('Rejects invalid client id',function(done) {
var getClient = sinon.stub(Clients,"get").callsFake(function(client) {
return Promise.resolve(null);
});
strategies.clientPasswordStrategy("invalid_id","invalid_secret",function(err,client) {
try {
should.not.exist(err);
client.should.be.false();
done();
} catch(e) {
done(e);
} finally {
getClient.restore();
}
});
});
var userAuthentication;
it('Blocks after 5 failures',function(done) {
userAuthentication = sinon.stub(Users,"authenticate").callsFake(function(username,password) {
return Promise.resolve(null);
});
for (var z=0; z<5; z++) {
strategies.passwordTokenExchange({},"user","badpassword","scope",function(err,token) {
});
}
strategies.passwordTokenExchange({},"user","badpassword","scope",function(err,token) {
try {
err.toString().should.equal("Error: Too many login attempts. Wait 10 minutes and try again");
token.should.be.false();
done();
} catch(e) {
done(e);
} finally {
userAuthentication.restore();
}
});
});
});
});

View File

@@ -1,180 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
describe("api/auth/tokens", function() {
describe("#init",function() {
it('loads sessions', function(done) {
Tokens.init({}).then(done);
});
});
describe("#get",function() {
it('returns a valid token', function(done) {
Tokens.init({},{
getSessions:function() {
return Promise.resolve({"1234":{"user":"fred","expires":Date.now()+1000}});
}
}).then(function() {
Tokens.get("1234").then(function(token) {
try {
token.should.have.a.property("user","fred");
done();
} catch(err) {
done(err);
}
});
});
});
it('returns null for an invalid token', function(done) {
Tokens.init({},{
getSessions:function() {
return Promise.resolve({});
}
}).then(function() {
Tokens.get("1234").then(function(token) {
try {
should.not.exist(token);
done();
} catch(err) {
done(err);
}
});
});
});
it('returns null for an expired token', function(done) {
var saveSessions = sinon.stub().returns(Promise.resolve());
var expiryTime = Date.now()+50;
Tokens.init({},{
getSessions:function() {
return Promise.resolve({"1234":{"user":"fred","expires":expiryTime}});
},
saveSessions: saveSessions
}).then(function() {
Tokens.get("1234").then(function(token) {
try {
should.exist(token);
setTimeout(function() {
Tokens.get("1234").then(function(token) {
try {
should.not.exist(token);
saveSessions.calledOnce.should.be.true();
done();
} catch(err) {
done(err);
}
});
},100);
} catch(err) {
done(err);
}
});
});
});
it('returns a valid api token', function(done) {
Tokens.init({
tokens: [{
token: "1234",
user: "fred",
}]
},{
getSessions:function() {
return Promise.resolve({});
}
}).then(function() {
Tokens.get("1234").then(function(token) {
try {
token.should.have.a.property("user","fred");
done();
} catch(err) {
done(err);
}
});
});
});
});
describe("#create",function() {
it('creates a token', function(done) {
var savedSession;
Tokens.init({sessionExpiryTime: 10},{
getSessions:function() {
return Promise.resolve({});
},
saveSessions:function(sess) {
savedSession = sess;
return Promise.resolve();
}
});
var expectedExpiryTime = Date.now()+10000;
Tokens.create("user","client","scope").then(function(token) {
try {
should.exist(savedSession);
var sessionKeys = Object.keys(savedSession);
sessionKeys.should.have.lengthOf(1);
token.should.have.a.property('accessToken',sessionKeys[0]);
savedSession[sessionKeys[0]].should.have.a.property('user','user');
savedSession[sessionKeys[0]].should.have.a.property('client','client');
savedSession[sessionKeys[0]].should.have.a.property('scope','scope');
savedSession[sessionKeys[0]].should.have.a.property('expires');
savedSession[sessionKeys[0]].expires.should.be.within(expectedExpiryTime-200,expectedExpiryTime+200);
done();
} catch(err) {
done(err);
}
});
});
});
describe("#revoke", function() {
it('revokes a token', function(done) {
var savedSession;
Tokens.init({},{
getSessions:function() {
return Promise.resolve({"1234":{"user":"fred","expires":Date.now()+1000}});
},
saveSessions:function(sess) {
savedSession = sess;
return Promise.resolve();
}
}).then(function() {
Tokens.revoke("1234").then(function() {
try {
savedSession.should.not.have.a.property("1234");
done();
} catch(err) {
done(err);
}
});
});
});
});
});

View File

@@ -1,276 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
describe("api/auth/users", function() {
after(function() {
Users.init({});
})
describe('Initalised with a credentials object, no anon',function() {
before(function() {
Users.init({
type:"credentials",
users:{
username:"fred",
password:'$2a$08$LpYMefvGZ3MjAfZGzcoyR.1BcfHh4wy4NpbN.cEny5aHnWOqjKOXK',
// 'password' -> require('bcryptjs').hashSync('password', 8);
permissions:"*"
}
});
});
describe('#get',function() {
it('returns known user',function(done) {
Users.get("fred").then(function(user) {
try {
user.should.have.a.property("username","fred");
user.should.have.a.property("permissions","*");
user.should.not.have.a.property("password");
done();
} catch(err) {
done(err);
}
});
});
it('returns null for unknown user', function(done) {
Users.get("barney").then(function(user) {
try {
should.not.exist(user);
done();
} catch(err) {
done(err);
}
});
});
});
describe('#default',function() {
it('returns null for default user', function(done) {
Users.default().then(function(user) {
try {
should.not.exist(user);
done();
} catch(err) {
done(err);
}
});
});
});
describe('#authenticate',function() {
it('authenticates a known user', function(done) {
Users.authenticate('fred','password').then(function(user) {
try {
user.should.have.a.property("username","fred");
user.should.have.a.property("permissions","*");
user.should.not.have.a.property("password");
done();
} catch(err) {
done(err);
}
});
});
it('rejects invalid password for a known user', function(done) {
Users.authenticate('fred','wrong').then(function(user) {
try {
should.not.exist(user);
done();
} catch(err) {
done(err);
}
});
});
it('rejects invalid user', function(done) {
Users.authenticate('barney','wrong').then(function(user) {
try {
should.not.exist(user);
done();
} catch(err) {
done(err);
}
});
});
});
});
describe('Initalised with a credentials object including anon',function() {
before(function() {
Users.init({
type:"credentials",
users:[],
default: { permissions: "*" }
});
});
describe('#default',function() {
it('returns default user', function(done) {
Users.default().then(function(user) {
try {
user.should.have.a.property('anonymous',true);
user.should.have.a.property('permissions','*');
done();
} catch(err) {
done(err);
}
});
});
});
});
describe('Initialised with a credentials object with user functions',function() {
var authUsername = '';
var authPassword = '';
before(function() {
Users.init({
type:"credentials",
users:function(username) {
return Promise.resolve({'username':'dave','permissions':'read'});
},
authenticate: function(username,password) {
authUsername = username;
authPassword = password;
return Promise.resolve({'username':'pete','permissions':'write'});
}
});
});
describe('#get',function() {
it("returns null for tokenHeader", function() {
should.not.exist(Users.tokenHeader());
});
it('delegates get user',function(done) {
Users.get('dave').then(function(user) {
try {
user.should.have.a.property("username","dave");
user.should.have.a.property("permissions","read");
user.should.not.have.a.property("password");
done();
} catch(err) {
done(err);
}
});
});
it('delegates authenticate user',function(done) {
Users.authenticate('pete','secret').then(function(user) {
try {
user.should.have.a.property("username","pete");
user.should.have.a.property("permissions","write");
user.should.not.have.a.property("password");
authUsername.should.equal('pete');
authPassword.should.equal('secret');
done();
} catch(err) {
done(err);
}
});
});
});
});
describe('Initialised with bad settings to test else cases',function() {
before(function() {
Users.init({
type:"foo",
users:{
username:"fred",
password:'$2a$08$LpYMefvGZ3MjAfZGzcoyR.1BcfHh4wy4NpbN.cEny5aHnWOqjKOXK',
permissions:"*"
}
});
});
describe('#get',function() {
it('should fail to return user fred',function(done) {
Users.get("fred").then(function(userf) {
try {
should.not.exist(userf);
done();
} catch(err) {
done(err);
}
});
});
});
});
describe('Initialised with default set as function',function() {
before(function() {
Users.init({
type:"credentials",
default: function() { return("Done"); }
});
});
after(function() {
Users.init({});
});
describe('#default',function() {
it('handles api.default being a function',function(done) {
Users.should.have.property('default').which.is.a.Function();
(Users.default()).should.equal("Done");
done();
});
});
});
describe('Initialised with tokens set as function',function() {
before(function() {
Users.init({
type:"strategy",
tokens: function(token) { return("Done-"+token); }
});
});
after(function() {
Users.init({});
});
describe('#tokens',function() {
it('handles api.tokens being a function',function(done) {
Users.should.have.property('tokens').which.is.a.Function();
(Users.tokens("1234")).should.equal("Done-1234");
(Users.tokenHeader()).should.equal("authorization");
done();
});
});
});
describe('Initialised with tokens set as function and tokenHeader set as token header name',function() {
before(function() {
Users.init({
type:"strategy",
tokens: function(token) { return("Done-"+token); },
tokenHeader: "X-TEST-TOKEN"
});
});
after(function() {
Users.init({});
});
describe('#tokens',function() {
it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) {
Users.should.have.property('tokens').which.is.a.Function();
(Users.tokens("1234")).should.equal("Done-1234");
Users.should.have.property('tokenHeader').which.is.a.Function();
(Users.tokenHeader()).should.equal("x-test-token");
done();
});
});
});
});

View File

@@ -1,618 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
const stoppable = require('stoppable');
var http = require('http');
var express = require('express');
var app = express();
var WebSocket = require('ws');
var NR_TEST_UTILS = require("nr-test-utils");
var comms = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/comms");
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
var Strategies = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/strategies");
var address = '127.0.0.1';
var listenPort = 0; // use ephemeral port
describe("api/editor/comms", function() {
var connections = [];
var mockComms = {
addConnection: function(opts) {
connections.push(opts.client);
return Promise.resolve()
},
removeConnection: function(opts) {
for (var i=0;i<connections.length;i++) {
if (connections[i] === opts.client) {
connections.splice(i,1);
break;
}
}
return Promise.resolve()
},
subscribe: function() { return Promise.resolve()},
unsubscribe: function() { return Promise.resolve(); }
}
describe("with default keepalive", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('accepts connection', function(done) {
var ws = new WebSocket(url);
connections.length.should.eql(0);
ws.on('open', function() {
try {
connections.length.should.eql(1);
ws.close();
done();
} catch(err) {
done(err);
}
});
});
it('publishes message after subscription', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic1"}');
connections.length.should.eql(1);
connections[0].send('topic1', 'foo');
});
ws.on('message', function(msg) {
msg.should.equal('[{"topic":"topic1","data":"foo"}]');
ws.close();
done();
});
});
it('malformed messages are ignored',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('not json');
ws.send('[]');
ws.send('{"subscribe":"topic3"}');
connections[0].send('topic3', 'correct');
});
ws.on('message', function(msg) {
msg.should.equal('[{"topic":"topic3","data":"correct"}]');
ws.close();
done();
});
});
});
describe("disabled editor", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {disableEditor:true}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('rejects websocket connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url);
ws.on('open', function() {
done(new Error("Socket connection unexpectedly accepted"));
ws.close();
});
ws.on('error', function() {
connections.length.should.eql(0);
done();
});
});
});
describe("non-default httpAdminRoot set: /adminPath", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {httpAdminRoot:"/adminPath"}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url);
ws.on('open', function() {
connections.length.should.eql(1);
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) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {httpAdminRoot:"/adminPath/"}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url);
ws.on('open', function() {
connections.length.should.eql(1);
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) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {httpAdminRoot:"adminPath"}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/adminPath/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url);
ws.on('open', function() {
connections.length.should.eql(1);
ws.close();
done();
});
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("keep alives", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {webSocketKeepAliveTime: 100}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function(done) {
Users.default.restore();
comms.stop();
server.stop(done);
});
it('are sent', function(done) {
var ws = new WebSocket(url);
var count = 0;
ws.on('message', function(data) {
var msg = JSON.parse(data)[0];
msg.should.have.property('topic','hb');
msg.should.have.property('data').be.a.Number();
count++;
if (count == 3) {
ws.close();
done();
}
});
});
it('are not sent if other messages are sent', function(done) {
var ws = new WebSocket(url);
var count = 0;
var interval;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
interval = setInterval(function() {
connections[0].send('foo', 'bar');
}, 50);
});
ws.on('message', function(data) {
var msg = JSON.parse(data)[0];
// It is possible a heartbeat message may arrive - so ignore them
if (msg.topic != "hb") {
msg.should.have.property('topic', 'foo');
msg.should.have.property('data', 'bar');
count++;
if (count == 5) {
clearInterval(interval);
ws.close();
done();
}
}
});
});
});
describe('authentication required, no anonymous',function() {
var server;
var url;
var port;
var getDefaultUser;
var getUser;
var getToken;
var getUserToken;
var getUserTokenHeader;
var authenticateUserToken;
var onSessionExpiry;
var onSessionExpiryCallback;
before(function(done) {
getDefaultUser = sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
getUser = sinon.stub(Users,"get").callsFake(function(username) {
if (username == "fred") {
return Promise.resolve({permissions:"read"});
} else {
return Promise.resolve(null);
}
});
getUserToken = sinon.stub(Users,"tokens").callsFake(function(token) {
if (token == "abcde") {
return Promise.resolve({user:"wilma", permissions:"*"})
} else {
return Promise.resolve(null);
}
});
getToken = sinon.stub(Tokens,"get").callsFake(function(token) {
if (token == "1234") {
return Promise.resolve({user:"fred",scope:["*"]});
} else if (token == "5678") {
return Promise.resolve({user:"barney",scope:["*"]});
} else {
return Promise.resolve(null);
}
});
getUserTokenHeader = sinon.stub(Users,"tokenHeader").callsFake(function() {
return "custom-header"
})
authenticateUserToken = sinon.stub(Strategies, "authenticateUserToken").callsFake(async function(req) {
var token = req.headers['custom-header'];
if (token === "knock-knock") {
return {user:"fred",scope:["*"]}
}
throw new Error("Invalid user");
})
onSessionExpiry = sinon.stub(Tokens,"onSessionExpiry").callsFake(function(cb) {
onSessionExpiryCallback = cb;
});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {adminAuth:{}}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function(done) {
getDefaultUser.restore();
getUser.restore();
getToken.restore();
getUserToken.restore();
getUserTokenHeader.restore();
authenticateUserToken.restore();
onSessionExpiry.restore();
comms.stop();
server.stop(done);
});
it('prevents connections that do not authenticate',function(done) {
var ws = new WebSocket(url);
var count = 0;
var interval;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
});
ws.on('close', function() {
done();
});
});
it('allows connections that do authenticate',function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"1234"}');
});
ws.on('message', function(msg) {
received++;
if (received == 1) {
msg.should.equal('{"auth":"ok"}');
ws.send('{"subscribe":"foo"}');
connections[0].send('foo', 'correct');
} else {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
ws.close();
}
});
ws.on('close', function() {
try {
received.should.equal(2);
done();
} catch(err) {
done(err);
}
});
});
it('allows connections that do authenticate - header-provided-token',function(done) {
var ws = new WebSocket(url,{
headers: { "custom-header": "knock-knock" }
});
var received = 0;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
connections.should.have.length(1);
connections[0].send('foo', 'correct');
});
ws.on('message', function(msg) {
received++;
if (received == 1) {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
ws.close();
}
});
ws.on('close', function() {
try {
received.should.equal(1);
done();
} catch(err) {
done(err);
}
});
});
it('allows connections that do authenticate - user-provided-token',function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"abcde"}');
});
ws.on('message', function(msg) {
received++;
if (received == 1) {
msg.should.equal('{"auth":"ok"}');
ws.send('{"subscribe":"foo"}');
connections[0].send('foo', 'correct');
} else {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
ws.close();
}
});
ws.on('close', function() {
try {
received.should.equal(2);
done();
} catch(err) {
done(err);
}
});
});
it('rejects connections for non-existant token',function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"2345"}');
});
ws.on('close', function() {
done();
});
});
it('rejects connections for invalid token',function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"5678"}');
});
ws.on('close', function() {
done();
});
});
it('rejects connections for invalid token - header-provided-token',function(done) {
var ws = new WebSocket(url,{
headers: { "custom-header": "bad token" }
});
var received = 0;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
});
ws.on('error', function() {
done();
})
});
it("expires websocket sessions", function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"1234"}');
});
ws.on('message', function(msg) {
received++;
if (received == 3) {
msg.should.equal('{"auth":"fail"}');
} else if (received == 1) {
msg.should.equal('{"auth":"ok"}');
ws.send('{"subscribe":"foo"}');
connections[0].send('foo', 'correct');
} else {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
setTimeout(function() {
onSessionExpiryCallback({accessToken:"1234"})
},50);
}
});
ws.on('close', function() {
try {
received.should.equal(3);
done();
} catch(err) {
done(err);
}
});
})
});
describe('authentication required, anonymous enabled',function() {
var server;
var url;
var port;
var getDefaultUser;
before(function(done) {
getDefaultUser = sinon.stub(Users,"default").callsFake(function() { return Promise.resolve({permissions:"read"});});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {adminAuth:{}}, {comms: mockComms});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port + '/comms';
comms.start();
done();
});
});
after(function(done) {
getDefaultUser.restore();
comms.stop();
server.stop(done);
});
it('allows anonymous connections that do not authenticate',function(done) {
var ws = new WebSocket(url);
var count = 0;
var interval;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
setTimeout(function() {
connections[0].send('foo', 'correct');
},200);
});
ws.on('message', function(msg) {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
count++;
ws.close();
});
ws.on('close', function() {
try {
count.should.equal(1);
done();
} catch(err) {
done(err);
}
});
});
});
});

View File

@@ -1,94 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var credentials = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/credentials");
describe('api/editor/credentials', function() {
var app;
before(function() {
app = express();
app.get('/credentials/:type/:id',credentials.get);
credentials.init({
flows: {
getNodeCredentials: function(opts) {
if (opts.type === "known-type" && opts.id === "n1") {
return Promise.resolve({
user1:"abc",
has_password1: true
});
} else {
var err = new Error("message");
err.code = "test_code";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
});
});
it('returns stored credentials',function(done) {
request(app)
.get("/credentials/known-type/n1")
.expect("Content-Type",/json/)
.expect(200)
.end(function(err,res) {
if (err) {
done(err);
} else {
try {
res.body.should.have.a.property("user1","abc");
res.body.should.not.have.a.property("password1");
res.body.should.have.a.property("has_password1",true);
done();
} catch(e) {
done(e);
}
}
})
});
it('returns any error',function(done) {
request(app)
.get("/credentials/unknown-type/n2")
.expect("Content-Type",/json/)
.expect(400)
.end(function(err,res) {
if (err) {
done(err);
} else {
try {
res.body.should.have.property('code');
res.body.code.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal('message');
done();
} catch(e) {
done(e);
}
}
})
});
});

View File

@@ -1,132 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var NR_TEST_UTILS = require("nr-test-utils");
var editorApi = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor");
var comms = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/comms");
var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings");
var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth");
var log = NR_TEST_UTILS.require("@node-red/util").log;
describe("api/editor/index", function() {
var app;
describe("disabled the editor", function() {
beforeEach(function() {
sinon.stub(comms,'init').callsFake(function(){});
sinon.stub(info,'init').callsFake(function(){});
});
afterEach(function() {
comms.init.restore();
info.init.restore();
});
it("disables the editor", function() {
var editorApp = editorApi.init({},{disableEditor:true},{});
should.not.exist(editorApp);
comms.init.called.should.be.false();
info.init.called.should.be.false();
});
});
describe("enables the editor", function() {
var mockList = [
'library','theme','locales','credentials','comms',"settings"
]
var isStarted = true;
var errors = [];
var session_data = {};
before(function() {
sinon.stub(auth,'needsPermission').callsFake(function(permission) {
return function(req,res,next) { next(); }
});
mockList.forEach(function(m) {
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m),"init").callsFake(function(){});
});
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"),"app").callsFake(function(){ return express()});
});
after(function() {
mockList.forEach(function(m) {
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m).init.restore();
})
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme").app.restore();
auth.needsPermission.restore();
log.error.restore();
});
before(function() {
sinon.stub(log,"error").callsFake(function(err) { errors.push(err)})
app = editorApi.init({},{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}},{
isStarted: () => Promise.resolve(isStarted)
});
});
it('serves the editor', function(done) {
request(app)
.get("/")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
// Index page should probably mention Node-RED somewhere
res.text.indexOf("Node-RED").should.not.eql(-1);
done();
});
});
it('serves icons', function(done) {
request(app)
.get("/red/images/icons/arrow-in.svg")
.expect(200)
.expect("Content-Type", /image\/svg\+xml/)
.end(function(err,res) {
done(err);
});
});
it('handles page not there', function(done) {
request(app)
.get("/foo")
.expect(404,done)
});
it('warns if runtime not started', function(done) {
isStarted = false;
request(app)
.get("/")
.expect(503)
.end(function(err,res) {
if (err) {
return done(err);
}
res.text.should.eql("Not started");
errors.should.have.lengthOf(1);
errors[0].should.eql("Node-RED runtime not started");
done();
});
});
// it.skip('GET /settings', function(done) {
// request(app).get("/settings").expect(200).end(function(err,res) {
// if (err) {
// return done(err);
// }
// // permissionChecks.should.have.property('settings.read',1);
// done();
// })
// });
});
});

View File

@@ -1,263 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var NR_TEST_UTILS = require("nr-test-utils");
var library = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/library");
var app;
describe("api/editor/library", function() {
before(function() {
app = express();
app.use(bodyParser.json());
app.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,library.getEntry);
app.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,library.saveEntry);
});
after(function() {
});
it('returns an individual entry - flow type', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
return Promise.resolve('{"a":1,"b":2}');
}
}
});
request(app)
.get('/library/local/flows/abc')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('a',1);
res.body.should.have.property('b',2);
opts.should.have.property('library','local');
opts.should.have.property('type','flows');
opts.should.have.property('path','abc');
done();
});
})
it('returns a directory listing - flow type', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
return Promise.resolve({"a":1,"b":2});
}
}
});
request(app)
.get('/library/local/flows/abc/def')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('a',1);
res.body.should.have.property('b',2);
opts.should.have.property('library','local');
opts.should.have.property('type','flows');
opts.should.have.property('path','abc/def');
done();
});
})
it('returns an individual entry - non-flow type', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
return Promise.resolve('{"a":1,"b":2}');
}
}
});
request(app)
.get('/library/local/non-flow/abc')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('library','local');
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc');
res.text.should.eql('{"a":1,"b":2}');
done();
});
})
it('returns a directory listing - non-flow type', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
return Promise.resolve({"a":1,"b":2});
}
}
});
request(app)
.get('/library/local/non-flow/abc/def')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('a',1);
res.body.should.have.property('b',2);
opts.should.have.property('library','local');
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc/def');
done();
});
})
it('returns an error on individual get', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
var err = new Error("message");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
});
request(app)
.get('/library/local/flows/123')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('library','local');
opts.should.have.property('type','flows');
opts.should.have.property('path','123');
res.body.should.have.property('code');
res.body.code.should.be.equal("random_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("message");
done();
});
});
it('saves an individual entry - flow type', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app)
.post('/library/local/flows/abc/def')
.expect(204)
.send({a:1,b:2,c:3})
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('library','local');
opts.should.have.property('type','flows');
opts.should.have.property('path','abc/def');
opts.should.have.property('meta',{});
opts.should.have.property('body',JSON.stringify({a:1,b:2,c:3}));
done();
});
})
it('saves an individual entry - non-flow type', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app)
.post('/library/local/non-flow/abc/def')
.expect(204)
.send({a:1,b:2,text:"123"})
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('library','local');
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc/def');
opts.should.have.property('meta',{a:1,b:2});
opts.should.have.property('body',"123");
done();
});
})
it('returns an error on individual save', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
var err = new Error("message");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
});
request(app)
.post('/library/local/non-flow/abc/def')
.send({a:1,b:2,text:"123"})
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('type','non-flow');
opts.should.have.property('library','local');
opts.should.have.property('path','abc/def');
res.body.should.have.property('code');
res.body.code.should.be.equal("random_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("message");
done();
});
});
});

View File

@@ -1,165 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var locales = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/locales");
var i18n = NR_TEST_UTILS.require("@node-red/util").i18n;
describe("api/editor/locales", function() {
beforeEach(function() {
})
afterEach(function() {
})
describe('get named resource catalog',function() {
var app;
before(function() {
// locales.init({
// i18n: {
// i: {
// language: function() { return 'en-US'},
// changeLanguage: function(lang,callback) {
// if (callback) {
// callback();
// }
// },
// getResourceBundle: function(lang, namespace) {
// return {namespace:namespace, lang:lang};
// }
// },
// }
// });
locales.init({});
// bit of a mess of internal workings
sinon.stub(i18n.i,'changeLanguage').callsFake(function(lang,callback) { if (callback) {callback();}});
if (i18n.i.getResourceBundle) {
sinon.stub(i18n.i,'getResourceBundle').callsFake(function(lang, namespace) {return {namespace:namespace, lang:lang};});
} else {
// If i18n.init has not been called, then getResourceBundle isn't
// defined - so hardcode a stub
i18n.i.getResourceBundle = function(lang, namespace) {return {namespace:namespace, lang:lang};};
i18n.i.getResourceBundle.restore = function() { delete i18n.i.getResourceBundle };
}
app = express();
app.get(/locales\/(.+)\/?$/,locales.get);
});
after(function() {
i18n.i.changeLanguage.restore();
i18n.i.getResourceBundle.restore();
})
it('returns with default language', function(done) {
request(app)
.get("/locales/message-catalog")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('namespace','message-catalog');
done();
});
});
it('returns with selected language', function(done) {
request(app)
.get("/locales/message-catalog?lng=fr-FR")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('namespace','message-catalog');
res.body.should.have.property('lang','fr-FR');
done();
});
});
it('returns for locale defined only with primary tag ', function(done) {
var orig = i18n.i.getResourceBundle;
i18n.i.getResourceBundle = function (lang, ns) {
if (lang === "ja-JP") {
return undefined;
}
return orig(lang, ns);
};
request(app)
// returns `ja` instead of `ja-JP`
.get("/locales/message-catalog?lng=ja-JP")
.expect(200)
.end(function(err,res) {
i18n.i.getResourceBundle = orig;
if (err) {
return done(err);
}
res.body.should.have.property('namespace','message-catalog');
res.body.should.have.property('lang','ja');
done();
});
});
});
// describe('get all node resource catalogs',function() {
// var app;
// before(function() {
// // bit of a mess of internal workings
// sinon.stub(i18n,'catalog').callsFake(function(namespace, lang) {
// return {
// "node-red": "should not return",
// "test-module-a-id": "test-module-a-catalog",
// "test-module-b-id": "test-module-b-catalog",
// "test-module-c-id": "test-module-c-catalog"
// }[namespace]
// });
// locales.init({
// nodes: {
// getNodeList: function(opts) {
// return Promise.resolve([
// {module:"node-red",id:"node-red-id"},
// {module:"test-module-a",id:"test-module-a-id"},
// {module:"test-module-b",id:"test-module-b-id"}
// ]);
// }
// }
// });
// app = express();
// app.get("/locales/nodes",locales.getAllNodes);
// });
// after(function() {
// i18n.catalog.restore();
// })
// it('returns with the node catalogs', function(done) {
// request(app)
// .get("/locales/nodes")
// .expect(200)
// .end(function(err,res) {
// if (err) {
// return done(err);
// }
// res.body.should.eql({
// 'test-module-a-id': 'test-module-a-catalog',
// 'test-module-b-id': 'test-module-b-catalog'
// });
// done();
// });
// });
// });
});

View File

@@ -1,21 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var NR_TEST_UTILS = require("nr-test-utils");
describe("api/editor/projects", function() {
it.skip("NEEDS TESTS WRITING",function() {});
});

View File

@@ -1,97 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require("body-parser");
var sinon = require('sinon');
var app;
var NR_TEST_UTILS = require("nr-test-utils");
var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings");
var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme");
describe("api/editor/settings", function() {
before(function() {
sinon.stub(theme,"settings").callsFake(function() { return { existing: 123, test: 456 };});
app = express();
app.use(bodyParser.json());
app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings);
app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings);
});
after(function() {
theme.settings.restore();
});
it('returns the user settings', function(done) {
info.init({
settings: {
getUserSettings: function(opts) {
if (opts.user !== "fred") {
return Promise.reject(new Error("Unknown user"));
}
return Promise.resolve({
c:3,
d:4
})
}
}
});
request(app)
.get("/settings/user")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.eql({c:3,d:4});
done();
});
});
it('updates the user settings', function(done) {
var update;
info.init({
settings: {
updateUserSettings: function(opts) {
if (opts.user !== "fred") {
return Promise.reject(new Error("Unknown user"));
}
update = opts.settings;
return Promise.resolve()
}
}
});
request(app)
.post("/settings/user")
.send({
e:4,
f:5
})
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
update.should.eql({e:4,f:5});
done();
});
});
});

View File

@@ -1,333 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var NR_TEST_UTILS = require("nr-test-utils");
var sshkeys = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/sshkeys");
var bodyParser = require("body-parser");
describe("api/editor/sshkeys", function() {
var app;
var mockRuntime = {
settings: {
getUserKeys: function() {},
getUserKey: function() {},
generateUserKey: function() {},
removeUserKey: function() {}
}
}
before(function() {
sshkeys.init(mockRuntime);
app = express();
app.use(bodyParser.json());
app.use("/settings/user/keys", sshkeys.app());
});
beforeEach(function() {
sinon.stub(mockRuntime.settings, "getUserKeys");
sinon.stub(mockRuntime.settings, "getUserKey");
sinon.stub(mockRuntime.settings, "generateUserKey");
sinon.stub(mockRuntime.settings, "removeUserKey");
})
afterEach(function() {
mockRuntime.settings.getUserKeys.restore();
mockRuntime.settings.getUserKey.restore();
mockRuntime.settings.generateUserKey.restore();
mockRuntime.settings.removeUserKey.restore();
})
it('GET /settings/user/keys --- return empty list', function(done) {
mockRuntime.settings.getUserKeys.returns(Promise.resolve([]));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
res.body.keys.should.be.empty();
done();
});
});
it('GET /settings/user/keys --- return normal list', function(done) {
var fileList = [
'test_key01',
'test_key02'
];
var retList = fileList.map(function(elem) {
return {
name: elem
};
});
mockRuntime.settings.getUserKeys.returns(Promise.resolve(retList));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
for (var item of retList) {
res.body.keys.should.containEql(item);
}
done();
});
});
it('GET /settings/user/keys --- return Error', function(done) {
var errInstance = new Error("Messages here.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
var errInstance = new Error("Not Found.");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKey.returns(p);
request(app)
.get("/settings/user/keys/NOT_REAL")
.expect(404)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
var errInstance = new Error();
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKeys.returns(p)
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
var key_file_name = "test_key";
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
mockRuntime.settings.getUserKey.returns(Promise.resolve(fileContent));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
mockRuntime.settings.getUserKey.called.should.be.true();
mockRuntime.settings.getUserKey.firstCall.args[0].should.eql({ user: undefined, id: 'test_key' });
res.body.should.be.deepEqual({ publickey: fileContent });
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("Messages.....");
done();
});
});
it('POST /settings/user/keys --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.settings.generateUserKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('POST /settings/user/keys --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.generateUserKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('POST /settings/user/keys --- return Unexpected error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.generateUserKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("Messages.....");
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.settings.removeUserKey.returns(Promise.resolve(true));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.be.deepEqual({});
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.removeUserKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.removeUserKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('code');
res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal('Messages.....');
done();
});
});
});

View File

@@ -1,275 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require("supertest");
var express = require('express');
var sinon = require('sinon');
var fs = require("fs");
var app = express();
var NR_TEST_UTILS = require("nr-test-utils");
var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme");
describe("api/editor/theme", function () {
beforeEach(function () {
sinon.stub(fs, "statSync").callsFake(function () { return true; });
});
afterEach(function () {
theme.init({settings: {}});
fs.statSync.restore();
});
it("applies the default theme", async function () {
var result = theme.init({});
should.not.exist(result);
var context = await theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title", "Node-RED");
context.page.should.have.a.property("favicon", "favicon.ico");
context.page.should.have.a.property("tabicon");
context.page.tabicon.should.have.a.property("icon", "red/images/node-red-icon-black.svg");
context.page.tabicon.should.have.a.property("colour", "#8f0000");
context.should.have.a.property("header");
context.header.should.have.a.property("title", "Node-RED");
context.header.should.have.a.property("image", "red/images/node-red.svg");
context.should.have.a.property("asset");
context.asset.should.have.a.property("red", "red/red.min.js");
context.asset.should.have.a.property("main", "red/main.min.js");
context.asset.should.have.a.property("vendorMonaco", "");
should.not.exist(theme.settings());
});
it("uses non-minified js files when in dev mode", async function () {
const previousEnv = process.env.NODE_ENV;
try {
process.env.NODE_ENV = 'development'
theme.init({});
var context = await theme.context();
context.asset.should.have.a.property("red", "red/red.js");
context.asset.should.have.a.property("main", "red/main.js");
} finally {
process.env.NODE_ENV = previousEnv;
}
});
it("Adds monaco bootstrap when enabled", async function () {
theme.init({
editorTheme: {
codeEditor: {
lib: 'monaco'
}
}
});
var context = await theme.context();
context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js");
});
it("picks up custom theme", async function () {
theme.init({
editorTheme: {
page: {
title: "Test Page Title",
favicon: "/absolute/path/to/theme/favicon",
tabicon: {
icon: "/absolute/path/to/theme/tabicon",
colour: "#8f008f"
},
css: [
"/absolute/path/to/custom/css/file.css"
],
scripts: "/absolute/path/to/script.js"
},
header: {
title: "Test Header Title",
url: "http://nodered.org",
image: "/absolute/path/to/header/image" // or null to remove image
},
deployButton: {
type: "simple",
label: "Save",
icon: "/absolute/path/to/deploy/button/image" // or null to remove image
},
menu: { // Hide unwanted menu items by id. see editor/js/main.js:loadEditor for complete list
"menu-item-import-library": false,
"menu-item-export-library": false,
"menu-item-keyboard-shortcuts": false,
"menu-item-help": {
label: "Alternative Help Link Text",
url: "http://example.com"
}
},
userMenu: false, // Hide the user-menu even if adminAuth is enabled
login: {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image
},
palette: {
editable: true,
catalogues: ['https://catalogue.nodered.org/catalogue.json'],
theme: [{ category: ".*", type: ".*", color: "#f0f" }]
},
projects: {
enabled: false
}
}
});
theme.app();
var context = await theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title", "Test Page Title");
context.page.should.have.a.property("favicon", "theme/favicon/favicon");
context.page.should.have.a.property("tabicon")
context.page.tabicon.should.have.a.property("icon", "theme/tabicon/tabicon");
context.page.tabicon.should.have.a.property("colour", "#8f008f")
context.should.have.a.property("header");
context.header.should.have.a.property("title", "Test Header Title");
context.header.should.have.a.property("url", "http://nodered.org");
context.header.should.have.a.property("image", "theme/header/image");
context.page.should.have.a.property("css");
context.page.css.should.have.lengthOf(1);
context.page.css[0].should.eql('theme/css/file.css');
context.page.should.have.a.property("scripts");
context.page.scripts.should.have.lengthOf(1);
context.page.scripts[0].should.eql('theme/scripts/script.js');
context.should.have.a.property("login");
context.login.should.have.a.property("image", "theme/login/image");
var settings = theme.settings();
settings.should.have.a.property("deployButton");
settings.deployButton.should.have.a.property("type", "simple");
settings.deployButton.should.have.a.property("label", "Save");
settings.deployButton.should.have.a.property("icon", "theme/deploy/image");
settings.should.have.a.property("userMenu");
settings.userMenu.should.be.eql(false);
settings.should.have.a.property("menu");
settings.menu.should.have.a.property("menu-item-import-library", false);
settings.menu.should.have.a.property("menu-item-export-library", false);
settings.menu.should.have.a.property("menu-item-keyboard-shortcuts", false);
settings.menu.should.have.a.property("menu-item-help", { label: "Alternative Help Link Text", url: "http://example.com" });
settings.should.have.a.property("palette");
settings.palette.should.have.a.property("editable", true);
settings.palette.should.have.a.property("catalogues", ['https://catalogue.nodered.org/catalogue.json']);
settings.palette.should.have.a.property("theme", [{ category: ".*", type: ".*", color: "#f0f" }]);
settings.should.have.a.property("projects");
settings.projects.should.have.a.property("enabled", false);
});
it("picks up backwards compatible tabicon setting", async function () {
theme.init({
editorTheme: {
page: {
tabicon: "/absolute/path/to/theme/tabicon",
}
}
});
theme.app();
var context = await theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("tabicon");
context.page.tabicon.should.have.a.property("icon", "theme/tabicon/tabicon");
// The colour property should remain as default in this case as the
// legacy format for defining tabicon doesn't allow specifying a colour
context.page.tabicon.should.have.a.property("colour", "#8f0000");
});
it("test explicit userMenu set to true in theme setting", function () {
theme.init({
editorTheme: {
userMenu: true,
}
});
theme.app();
var settings = theme.settings();
settings.should.have.a.property("userMenu");
settings.userMenu.should.be.eql(true);
});
it("includes list of plugin themes", function(done) {
theme.init({},{
plugins: { getPluginsByType: _ => [{id:"theme-plugin"}] }
});
const app = theme.app();
request(app)
.get("/")
.end(function(err,res) {
if (err) {
return done(err);
}
try {
const response = JSON.parse(res.text);
response.should.have.property("themes");
response.themes.should.eql(["theme-plugin"])
done();
} catch(err) {
done(err);
}
});
});
it("includes theme plugin settings", async function () {
theme.init({
editorTheme: {
theme: 'test-theme'
}
},{
plugins: { getPlugin: t => {
return ({'test-theme':{
path: '/abosolute/path/to/plugin',
css: [
"path/to/custom/css/file1.css",
"/invalid/path/to/file2.css",
"../another/invalid/path/file3.css"
],
scripts: [
"path/to/custom/js/file1.js",
"/invalid/path/to/file2.js",
"../another/invalid/path/file3.js"
]
}})[t.id];
} }
});
theme.app();
var context = await theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("css");
context.page.css.should.have.lengthOf(1);
context.page.css[0].should.eql('theme/css/file1.css');
context.page.should.have.a.property("scripts");
context.page.scripts.should.have.lengthOf(1);
context.page.scripts[0].should.eql('theme/scripts/file1.js');
});
});

View File

@@ -1,210 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require("supertest");
var express = require("express");
var fs = require("fs");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var ui = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/ui");
describe("api/editor/ui", function() {
var app;
before(function() {
ui.init({
nodes: {
getIcon: function(opts) {
return new Promise(function(resolve,reject) {
if (opts.icon === "icon.png") {
fs.readFile(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"), function(err,data) {
resolve(data);
})
} else {
resolve(null);
}
});
},
getModuleResource: async function(opts) {
if (opts.module !== "test-module" || opts.path !== "a/path/text.txt") {
return null;
} else {
return "Some text data";
}
}
}
});
});
describe("slash handler", function() {
before(function() {
app = express();
app.get("/foo",ui.ensureSlash,function(req,res) { res.sendStatus(200);});
});
it('redirects if the path does not end in a slash',function(done) {
request(app)
.get('/foo')
.expect(301,done);
});
it('redirects if the path, with query string, does not end in a slash',function(done) {
request(app)
.get('/foo?abc=def')
.expect(301)
.end(function(err,res) {
if (err) {
return done(err);
}
res.header['location'].should.equal("/foo/?abc=def");
done();
});
});
it('does not redirect if the path ends in a slash',function(done) {
request(app)
.get('/foo/')
.expect(200,done);
});
});
describe("icon handler", function() {
before(function() {
app = express();
app.get("/icons/:module/:icon",ui.icon);
});
function binaryParser(res, callback) {
res.setEncoding('binary');
res.data = '';
res.on('data', function (chunk) {
res.data += chunk;
});
res.on('end', function () {
callback(null, Buffer.from(res.data, 'binary'));
});
}
function compareBuffers(b1,b2) {
b1.length.should.equal(b2.length);
for (var i=0;i<b1.length;i++) {
b1[i].should.equal(b2[i]);
}
}
it('returns the requested icon', function(done) {
var defaultIcon = fs.readFileSync(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"));
request(app)
.get("/icons/module/icon.png")
.expect("Content-Type", /image\/png/)
.expect(200)
.parse(binaryParser)
.end(function(err,res) {
if (err){
return done(err);
}
Buffer.isBuffer(res.body).should.be.true();
compareBuffers(res.body,defaultIcon);
done();
});
});
it('returns the default icon for invalid paths', function(done) {
var defaultIcon = fs.readFileSync(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"));
request(app)
.get("/icons/module/unreal.png")
.expect("Content-Type", /image\/svg/)
.expect(200)
.parse(binaryParser)
.end(function(err,res) {
if (err){
return done(err);
}
Buffer.isBuffer(res.body).should.be.true();
compareBuffers(res.body,defaultIcon);
done();
});
});
});
describe("module resource handler", function() {
before(function() {
app = express();
app.get(/^\/resources\/((?:@[^\/]+\/)?[^\/]+)\/(.+)$/,ui.moduleResource);
});
it('returns the requested resource', function(done) {
request(app)
.get("/resources/test-module/a/path/text.txt")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.text.should.eql('Some text data');
done();
});
});
it('404s invalid paths', function(done) {
request(app)
.get("/resources/test-module/../a/path/text.txt")
.expect(404)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
});
describe("editor ui handler", function() {
before(function() {
app = express();
app.use("/",ui.editor);
});
it('serves the editor', function(done) {
request(app)
.get("/")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
// Index page should probably mention Node-RED somewhere
res.text.indexOf("Node-RED").should.not.eql(-1);
done();
});
});
});
describe("editor ui resource handler", function() {
before(function() {
app = express();
app.use("/",ui.editorResources);
});
it('serves the editor resources', function(done) {
request(app)
.get("/favicon.ico")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
});
});

View File

@@ -1,182 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var NR_TEST_UTILS = require("nr-test-utils");
const auth = require("basic-auth");
var api = NR_TEST_UTILS.require("@node-red/editor-api");
var apiAuth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth");
var apiEditor = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor");
var apiAdmin = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin");
describe("api/index", function() {
var beforeEach = function() {
sinon.stub(apiAuth,"init").callsFake(function(){});
sinon.stub(apiEditor,"init").callsFake(function(){
var app = express();
app.get("/editor",function(req,res) { res.status(200).end(); });
return app;
});
sinon.stub(apiAdmin,"init").callsFake(function(){
var app = express();
app.get("/admin",function(req,res) { res.status(200).end(); });
return app;
});
sinon.stub(apiAuth,"login").callsFake(function(req,res){
res.status(200).end();
});
};
var afterEach = function() {
apiAuth.init.restore();
apiAuth.login.restore();
apiEditor.init.restore();
apiAdmin.init.restore();
};
beforeEach(beforeEach);
afterEach(afterEach);
it("does not setup admin api if httpAdminRoot is false", function(done) {
api.init({ httpAdminRoot: false },{},{},{});
should.not.exist(api.httpAdmin);
done();
});
describe('initalises admin api without adminAuth', function(done) {
before(function() {
beforeEach();
api.init({},{},{},{});
});
after(afterEach);
it('exposes the editor',function(done) {
request(api.httpAdmin).get("/editor").expect(200).end(done);
})
it('exposes the admin api',function(done) {
request(api.httpAdmin).get("/admin").expect(200).end(done);
})
it('exposes the auth api',function(done) {
request(api.httpAdmin).get("/auth/login").expect(200).end(done);
})
});
describe('initalises admin api without editor', function(done) {
before(function() {
beforeEach();
api.init({ disableEditor: true },{},{},{});
});
after(afterEach);
it('does not expose the editor',function(done) {
request(api.httpAdmin).get("/editor").expect(404).end(done);
})
it('exposes the admin api',function(done) {
request(api.httpAdmin).get("/admin").expect(200).end(done);
})
it('exposes the auth api',function(done) {
request(api.httpAdmin).get("/auth/login").expect(200).end(done)
})
});
describe('initialises api with admin middleware', function(done) {
it('ignores non-function values',function(done) {
api.init({ httpAdminRoot: true, httpAdminMiddleware: undefined },{},{},{});
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware')
should(middlewareFound).be.empty();
done();
});
it('only accepts functions as middleware',function(done) {
const testMiddleware = function(req, res, next){ next(); };
api.init({ httpAdminRoot: true, httpAdminMiddleware: testMiddleware },{},{},{});
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware')
should(middlewareFound).be.length(1);
done();
});
});
describe('initialises api with authentication enabled', function(done) {
it('enables an oauth/openID based authentication mechanism',function(done) {
const stub = sinon.stub(apiAuth, 'genericStrategy').callsFake(function(){});
const adminAuth = { type: 'strategy', strategy: {} }
api.init({ httpAdminRoot: true, adminAuth },{},{},{});
should(stub.called).be.ok();
stub.restore();
done();
});
it('enables password protection',function(done) {
const adminAuth = { type: 'credentials' }
api.init({ httpAdminRoot: true, adminAuth },{},{},{});
// is the name ("initialize") of the passport middleware present
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'initialize')
should(middlewareFound).be.length(1);
done();
});
});
describe('initialises api with custom cors config', function (done) {
const httpAdminCors = {
origin: "*",
methods: "GET,PUT,POST,DELETE"
};
it('uses default cors middleware when user settings absent', function(done){
api.init({ httpAdminRoot: true }, {}, {}, {});
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'corsMiddleware')
should(middlewareFound).be.length(1);
done();
})
it('enables custom cors middleware when settings present', function(done){
api.init({ httpAdminRoot: true, httpAdminCors }, {}, {}, {});
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'corsMiddleware')
should(middlewareFound).be.length(2);
done();
})
});
describe('editor start', function (done) {
it('cannot be started when editor is disabled', function (done) {
const stub = sinon.stub(apiEditor, 'start').callsFake(function () {
return Promise.resolve(true);
});
api.init({ httpAdminRoot: true, disableEditor: true }, {}, {}, {});
should(api.start()).resolvedWith(true);
stub.restore();
done();
});
it('can be started when editor enabled', function (done) {
const stub = sinon.stub(apiEditor, 'start');
api.init({ httpAdminRoot: true, disableEditor: false }, {}, {}, {});
api.start();
should(stub.called).be.true();
stub.restore();
done();
});
});
});

View File

@@ -1,110 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require('supertest');
var express = require('express');
var NR_TEST_UTILS = require("nr-test-utils");
var apiUtil = NR_TEST_UTILS.require("@node-red/editor-api/lib/util");
var log = NR_TEST_UTILS.require("@node-red/util").log;
var i18n = NR_TEST_UTILS.require("@node-red/util").i18n;
describe("api/util", function() {
describe("errorHandler", function() {
var loggedError = null;
var loggedEvent = null;
var app;
before(function() {
app = express();
sinon.stub(log,'error').callsFake(function(msg) {loggedError = msg;});
sinon.stub(log,'audit').callsFake(function(event) {loggedEvent = event;});
app.get("/tooLarge", function(req,res) {
var err = new Error();
err.message = "request entity too large";
throw err;
},apiUtil.errorHandler)
app.get("/stack", function(req,res) {
var err = new Error();
err.message = "stacktrace";
throw err;
},apiUtil.errorHandler)
});
after(function() {
log.error.restore();
log.audit.restore();
})
beforeEach(function() {
loggedError = null;
loggedEvent = null;
})
it("logs an error for request entity too large", function(done) {
request(app).get("/tooLarge").expect(400).end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("error","unexpected_error");
res.body.should.have.property("message","Error: request entity too large");
loggedError.should.have.property("message","request entity too large");
loggedEvent.should.have.property("event","api.error");
loggedEvent.should.have.property("error","unexpected_error");
loggedEvent.should.have.property("message","Error: request entity too large");
done();
});
})
it("logs an error plus stack for other errors", function(done) {
request(app).get("/stack").expect(400).end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("error","unexpected_error");
res.body.should.have.property("message","Error: stacktrace");
/Error: stacktrace\s*at.*util_spec.js/m.test(loggedError).should.be.true();
loggedEvent.should.have.property("event","api.error");
loggedEvent.should.have.property("error","unexpected_error");
loggedEvent.should.have.property("message","Error: stacktrace");
done();
});
});
})
describe('determineLangFromHeaders', function() {
var oldDefaultLang;
before(function() {
oldDefaultLang = i18n.defaultLang;
i18n.defaultLang = "en-US";
})
after(function() {
i18n.defaultLang = oldDefaultLang;
})
it('returns the default lang if non provided', function() {
apiUtil.determineLangFromHeaders(null).should.eql("en-US");
})
it('returns the first language accepted', function() {
apiUtil.determineLangFromHeaders(['fr-FR','en-GB']).should.eql("fr-FR");
})
})
});

View File

@@ -1,30 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var NR_TEST_UTILS = require("nr-test-utils");
var deprecated = NR_TEST_UTILS.require("@node-red/registry/lib/deprecated.js");
describe('deprecated', function() {
it('should return info on a node',function() {
deprecated.get("irc in").should.eql({module:"node-red-node-irc"});
});
it('should return null for non-deprecated node',function() {
should.not.exist(deprecated.get("foo"));
});
});

View File

@@ -1,371 +0,0 @@
// init: init,
// register: register,
// registerSubflow: registerSubflow,
// checkFlowDependencies: checkFlowDependencies,
// require: requireModule
//
const should = require("should");
const sinon = require("sinon");
const fs = require("fs-extra");
const path = require("path");
const os = require("os");
const NR_TEST_UTILS = require("nr-test-utils");
const externalModules = NR_TEST_UTILS.require("@node-red/registry/lib/externalModules");
const exec = NR_TEST_UTILS.require("@node-red/util/lib/exec");
const hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
let homeDir;
async function createUserDir() {
if (!homeDir) {
homeDir = path.join(os.tmpdir(),"nr-test-"+Math.floor(Math.random()*100000));
}
await fs.ensureDir(homeDir);
}
async function setupExternalModulesPackage(dependencies) {
await fs.writeFile(path.join(homeDir,"package.json"),`{
"name": "Node-RED-External-Modules",
"description": "These modules are automatically installed by Node-RED to use in Function nodes.",
"version": "1.0.0",
"private": true,
"dependencies": ${JSON.stringify(dependencies)}
}`)
}
describe("externalModules api", function() {
beforeEach(async function() {
await createUserDir()
})
afterEach(async function() {
hooks.clear();
await fs.remove(homeDir);
})
describe("checkFlowDependencies", function() {
beforeEach(function() {
sinon.stub(exec,"run").callsFake(async function(cmd, args, options) {
let error;
let moduleName = args[args.length-1];
if (moduleName === "moduleNotFound") {
error = new Error();
error.stderr = "E404";
} else if (moduleName === "moduleVersionNotFound") {
error = new Error();
error.stderr = "ETARGET";
} else if (moduleName === "moduleFail") {
error = new Error();
error.stderr = "Some unexpected install error";
}
if (error) {
throw error;
}
})
})
afterEach(function() {
exec.run.restore();
})
it("does nothing when no types are registered",async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.false();
});
it("skips install for modules already installed", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
await setupExternalModulesPackage({"foo": "1.2.3", "bar":"2.3.4"});
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.false();
})
it("skips install for built-in modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "fs"}]}
])
exec.run.called.should.be.false();
})
it("installs missing modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.true();
})
it("calls pre/postInstall hooks", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.true();
// exec.run.lastCall.args[1].should.eql([ 'install', 'a', 'foo' ]);
receivedPreEvent.should.have.property("module","foo")
receivedPreEvent.should.have.property("version")
receivedPreEvent.should.have.property("dir")
receivedPreEvent.should.eql(receivedPostEvent)
})
it("skips npm install if preInstall returns false", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { receivedPreEvent = event; return false })
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.false();
receivedPreEvent.should.have.property("module","foo")
receivedPreEvent.should.have.property("version")
receivedPreEvent.should.have.property("dir")
receivedPreEvent.should.eql(receivedPostEvent)
})
it("installs missing modules from inside subflow module", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
externalModules.registerSubflow("sf", {"flow":[{type: "function", libs:[{module: "foo"}]}]});
await externalModules.checkFlowDependencies([
{type: "sf"}
])
exec.run.called.should.be.true();
})
it("reports install fail - 404", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "moduleNotFound"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.called.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","moduleNotFound");
err[0].should.have.property("error");
err[0].error.should.have.property("code",404);
}
})
it("reports install fail - target", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "moduleVersionNotFound"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.called.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","moduleVersionNotFound");
err[0].should.have.property("error");
err[0].error.should.have.property("code",404);
}
})
it("reports install fail - unexpected", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "moduleFail"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.called.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","moduleFail");
err[0].should.have.property("error");
err[0].error.should.have.property("code","unexpected_error");
}
})
it("reports install fail - multiple", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "moduleNotFound"},{module: "moduleFail"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.called.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(2);
// Sort the array so we know the order to test for
err.sort(function(A,B) {
return A.module.module.localeCompare(B.module.module);
})
err[1].should.have.property("module");
err[1].module.should.have.property("module","moduleNotFound");
err[1].should.have.property("error");
err[1].error.should.have.property("code",404);
err[0].should.have.property("module");
err[0].module.should.have.property("module","moduleFail");
err[0].should.have.property("error");
err[0].error.should.have.property("code","unexpected_error");
}
})
it("reports install fail - install disabled", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
allowInstall: false
}
}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
// Should not try to install
exec.run.called.should.be.false();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","foo");
err[0].should.have.property("error");
err[0].error.should.have.property("code","install_not_allowed");
}
})
it("reports install fail - module disallowed", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['foo']
}
}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
// foo disallowed
// bar allowed
{type: "function", libs:[{module: "foo"},{module: "bar"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.calledOnce.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","foo");
err[0].should.have.property("error");
err[0].error.should.have.property("code","install_not_allowed");
}
})
it("reports install fail - built-in module disallowed", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
// foo disallowed
// bar allowed
{type: "function", libs:[{module: "fs"},{module: "bar"}]}
])
throw new Error("checkFlowDependencies did not reject after install fail")
} catch(err) {
exec.run.calledOnce.should.be.true();
Array.isArray(err).should.be.true();
err.should.have.length(1);
err[0].should.have.property("module");
err[0].module.should.have.property("module","fs");
err[0].should.have.property("error");
err[0].error.should.have.property("code","module_not_allowed");
}
})
})
describe("require", async function() {
it("requires built-in modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
const result = externalModules.require("fs")
result.should.eql(require("fs"));
})
it("rejects unknown modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
try {
externalModules.require("foo")
throw new Error("require did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
it("rejects disallowed modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
}});
try {
externalModules.require("fs")
throw new Error("require did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
})
describe("import", async function() {
it("import built-in modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
const result = await externalModules.import("fs")
// `result` won't have the `should` property
should.exist(result);
should.exist(result.existsSync);
})
it("rejects unknown modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
try {
await externalModules.import("foo")
throw new Error("import did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
it("rejects disallowed modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
}});
try {
await externalModules.import("fs")
throw new Error("import did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
})
});

View File

@@ -1,127 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var fs = require("fs");
var NR_TEST_UTILS = require("nr-test-utils");
var registry = NR_TEST_UTILS.require("@node-red/registry");
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
var loader = NR_TEST_UTILS.require("@node-red/registry/lib/loader");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
describe('red/registry/index', function() {
var stubs = [];
afterEach(function() {
while(stubs.length) {
stubs.pop().restore();
}
})
describe('#init',function() {
it('intialises components', function() {
stubs.push(sinon.stub(installer,"init"));
stubs.push(sinon.stub(loader,"init"));
stubs.push(sinon.stub(typeRegistry,"init"));
registry.init({settings:{}});
installer.init.called.should.be.true();
loader.init.called.should.be.true();
typeRegistry.init.called.should.be.true();
})
});
describe('#addModule', function() {
it('loads the module and returns its info', function(done) {
stubs.push(sinon.stub(loader,"addModule").callsFake(function(module) {
return Promise.resolve();
}));
stubs.push(sinon.stub(typeRegistry,"getModuleInfo").callsFake(function(module) {
return "info";
}));
registry.addModule("foo").then(function(info) {
info.should.eql("info");
done();
}).catch(function(err) { done(err); });
});
it('rejects if loader rejects', function(done) {
stubs.push(sinon.stub(loader,"addModule").callsFake(function(module) {
return Promise.reject("error");
}));
stubs.push(sinon.stub(typeRegistry,"getModuleInfo").callsFake(function(module) {
return "info";
}));
registry.addModule("foo").then(function(info) {
done(new Error("unexpected resolve"));
}).catch(function(err) {
err.should.eql("error");
done();
})
});
});
describe('#enableNode',function() {
it('enables a node set',function(done) {
stubs.push(sinon.stub(typeRegistry,"enableNodeSet").callsFake(function() {
return Promise.resolve();
}));
stubs.push(sinon.stub(typeRegistry,"getNodeInfo").callsFake(function() {
return {id:"node-set",loaded:true};
}));
registry.enableNode("node-set").then(function(ns) {
typeRegistry.enableNodeSet.called.should.be.true();
ns.should.have.a.property('id','node-set');
done();
}).catch(function(err) { done(err); });
});
it('rejects if node unknown',function() {
stubs.push(sinon.stub(typeRegistry,"enableNodeSet").callsFake(function() {
throw new Error('failure');
}));
/*jshint immed: false */
(function(){
registry.enableNode("node-set")
}).should.throw();
});
it('triggers a node load',function(done) {
stubs.push(sinon.stub(typeRegistry,"enableNodeSet").callsFake(function() {
return Promise.resolve();
}));
var calls = 0;
stubs.push(sinon.stub(typeRegistry,"getNodeInfo").callsFake(function() {
// loaded=false on first call, true on subsequent
return {id:"node-set",loaded:(calls++>0)};
}));
stubs.push(sinon.stub(loader,"loadNodeSet").callsFake(function(){return Promise.resolve();}));
stubs.push(sinon.stub(typeRegistry,"getFullNodeInfo"));
registry.enableNode("node-set").then(function(ns) {
typeRegistry.enableNodeSet.called.should.be.true();
loader.loadNodeSet.called.should.be.true();
ns.should.have.a.property('id','node-set');
ns.should.have.a.property('loaded',true);
done();
}).catch(function(err) { done(err); });
});
});
});

View File

@@ -1,528 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var fs = require('fs-extra');
var EventEmitter = require('events');
var NR_TEST_UTILS = require("nr-test-utils");
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/index");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
const { events, exec, log, hooks } = NR_TEST_UTILS.require("@node-red/util");
describe('nodes/registry/installer', function() {
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
_: function(msg) { return msg }
}
var execResponse;
beforeEach(function() {
sinon.stub(exec,"run").callsFake(() => execResponse || Promise.resolve(""))
installer.init({})
});
afterEach(function() {
execResponse = null;
if (registry.addModule.restore) {
registry.addModule.restore();
}
if (registry.removeModule.restore) {
registry.removeModule.restore();
}
if (typeRegistry.removeModule.restore) {
typeRegistry.removeModule.restore();
}
if (registry.getModuleInfo.restore) {
registry.getModuleInfo.restore();
}
if (typeRegistry.getModuleInfo.restore) {
typeRegistry.getModuleInfo.restore();
}
if (typeRegistry.setModulePendingUpdated.restore) {
typeRegistry.setModulePendingUpdated.restore();
}
if (fs.statSync.restore) {
fs.statSync.restore();
}
exec.run.restore();
hooks.clear();
});
describe("installs module", function() {
it("rejects module name that includes version", function(done) {
installer.installModule("module@version",null,null).catch(function(err) {
err.code.should.be.eql('invalid_module_name');
done();
}).catch(done);
});
it("rejects missing module name", function(done) {
installer.installModule("",null,null).catch(function(err) {
err.code.should.be.eql('invalid_module_name');
done();
}).catch(done);
});
it("rejects null module name", function(done) {
installer.installModule(null,null,null).catch(function(err) {
err.code.should.be.eql('invalid_module_name');
done();
}).catch(done);
});
it("rejects invalid url", function(done) {
installer.installModule("module",null,"abc").catch(function(err) {
err.code.should.be.eql('invalid_module_url');
done();
});
});
it("rejects when npm returns a 404", function(done) {
var res = {
code: 1,
stdout:"",
stderr:" 404 this_wont_exist"
}
var p = Promise.reject(res);
p.catch((err)=>{});
execResponse = p;
installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404);
done();
}).catch(done);
});
it("rejects when npm does not find specified version", function(done) {
var res = {
code: 1,
stdout:"",
stderr:" version not found: this_wont_exist@0.1.2"
}
var p = Promise.reject(res);
p.catch((err)=>{});
execResponse = p;
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404);
done();
}).catch(done);
});
it("rejects when update requested to existing version", function(done) {
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.1").catch(function(err) {
err.code.should.be.eql('module_already_loaded');
done();
}).catch(done);
});
it("rejects when update requested to existing version and url", function(done) {
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.1","https://example/foo-0.1.1.tgz").catch(function(err) {
err.code.should.be.eql('module_already_loaded');
done();
}).catch(done);
});
it("rejects with generic error", function(done) {
var res = {
code: 1,
stdout:"",
stderr:" kaboom!"
}
var p = Promise.reject(res);
p.catch((err)=>{});
execResponse = p;
installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
}).catch(err => {
// Expected result
done()
});
});
it("succeeds when module is found", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist").then(function(info) {
info.should.eql(nodeInfo);
// commsMessages.should.have.length(1);
// commsMessages[0].topic.should.equal("node/added");
// commsMessages[0].msg.should.eql(nodeInfo.nodes);
done();
}).catch(done);
});
it("rejects when non-existant path is provided", function(done) {
this.timeout(20000);
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","NonExistant"));
installer.installModule(resourcesDir).then(function() {
done(new Error("Unexpected success"));
}).catch(function(err) {
if (err.hasOwnProperty("code")) {
err.code.should.eql(404);
done();
}
else {
console.log("ERRROR::"+err.toString()+"::");
err.toString().should.eql("Error: Install failed");
done();
}
});
});
it("succeeds when path is valid node-red module", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule"));
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo);
done();
}).catch(done);
});
it("succeeds when url is valid node-red module", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) {
info.should.eql(nodeInfo);
done();
}).catch(done);
});
it("triggers preInstall and postInstall hooks", function(done) {
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var res = {code: 0,stdout:"",stderr:""}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist","1.2.3").then(function(info) {
exec.run.called.should.be.true();
exec.run.lastCall.args[1].should.eql([ 'install', 'a', 'this_wont_exist@1.2.3' ]);
info.should.eql(nodeInfo);
should.exist(receivedPreEvent)
receivedPreEvent.should.have.property("module","this_wont_exist")
receivedPreEvent.should.have.property("version","1.2.3")
receivedPreEvent.should.have.property("dir")
receivedPreEvent.should.have.property("url")
receivedPreEvent.should.have.property("isExisting")
receivedPreEvent.should.have.property("isUpgrade")
receivedPreEvent.should.eql(receivedPostEvent)
done();
}).catch(done);
});
it("fails install if preInstall hook fails", function(done) {
let receivedEvent;
hooks.add("preInstall", function(event) { throw new Error("preInstall-error"); })
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
exec.run.called.should.be.false();
done();
}).catch(done);
});
it("skips invoking npm if preInstall returns false", function(done) {
let receivedEvent;
hooks.add("preInstall", function(event) { return false })
hooks.add("postInstall", function(event) { receivedEvent = event; })
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist","1.2.3").then(function() {
exec.run.called.should.be.false();
should.exist(receivedEvent);
done();
}).catch(done);
});
it("rollsback install if postInstall hook fails", function(done) {
hooks.add("postInstall", function(event) { throw new Error("fail"); })
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
exec.run.calledTwice.should.be.true();
exec.run.firstCall.args[1].includes("install").should.be.true();
exec.run.secondCall.args[1].includes("remove").should.be.true();
done();
}).catch(done);
});
describe("allowUpdate lists", function() {
it("rejects when update requested with allowUpdate set to false", function(done) {
installer.init({ externalModules: { palette: { allowUpdate: false } } })
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql('update_not_allowed');
done();
}).catch(done);
})
it("succeeds when update requested with module not on denyUpdateList", function(done) {
installer.init({ externalModules: { palette: { denyUpdateList: ['this_wont_exist'] } } })
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var nodeInfo = {nodes:{module:"this_is_allowed",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
sinon.stub(typeRegistry,"setModulePendingUpdated").callsFake(function() {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_is_allowed","0.1.2").then(function() {
done();
}).catch(done);
})
it("rejects when update requested with module on denyUpdateList", function(done) {
installer.init({ externalModules: { palette: { denyUpdateList: ['this_wont_exist'] } } })
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var nodeInfo = {nodes:{module:"this_is_allowed",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
sinon.stub(typeRegistry,"setModulePendingUpdated").callsFake(function() {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql('update_not_allowed');
done();
}).catch(done);
})
it("succeeds when update requested with module on allowUpdateList", function(done) {
installer.init({ externalModules: { palette: { allowUpdateList: ['this_is_allowed'] } } })
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var nodeInfo = {nodes:{module:"this_is_allowed",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
sinon.stub(typeRegistry,"setModulePendingUpdated").callsFake(function() {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_is_allowed","0.1.2").then(function() {
done();
}).catch(done);
})
it("rejects when update requested with module not on allowUpdateList", function(done) {
installer.init({ externalModules: { palette: { allowUpdateList: ['this_is_allowed'] } } })
sinon.stub(typeRegistry,"getModuleInfo").callsFake(function() {
return {
user: true,
version: "0.1.1"
}
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var nodeInfo = {nodes:{module:"this_wont_exist",types:["a"]}};
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
sinon.stub(typeRegistry,"setModulePendingUpdated").callsFake(function() {
return Promise.resolve(nodeInfo);
});
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql('update_not_allowed');
done();
}).catch(done);
})
});
});
describe("uninstalls module", function() {
it("rejects invalid module names", function(done) {
var promises = [];
var rejectedCount = 0;
promises.push(installer.uninstallModule("this_wont_exist ").catch(() => {rejectedCount++}));
promises.push(installer.uninstallModule("this_wont_exist;no_it_really_wont").catch(() => {rejectedCount++}));
Promise.all(promises).then(function() {
rejectedCount.should.eql(2);
done();
}).catch(done);
});
it("rejects with generic error", function(done) {
var nodeInfo = [{module:"foo",types:["a"]}];
var removeModule = sinon.stub(registry,"removeModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
var res = {
code: 1,
stdout:"",
stderr:"error"
}
var p = Promise.reject(res);
p.catch((err)=>{});
execResponse = p;
installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
}).catch(err => {
// Expected result
done()
});
});
it("succeeds when module is found", function(done) {
var nodeInfo = [{module:"foo",types:["a"]}];
var removeModule = sinon.stub(typeRegistry,"removeModule").callsFake(function(md) {
return nodeInfo;
});
var getModuleInfo = sinon.stub(registry,"getModuleInfo").callsFake(function(md) {
return {nodes:[]};
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
sinon.stub(fs,"statSync").callsFake(function(fn) { return {}; });
installer.uninstallModule("this_wont_exist").then(function(info) {
info.should.eql(nodeInfo);
// commsMessages.should.have.length(1);
// commsMessages[0].topic.should.equal("node/removed");
// commsMessages[0].msg.should.eql(nodeInfo);
done();
}).catch(done);
});
});
});

View File

@@ -1,62 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var fs = require("fs");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var library = NR_TEST_UTILS.require("@node-red/registry/lib/library");
describe("library api", function() {
it('returns null list when no modules have been registered', function() {
library.init();
should.not.exist(library.getExampleFlows());
});
it('returns null path when module is not known', function() {
library.init();
should.not.exist(library.getExampleFlowPath('foo','bar'));
});
it('returns a valid example path', function(done) {
library.init();
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
try {
var flows = library.getExampleFlows();
flows.should.deepEqual({"test-module":{"f":["one"]}});
var examplePath = library.getExampleFlowPath('test-module','one');
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
library.removeExamplesDir('test-module');
try {
should.not.exist(library.getExampleFlows());
should.not.exist(library.getExampleFlowPath('test-module','one'));
done();
} catch(err) {
done(err);
}
}catch(err) {
done(err);
}
});
})
});

View File

@@ -1,720 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var fs = require("fs-extra");
var NR_TEST_UTILS = require("nr-test-utils");
var loader = NR_TEST_UTILS.require("@node-red/registry/lib/loader");
var localfilesystem = NR_TEST_UTILS.require("@node-red/registry/lib/localfilesystem");
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
var nodes = NR_TEST_UTILS.require("@node-red/registry");
var resourcesDir = path.resolve(path.join(__dirname,"resources","local"));
describe("red/nodes/registry/loader",function() {
var stubs = [];
before(function() {
sinon.stub(localfilesystem,"init");
});
after(function() {
localfilesystem.init.restore();
});
afterEach(function() {
while(stubs.length) {
stubs.pop().restore();
}
})
describe("#load",function() {
it("load empty set without settings available", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){ return {};}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return {};}));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return false;}}});
loader.load(true).then(function() {
localfilesystem.getNodeFiles.called.should.be.true();
localfilesystem.getNodeFiles.lastCall.args[0].should.be.true();
registry.saveNodeList.called.should.be.false();
done();
})
});
it("load empty set with settings available triggers registery save", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){ return {};}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return {};}));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load(true).then(function() {
registry.saveNodeList.called.should.be.true();
done();
}).catch(function(err) {
done(err);
})
});
it("load core node files scanned by lfs - single node single file", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version": "1.2.3",
"nodes": {
"TestNode1": {
"file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
"module": "node-red",
"name": "TestNode1"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("TestNode1");
module.nodes.TestNode1.should.have.property("id","node-red/TestNode1");
module.nodes.TestNode1.should.have.property("module","node-red");
module.nodes.TestNode1.should.have.property("name","TestNode1");
module.nodes.TestNode1.should.have.property("file");
module.nodes.TestNode1.should.have.property("template");
module.nodes.TestNode1.should.have.property("enabled",true);
module.nodes.TestNode1.should.have.property("loaded",true);
module.nodes.TestNode1.should.have.property("types");
module.nodes.TestNode1.types.should.have.a.length(1);
module.nodes.TestNode1.types[0].should.eql('test-node-1');
module.nodes.TestNode1.should.have.property("config");
module.nodes.TestNode1.config.should.not.eql("");
module.nodes.TestNode1.should.have.property("help");
module.nodes.TestNode1.help.should.have.property("en-US");
module.nodes.TestNode1.should.have.property("namespace","node-red");
nodes.registerType.calledOnce.should.be.true();
nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1');
nodes.registerType.lastCall.args[1].should.eql('test-node-1');
done();
}).catch(function(err) {
done(err);
});
});
it("load core node files scanned by lfs - ignore html if disableEditor true", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version": "1.2.3",
"nodes": {
"TestNode1": {
"file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
"module": "node-red",
"name": "TestNode1"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{disableEditor: true, available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("TestNode1");
module.nodes.TestNode1.should.have.property("id","node-red/TestNode1");
module.nodes.TestNode1.should.have.property("module","node-red");
module.nodes.TestNode1.should.have.property("name","TestNode1");
module.nodes.TestNode1.should.have.property("file");
module.nodes.TestNode1.should.have.property("template");
module.nodes.TestNode1.should.have.property("enabled",true);
module.nodes.TestNode1.should.have.property("loaded",true);
// With disableEditor true, the types property is not populated by the
// html file - but instead is populated as nodes register themselves.
// But for this test, we have stubbed out registerType, so we won't get any types
// module.nodes.TestNode1.should.have.property("types");
// module.nodes.TestNode1.types.should.have.a.length(1);
// module.nodes.TestNode1.types[0].should.eql('test-node-1');
// With disableEditor set, config should be blank
module.nodes.TestNode1.should.have.property("config");
module.nodes.TestNode1.config.should.eql("");
// help should be an empty object
module.nodes.TestNode1.should.have.property("help");
module.nodes.TestNode1.help.should.eql({})
module.nodes.TestNode1.should.have.property("namespace","node-red");
nodes.registerType.calledOnce.should.be.true();
nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1');
nodes.registerType.lastCall.args[1].should.eql('test-node-1');
done();
}).catch(function(err) {
done(err);
});
});
it("load core node files scanned by lfs - multiple nodes single file", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version": "4.5.6",
"nodes": {
"MultipleNodes1": {
"file": path.join(resourcesDir,"MultipleNodes1","MultipleNodes1.js"),
"module": "node-red",
"name": "MultipleNodes1"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","4.5.6");
module.should.have.property("nodes");
module.nodes.should.have.property("MultipleNodes1");
module.nodes.MultipleNodes1.should.have.property("id","node-red/MultipleNodes1");
module.nodes.MultipleNodes1.should.have.property("module","node-red");
module.nodes.MultipleNodes1.should.have.property("name","MultipleNodes1");
module.nodes.MultipleNodes1.should.have.property("file");
module.nodes.MultipleNodes1.should.have.property("template");
module.nodes.MultipleNodes1.should.have.property("enabled",true);
module.nodes.MultipleNodes1.should.have.property("loaded",true);
module.nodes.MultipleNodes1.should.have.property("types");
module.nodes.MultipleNodes1.types.should.have.a.length(2);
module.nodes.MultipleNodes1.types[0].should.eql('test-node-multiple-1a');
module.nodes.MultipleNodes1.types[1].should.eql('test-node-multiple-1b');
module.nodes.MultipleNodes1.should.have.property("config");
module.nodes.MultipleNodes1.should.have.property("help");
module.nodes.MultipleNodes1.should.have.property("namespace","node-red");
nodes.registerType.calledTwice.should.be.true();
nodes.registerType.firstCall.args[0].should.eql('node-red/MultipleNodes1');
nodes.registerType.firstCall.args[1].should.eql('test-node-multiple-1a');
nodes.registerType.secondCall.args[0].should.eql('node-red/MultipleNodes1');
nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b');
done();
}).catch(function(err) {
done(err);
});
});
it("load core node files scanned by lfs - node with promise", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version":"2.4.6",
"nodes": {
"TestNode2": {
"file": path.join(resourcesDir,"TestNode2","TestNode2.js"),
"module": "node-red",
"name": "TestNode2"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","2.4.6");
module.should.have.property("nodes");
module.nodes.should.have.property("TestNode2");
module.nodes.TestNode2.should.have.property("id","node-red/TestNode2");
module.nodes.TestNode2.should.have.property("module","node-red");
module.nodes.TestNode2.should.have.property("name","TestNode2");
module.nodes.TestNode2.should.have.property("file");
module.nodes.TestNode2.should.have.property("template");
module.nodes.TestNode2.should.have.property("enabled",true);
module.nodes.TestNode2.should.have.property("loaded",true);
module.nodes.TestNode2.should.have.property("types");
module.nodes.TestNode2.types.should.have.a.length(1);
module.nodes.TestNode2.types[0].should.eql('test-node-2');
module.nodes.TestNode2.should.have.property("config");
module.nodes.TestNode2.should.have.property("help");
module.nodes.TestNode2.should.have.property("namespace","node-red");
module.nodes.TestNode2.should.not.have.property('err');
nodes.registerType.calledOnce.should.be.true();
nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode2');
nodes.registerType.lastCall.args[1].should.eql('test-node-2');
done();
}).catch(function(err) {
done(err);
});
});
it("load core node files scanned by lfs - node with rejecting promise", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version":"1.2.3",
"nodes": {
"TestNode3": {
"file": path.join(resourcesDir,"TestNode3","TestNode3.js"),
"module": "node-red",
"name": "TestNode3"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("TestNode3");
module.nodes.TestNode3.should.have.property("id","node-red/TestNode3");
module.nodes.TestNode3.should.have.property("module","node-red");
module.nodes.TestNode3.should.have.property("name","TestNode3");
module.nodes.TestNode3.should.have.property("file");
module.nodes.TestNode3.should.have.property("template");
module.nodes.TestNode3.should.have.property("enabled",true);
module.nodes.TestNode3.should.have.property("loaded",false);
module.nodes.TestNode3.should.have.property("types");
module.nodes.TestNode3.types.should.have.a.length(1);
module.nodes.TestNode3.types[0].should.eql('test-node-3');
module.nodes.TestNode3.should.have.property("config");
module.nodes.TestNode3.should.have.property("help");
module.nodes.TestNode3.should.have.property("namespace","node-red");
module.nodes.TestNode3.should.have.property('err','fail');
nodes.registerType.called.should.be.false();
done();
}).catch(function(err) {
done(err);
});
});
it("load core node files scanned by lfs - missing file", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"version":"1.2.3",
"nodes": {
"DoesNotExist": {
"file": path.join(resourcesDir,"doesnotexist"),
"module": "node-red",
"name": "DoesNotExist"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("DoesNotExist");
module.nodes.DoesNotExist.should.have.property("id","node-red/DoesNotExist");
module.nodes.DoesNotExist.should.have.property("module","node-red");
module.nodes.DoesNotExist.should.have.property("name","DoesNotExist");
module.nodes.DoesNotExist.should.have.property("file");
module.nodes.DoesNotExist.should.have.property("template");
module.nodes.DoesNotExist.should.have.property("enabled",true);
module.nodes.DoesNotExist.should.have.property("loaded",false);
module.nodes.DoesNotExist.should.have.property("types");
module.nodes.DoesNotExist.types.should.have.a.length(0);
module.nodes.DoesNotExist.should.have.property("config","");
module.nodes.DoesNotExist.should.have.property("help",{});
module.nodes.DoesNotExist.should.have.property("namespace","node-red");
module.nodes.DoesNotExist.should.have.property('err');
nodes.registerType.called.should.be.false();
done();
}).catch(function(err) {
done(err);
});
});
// it("load core node files scanned by lfs - missing html file", function(done) {
// // This is now an okay situation
// stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
// var result = {};
// result["node-red"] = {
// "name": "node-red",
// "version": "1.2.3",
// "nodes": {
// "DuffNode": {
// "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
// "module": "node-red",
// "name": "DuffNode"
// }
// }
// };
// return result;
// }));
//
// stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
// stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
// // This module isn't already loaded
// stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
//
// stubs.push(sinon.stub(nodes,"registerType"));
// loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
// loader.load().then(function(result) {
//
// registry.addModule.called.should.be.true();
// var module = registry.addModule.lastCall.args[0];
// module.should.have.property("name","node-red");
// module.should.have.property("version","1.2.3");
// module.should.have.property("nodes");
// module.nodes.should.have.property("DuffNode");
// module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
// module.nodes.DuffNode.should.have.property("module","node-red");
// module.nodes.DuffNode.should.have.property("name","DuffNode");
// module.nodes.DuffNode.should.have.property("file");
// module.nodes.DuffNode.should.have.property("template");
// module.nodes.DuffNode.should.have.property("enabled",true);
// module.nodes.DuffNode.should.have.property("loaded",false);
// module.nodes.DuffNode.should.have.property("types");
// module.nodes.DuffNode.types.should.have.a.length(0);
// module.nodes.DuffNode.should.have.property("config","");
// module.nodes.DuffNode.should.have.property("help",{});
// module.nodes.DuffNode.should.have.property("namespace","node-red");
// module.nodes.DuffNode.should.have.property('err');
// module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
//
// nodes.registerType.called.should.be.false();
//
// done();
// }).catch(function(err) {
// done(err);
// });
// });
});
describe("#addModule",function() {
it("throws error if settings unavailable", function() {
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return false;}}});
/*jshint immed: false */
(function(){
loader.addModule("test-module");
}).should.throw("Settings unavailable");
});
it("returns rejected error if module already loaded", function(done) {
stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){return{}}));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").catch(function(err) {
err.code.should.eql("module_already_loaded");
done();
});
});
it("returns rejected error if module not found", function(done) {
stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){return null}));
stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function() {
throw new Error("failure");
}));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").catch(function(err) {
err.message.should.eql("failure");
done();
});
});
it("loads module by name", function(done) {
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function(){
var result = {};
result["TestNodeModule"] = {
"name": "TestNodeModule",
"version": "1.2.3",
"nodes": {
"TestNode1": {
"file": path.join(resourcesDir,"TestNodeModule","node_modules","TestNodeModule","TestNodeModule.js"),
"module": "TestNodeModule",
"name": "TestNode1",
"version": "1.2.3"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return "a node list" }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("TestNodeModule").then(function(result) {
result.should.eql("TestNodeModule");
registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","TestNodeModule");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("TestNode1");
module.nodes.TestNode1.should.have.property("id","TestNodeModule/TestNode1");
module.nodes.TestNode1.should.have.property("module","TestNodeModule");
module.nodes.TestNode1.should.have.property("name","TestNode1");
module.nodes.TestNode1.should.have.property("file");
module.nodes.TestNode1.should.have.property("template");
module.nodes.TestNode1.should.have.property("enabled",true);
module.nodes.TestNode1.should.have.property("loaded",true);
module.nodes.TestNode1.should.have.property("types");
module.nodes.TestNode1.types.should.have.a.length(1);
module.nodes.TestNode1.types[0].should.eql('test-node-mod-1');
module.nodes.TestNode1.should.have.property("config");
module.nodes.TestNode1.should.have.property("help");
module.nodes.TestNode1.should.have.property("namespace","TestNodeModule");
module.nodes.TestNode1.should.not.have.property('err');
nodes.registerType.calledOnce.should.be.true();
done();
}).catch(function(err) {
done(err);
});
});
it("skips module that fails version check", function(done) {
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){ return null; }));
stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function(){
var result = {};
result["TestNodeModule"] = {
"name": "TestNodeModule",
"version": "1.2.3",
"redVersion":"999.0.0",
"nodes": {
"TestNode1": {
"file": path.join(resourcesDir,"TestNodeModule","node_modules","TestNodeModule","TestNodeModule.js"),
"module": "TestNodeModule",
"name": "TestNode1",
"version": "1.2.3"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return "a node list" }));
stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({log:{"_":function(){},warn:function(){}},nodes:nodes,version: function() { return "0.12.0"}, settings:{available:function(){return true;}}});
loader.addModule("TestNodeModule").then(function(result) {
result.should.eql("TestNodeModule");
registry.addModule.called.should.be.false();
nodes.registerType.called.should.be.false();
done();
}).catch(function(err) {
done(err);
});
});
it.skip('registers a message catalog');
});
describe("#loadNodeSet",function() {
it("no-ops the load if node is not enabled", function(done) {
stubs.push(sinon.stub(nodes,"registerType"));
loader.loadNodeSet({
"file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
"module": "node-red",
"name": "TestNode1",
"enabled": false
}).then(function(node) {
node.enabled.should.be.false();
nodes.registerType.called.should.be.false();
done();
}).catch(function(err) {
done(err);
});
});
it("handles node that errors on require", function(done) {
stubs.push(sinon.stub(nodes,"registerType"));
loader.loadNodeSet({
"file": path.join(resourcesDir,"TestNode4","TestNode4.js"),
"module": "node-red",
"name": "TestNode4",
"enabled": true
}).then(function(node) {
node.enabled.should.be.true();
nodes.registerType.called.should.be.false();
node.should.have.property('err');
node.err.toString().should.eql("Error: fail to require (line:1)");
done();
}).catch(function(err) {
done(err);
});
});
});
describe("#getNodeHelp",function() {
it("returns preloaded help", function() {
loader.getNodeHelp({
help:{
en:"foo"
}
},"en").should.eql("foo");
});
it("loads help, caching result", function() {
stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
return 'bar';
}))
var node = {
template: "/tmp/node/directory/file.html",
help:{
en:"foo"
}
};
loader.getNodeHelp(node,"fr").should.eql("bar");
node.help['fr'].should.eql("bar");
fs.readFileSync.calledOnce.should.be.true();
fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
loader.getNodeHelp(node,"fr").should.eql("bar");
fs.readFileSync.calledOnce.should.be.true();
});
it("loads help, defaulting to en-US content", function() {
stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
throw new Error("not found");
}))
var node = {
template: "/tmp/node/directory/file.html",
help:{}
};
node.help['en-US'] = 'foo';
loader.getNodeHelp(node,"fr").should.eql("foo");
node.help['fr'].should.eql("foo");
fs.readFileSync.calledOnce.should.be.true();
fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
loader.getNodeHelp(node,"fr").should.eql("foo");
fs.readFileSync.calledOnce.should.be.true();
});
it("loads help, defaulting to en-US content for extra nodes", function() {
stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
if (path.indexOf("en-US") >= 0) {
return 'bar';
}
throw new Error("not found");
}));
var node = {
template: "/tmp/node/directory/file.html",
help:{}
};
delete node.help['en-US'];
loader.getNodeHelp(node,"fr").should.eql("bar");
node.help['fr'].should.eql("bar");
fs.readFileSync.calledTwice.should.be.true();
fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html"));
loader.getNodeHelp(node,"fr").should.eql("bar");
fs.readFileSync.calledTwice.should.be.true();
});
it("fails to load en-US help content", function() {
stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
throw new Error("not found");
}));
var node = {
template: "/tmp/node/directory/file.html",
help:{}
};
delete node.help['en-US'];
should.not.exist(loader.getNodeHelp(node,"en-US"));
should.not.exist(node.help['en-US']);
fs.readFileSync.calledTwice.should.be.true();
fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html"));
fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en/file.html"));
should.not.exist(loader.getNodeHelp(node,"en-US"));
fs.readFileSync.callCount.should.eql(4);
});
});
});

View File

@@ -1,276 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var localfilesystem = NR_TEST_UTILS.require("@node-red/registry/lib/localfilesystem");
var resourcesDir = path.resolve(path.join(__dirname,"resources","local"));
var userDir = path.resolve(path.join(__dirname,"resources","userDir"));
var moduleDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule"));
var i18n = NR_TEST_UTILS.require("@node-red/util").i18n;
describe("red/nodes/registry/localfilesystem",function() {
beforeEach(function() {
stubs.push(sinon.stub(i18n,"registerMessageCatalog").callsFake(function() { return Promise.resolve(); }));
})
var stubs = [];
afterEach(function() {
while(stubs.length) {
stubs.pop().restore();
}
})
function checkNodes(nodes,shouldHaveNodes,shouldNotHaveNodes,module) {
for (var i=0;i<shouldHaveNodes.length;i++) {
nodes.should.have.a.property(shouldHaveNodes[i]);
nodes[shouldHaveNodes[i]].should.have.a.property('file');
nodes[shouldHaveNodes[i]].file.should.equal(path.resolve(nodes[shouldHaveNodes[i]].file));
nodes[shouldHaveNodes[i]].should.have.a.property('module',module||'node-red');
nodes[shouldHaveNodes[i]].should.have.a.property('name',shouldHaveNodes[i]);
}
for (i=0;i<shouldNotHaveNodes.length;i++) {
nodes.should.not.have.a.property(shouldNotHaveNodes[i]);
}
}
describe("#getNodeFiles",function() {
it("Finds all the node files in the resources tree",function(done) {
localfilesystem.init({coreNodesDir:resourcesDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
var nodes = nm.nodes;
checkNodes(nm.nodes,['TestNode1','MultipleNodes1','NestedNode','TestNode2','TestNode3','TestNode4'],['TestNodeModule']);
i18n.registerMessageCatalog.called.should.be.true();
i18n.registerMessageCatalog.lastCall.args[0].should.eql('node-red');
i18n.registerMessageCatalog.lastCall.args[1].should.eql(path.resolve(path.join(resourcesDir,"locales")));
i18n.registerMessageCatalog.lastCall.args[2].should.eql('messages.json');
done();
});
it("Includes node files from settings",function(done) {
localfilesystem.init({nodesIncludes:['TestNode1.js'],coreNodesDir:resourcesDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNode1'],['MultipleNodes1','NestedNode','TestNode2','TestNode3','TestNode4','TestNodeModule']);
done();
});
it("Excludes node files from settings",function(done) {
localfilesystem.init({nodesExcludes:['TestNode1.js'],coreNodesDir:resourcesDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['MultipleNodes1','NestedNode','TestNode2','TestNode3','TestNode4'],['TestNode1','TestNodeModule']);
done();
});
it("Finds nodes in userDir/nodes",function(done) {
localfilesystem.init({userDir:userDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNode5'],['TestNode1']);
done();
});
it("Finds nodes in settings.nodesDir (string)",function(done) {
localfilesystem.init({nodesDir:userDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNode5'],['TestNode1']);
done();
});
it("Finds nodes in settings.nodesDir (string,relative path)",function(done) {
var relativeUserDir = path.join("test","unit","@node-red","registry","lib","resources","userDir");
localfilesystem.init({nodesDir:relativeUserDir});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNode5'],['TestNode1']);
done();
});
it("Finds nodes in settings.nodesDir (array)",function(done) {
localfilesystem.init({nodesDir:[userDir]});
var nodeList = localfilesystem.getNodeFiles(true);
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNode5'],['TestNode1']);
done();
});
it("Finds nodes module path",function(done) {
var _join = path.join;
stubs.push(sinon.stub(path,"join").callsFake(function() {
if (arguments[0] == resourcesDir) {
// This stops the module tree scan from going any higher
// up the tree than resourcesDir.
return arguments[0];
}
return _join.apply(null,arguments);
}));
localfilesystem.init({coreNodesDir:moduleDir});
var nodeList = localfilesystem.getNodeFiles();
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];
// The `node-red` module is loaded differently to those scanned for
// It doesn't get the `path` property set. Maybe it should.
nm.should.have.a.property('name','node-red');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,[],['TestNode1']);
nm = nodeList['TestNodeModule'];
nm.should.have.a.property('path')
nm.should.have.a.property('name','TestNodeModule');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['TestNodeMod1','TestNodeMod2'],[],'TestNodeModule');
nm = nodeList['VersionMismatchModule'];
nm.should.have.a.property('path')
nm.should.have.a.property('name','VersionMismatchModule');
nm.should.have.a.property("nodes");
checkNodes(nm.nodes,['VersionMismatchMod1','VersionMismatchMod2'],[],'VersionMismatchModule');
i18n.registerMessageCatalog.called.should.be.true();
i18n.registerMessageCatalog.lastCall.args[0].should.eql('node-red');
i18n.registerMessageCatalog.lastCall.args[1].should.eql(path.resolve(path.join(moduleDir,"locales")));
i18n.registerMessageCatalog.lastCall.args[2].should.eql('messages.json');
done();
});
it.skip("finds locales directory");
it.skip("finds icon path directory");
it("scans icon files in the resources tree",function(done) {
var count = 0;
localfilesystem.init({
coreNodesDir: resourcesDir
});
var list = localfilesystem.getNodeFiles(true);
list.should.have.property("node-red");
list["node-red"].should.have.property("icons");
list["node-red"].icons.should.have.length(1);
list["node-red"].icons[0].should.have.property("path",path.join(__dirname,"resources/local/NestedDirectoryNode/NestedNode/icons"))
list["node-red"].icons[0].should.have.property("icons");
list["node-red"].icons[0].icons.should.have.length(1);
list["node-red"].icons[0].icons[0].should.eql("arrow-in.png");
done();
});
it("scans icons dir in library",function(done) {
var count = 0;
localfilesystem.init({
userDir: userDir
});
var list = localfilesystem.getNodeFiles(true);
list.should.have.property("node-red");
list["node-red"].should.have.property("icons");
list["node-red"].icons.should.have.length(1);
list["node-red"].icons[0].should.have.property("path",path.join(__dirname,"resources/userDir/lib/icons"))
list["node-red"].icons[0].should.have.property("icons");
list["node-red"].icons[0].icons.should.have.length(1);
list["node-red"].icons[0].icons[0].should.eql("test_icon.png");
done();
});
});
describe("#getModuleFiles",function() {
it("gets a nodes module files",function(done) {
var _join = path.join;
stubs.push(sinon.stub(path,"join").callsFake(function() {
if (arguments[0] == resourcesDir) {
// This stops the module tree scan from going any higher
// up the tree than resourcesDir.
return arguments[0];
}
return _join.apply(null,arguments);
}));
localfilesystem.init({coreNodesDir:moduleDir});
var nodeModule = localfilesystem.getModuleFiles('TestNodeModule');
nodeModule.should.have.a.property('TestNodeModule');
nodeModule['TestNodeModule'].should.have.a.property('name','TestNodeModule');
nodeModule['TestNodeModule'].should.have.a.property('version','0.0.1');
nodeModule['TestNodeModule'].should.have.a.property('nodes');
nodeModule['TestNodeModule'].should.have.a.property('path');
checkNodes(nodeModule['TestNodeModule'].nodes,['TestNodeMod1','TestNodeMod2'],[],'TestNodeModule');
nodeModule = localfilesystem.getModuleFiles('VersionMismatchModule');
nodeModule.should.have.a.property('VersionMismatchModule');
nodeModule['VersionMismatchModule'].should.have.a.property('redVersion','100.0.0');
done();
});
it("throws an error if a node isn't found",function(done) {
var _join = path.join;
stubs.push(sinon.stub(path,"join").callsFake(function() {
if (arguments[0] == resourcesDir) {
// This stops the module tree scan from going any higher
// up the tree than resourcesDir.
return arguments[0];
}
return _join.apply(null,arguments);
}));
localfilesystem.init({coreNodesDir:moduleDir});
/*jshint immed: false */
(function(){
localfilesystem.getModuleFiles('WontExistModule');
}).should.throw();
done();
});
it.skip("finds locales directory");
it.skip("finds icon path directory");
it("scans icon files with a module file",function(done) {
var _join = path.join;
stubs.push(sinon.stub(path,"join").callsFake(function() {
if (arguments[0] == resourcesDir) {
// This stops the module tree scan from going any higher
// up the tree than resourcesDir.
return arguments[0];
}
return _join.apply(null,arguments);
}));
localfilesystem.init({
coreNodesDir: moduleDir
});
var nodeModule = localfilesystem.getModuleFiles('TestNodeModule');
nodeModule.should.have.property("TestNodeModule");
nodeModule.TestNodeModule.should.have.property('icons');
nodeModule.TestNodeModule.icons.should.have.length(1);
nodeModule.TestNodeModule.icons[0].should.have.property("path");
nodeModule.TestNodeModule.icons[0].should.have.property("icons");
nodeModule.TestNodeModule.icons[0].icons[0].should.eql("arrow-in.png");
done();
});
});
});

View File

@@ -1,200 +0,0 @@
const should = require("should");
const sinon = require("sinon");
const path = require("path");
const NR_TEST_UTILS = require("nr-test-utils");
const plugins = NR_TEST_UTILS.require("@node-red/registry/lib/plugins");
const registry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
const { events } = NR_TEST_UTILS.require("@node-red/util");
describe("red/nodes/registry/plugins",function() {
let receivedEvents = [];
let modules;
function handleEvent(evnt) {
receivedEvents.push(evnt);
}
beforeEach(function() {
plugins.init({});
receivedEvents = [];
modules = {
"test-module": {
plugins: {
"test-set": {
id: "test-module/test-set",
enabled: true,
config: "test-module-config",
plugins: []
},
"test-disabled-set": {
id: "test-module/test-disabled-set",
enabled: false,
config: "disabled-plugin-config",
plugins: []
}
}
}
}
events.on("registry:plugin-added",handleEvent);
sinon.stub(registry,"getModule").callsFake(moduleId => modules[moduleId]);
sinon.stub(registry,"getModuleList").callsFake(() => modules)
});
afterEach(function() {
events.removeListener("registry:plugin-added",handleEvent);
registry.getModule.restore();
registry.getModuleList.restore();
})
describe("registerPlugin", function() {
it("registers a plugin", function() {
let pluginDef = {}
plugins.registerPlugin("test-module/test-set","a-plugin",pluginDef);
receivedEvents.length.should.eql(1);
receivedEvents[0].should.eql("a-plugin");
should.exist(modules['test-module'].plugins['test-set'].plugins[0])
modules['test-module'].plugins['test-set'].plugins[0].should.equal(pluginDef)
})
it("calls a plugins onadd function", function() {
let pluginDef = { onadd: sinon.stub() }
plugins.registerPlugin("test-module/test-set","a-plugin",pluginDef);
pluginDef.onadd.called.should.be.true();
})
})
describe("getPlugin", function() {
it("returns a registered plugin", function() {
let pluginDef = {}
plugins.registerPlugin("test-module/test-set","a-plugin",pluginDef);
pluginDef.should.equal(plugins.getPlugin("a-plugin"));
})
})
describe("getPluginsByType", function() {
it("returns a plugins of a given type", function() {
let pluginDef = {type: "foo"}
let pluginDef2 = {type: "bar"}
let pluginDef3 = {type: "foo"}
plugins.registerPlugin("test-module/test-set","a-plugin",pluginDef);
plugins.registerPlugin("test-module/test-set","a-plugin2",pluginDef2);
plugins.registerPlugin("test-module/test-set","a-plugin3",pluginDef3);
let fooPlugins = plugins.getPluginsByType("foo");
let barPlugins = plugins.getPluginsByType("bar");
let noPlugins = plugins.getPluginsByType("none");
noPlugins.should.be.of.length(0);
fooPlugins.should.be.of.length(2);
fooPlugins.should.containEql(pluginDef);
fooPlugins.should.containEql(pluginDef3);
barPlugins.should.be.of.length(1);
barPlugins.should.containEql(pluginDef2);
})
})
describe("getPluginConfigs", function() {
it("gets all plugin configs", function() {
let configs = plugins.getPluginConfigs("en-US");
configs.should.eql(`
<!-- --- [red-plugin:test-module/test-set] --- -->
test-module-config`)
})
})
describe("getPluginList", function() {
it("returns a plugins of a given type", function() {
let pluginDef = {type: "foo"}
let pluginDef2 = {type: "bar"}
let pluginDef3 = {type: "foo"}
plugins.registerPlugin("test-module/test-set","a-plugin",pluginDef);
plugins.registerPlugin("test-module/test-set","a-plugin2",pluginDef2);
plugins.registerPlugin("test-module/test-set","a-plugin3",pluginDef3);
let pluginList = plugins.getPluginList();
JSON.stringify(pluginList).should.eql(JSON.stringify(
[
{
"id": "test-module/test-set",
"enabled": true,
"local": false,
"user": false,
"plugins": [
{
"type": "foo",
"id": "a-plugin",
"module": "test-module"
},
{
"type": "bar",
"id": "a-plugin2",
"module": "test-module"
},
{
"type": "foo",
"id": "a-plugin3",
"module": "test-module"
}
]
},
{
"id": "test-module/test-disabled-set",
"enabled": false,
"local": false,
"user": false,
"plugins": []
}
]
))
})
})
describe("exportPluginSettings", function() {
it("exports plugin settings - default false", function() {
plugins.init({ "a-plugin": { a: 123, b:234, c: 345} });
plugins.registerPlugin("test-module/test-set","a-plugin",{
settings: {
a: { exportable: true },
b: {exportable: false },
d: { exportable: true, value: 456}
}
});
var exportedSet = {};
plugins.exportPluginSettings(exportedSet);
exportedSet.should.have.property("a-plugin");
// a is exportable
exportedSet["a-plugin"].should.have.property("a",123);
// b is explicitly not exportable
exportedSet["a-plugin"].should.not.have.property("b");
// c isn't listed and default false
exportedSet["a-plugin"].should.not.have.property("c");
// d has a default value
exportedSet["a-plugin"].should.have.property("d",456);
})
it("exports plugin settings - default true", function() {
plugins.init({ "a-plugin": { a: 123, b:234, c: 345} });
plugins.registerPlugin("test-module/test-set","a-plugin",{
settings: {
'*': { exportable: true },
a: { exportable: true },
b: {exportable: false },
d: { exportable: true, value: 456}
}
});
var exportedSet = {};
plugins.exportPluginSettings(exportedSet);
exportedSet.should.have.property("a-plugin");
// a is exportable
exportedSet["a-plugin"].should.have.property("a",123);
// b is explicitly not exportable
exportedSet["a-plugin"].should.not.have.property("b");
// c isn't listed, but default true
exportedSet["a-plugin"].should.have.property("c");
// d has a default value
exportedSet["a-plugin"].should.have.property("d",456);
})
});
});

View File

@@ -1,614 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
const { events } = NR_TEST_UTILS.require("@node-red/util");
describe("red/nodes/registry/registry",function() {
afterEach(function() {
typeRegistry.clear();
});
function stubSettings(s,available,initialConfig) {
s.available = function() {return available;};
s.set = sinon.spy(function(s,v) { return Promise.resolve();});
s.get = function(s) { return initialConfig;};
return s;
}
var settings = stubSettings({},false,null);
var settingsWithStorageAndInitialConfig = stubSettings({},true,{"node-red":{module:"testModule",name:"testName",version:"testVersion",nodes:{"node":{id:"node-red/testName",name:"test",types:["a","b"],enabled:true}}}});
var testNodeSet1 = {
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"]
};
var testNodeSet2 = {
id: "test-module/test-name-2",
module: "test-module",
name: "test-name-2",
enabled: true,
loaded: false,
config: "configB",
types: [ "test-c","test-d"]
};
var testNodeSet2WithError = {
id: "test-module/test-name-2",
module: "test-module",
name: "test-name-2",
enabled: true,
loaded: false,
err: "I have an error",
config: "configC",
types: [ "test-c","test-d"]
};
var testNodeSet3 = {
id: "test-module-2/test-name-3",
module: "test-module-2",
name: "test-name-3",
enabled: true,
loaded: false,
config: "configB",
types: [ "test-a","test-e"]
};
describe('#init/load', function() {
it('loads initial config', function(done) {
typeRegistry.init(settingsWithStorageAndInitialConfig,null);
typeRegistry.getNodeList().should.have.lengthOf(0);
typeRegistry.load();
typeRegistry.getNodeList().should.have.lengthOf(1);
done();
});
it('migrates legacy format', function(done) {
var legacySettings = {
available: function() { return true; },
set: sinon.stub().returns(Promise.resolve()),
get: function() { return {
"123": {
"name": "72-sentiment.js",
"types": [
"sentiment"
],
"enabled": true
},
"456": {
"name": "20-inject.js",
"types": [
"inject"
],
"enabled": true
},
"789": {
"name": "testModule:a-module.js",
"types": [
"example"
],
"enabled":true,
"module":"testModule"
}
}}
};
var expected = JSON.parse('{"node-red":{"name":"node-red","nodes":{"sentiment":{"name":"sentiment","types":["sentiment"],"enabled":true,"module":"node-red"},"inject":{"name":"inject","types":["inject"],"enabled":true,"module":"node-red"}}},"testModule":{"name":"testModule","nodes":{"a-module.js":{"name":"a-module.js","types":["example"],"enabled":true,"module":"testModule"}}}}');
typeRegistry.init(legacySettings,null);
typeRegistry.load();
legacySettings.set.calledOnce.should.be.true();
legacySettings.set.args[0][1].should.eql(expected);
done();
});
});
describe.skip('#addNodeSet', function() {
it('adds a node set for an unknown module', function() {
typeRegistry.init(settings,null);
typeRegistry.getNodeList().should.have.lengthOf(0);
typeRegistry.getModuleList().should.eql({});
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1");
typeRegistry.getNodeList().should.have.lengthOf(1);
var moduleList = typeRegistry.getModuleList();
moduleList.should.have.a.property("test-module");
moduleList["test-module"].should.have.a.property("name","test-module");
moduleList["test-module"].should.have.a.property("version","0.0.1");
moduleList["test-module"].should.have.a.property("nodes");
moduleList["test-module"].nodes.should.have.a.property("test-name");
moduleList["test-module"].nodes["test-name"].should.eql({
config: 'configA',
id: 'test-module/test-name',
module: 'test-module',
name: 'test-name',
enabled: true,
loaded: false,
types: [ 'test-a', 'test-b' ]
});
});
it('adds a node set to an existing module', function() {
typeRegistry.init(settings,null);
typeRegistry.getNodeList().should.have.lengthOf(0);
typeRegistry.getModuleList().should.eql({});
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1");
typeRegistry.getNodeList().should.have.lengthOf(1);
var moduleList = typeRegistry.getModuleList();
Object.keys(moduleList).should.have.a.lengthOf(1);
moduleList.should.have.a.property("test-module");
moduleList["test-module"].should.have.a.property("name","test-module");
moduleList["test-module"].should.have.a.property("version","0.0.1");
moduleList["test-module"].should.have.a.property("nodes");
Object.keys(moduleList["test-module"].nodes).should.have.a.lengthOf(1);
moduleList["test-module"].nodes.should.have.a.property("test-name");
typeRegistry.addNodeSet("test-module/test-name-2",testNodeSet2);
typeRegistry.getNodeList().should.have.lengthOf(2);
moduleList = typeRegistry.getModuleList();
Object.keys(moduleList).should.have.a.lengthOf(1);
Object.keys(moduleList["test-module"].nodes).should.have.a.lengthOf(2);
moduleList["test-module"].nodes.should.have.a.property("test-name");
moduleList["test-module"].nodes.should.have.a.property("test-name-2");
});
it('doesnt add node set types if node set has an error', function() {
typeRegistry.init(settings,null);
typeRegistry.getNodeList().should.have.lengthOf(0);
typeRegistry.getModuleList().should.eql({});
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1");
typeRegistry.getTypeId("test-a").should.eql("test-module/test-name");
should.not.exist(typeRegistry.getTypeId("test-c"));
typeRegistry.addNodeSet("test-module/test-name-2",testNodeSet2WithError, "0.0.1");
should.not.exist(typeRegistry.getTypeId("test-c"));
});
it('doesnt add node set if type already exists', function() {
typeRegistry.init(settings,null);
typeRegistry.getNodeList().should.have.lengthOf(0);
typeRegistry.getModuleList().should.eql({});
should.not.exist(typeRegistry.getTypeId("test-e"));
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1");
typeRegistry.getNodeList().should.have.lengthOf(1);
should.exist(typeRegistry.getTypeId("test-a"));
typeRegistry.addNodeSet(testNodeSet3.id,testNodeSet3, "0.0.1");
typeRegistry.getNodeList().should.have.lengthOf(2);
// testNodeSet3 registers a duplicate test-a and unique test-e
// as test-a is a duplicate, test-e should not get registered
should.not.exist(typeRegistry.getTypeId("test-e"));
var testNodeSet3Result = typeRegistry.getNodeList()[1];
should.exist(testNodeSet3Result.err);
testNodeSet3Result.err.code.should.equal("type_already_registered");
testNodeSet3Result.err.details.type.should.equal("test-a");
testNodeSet3Result.err.details.moduleA.should.equal("test-module");
testNodeSet3Result.err.details.moduleB.should.equal("test-module-2");
//
// typeRegistry.addNodeSet("test-module/test-name-2",testNodeSet2WithError, "0.0.1");
//
// should.not.exist(typeRegistry.getTypeId("test-c"));
});
});
describe("#enableNodeSet", function() {
it('throws error if settings unavailable', function() {
typeRegistry.init(settings,null);
/*jshint immed: false */
(function(){
typeRegistry.enableNodeSet("test-module/test-name");
}).should.throw("Settings unavailable");
});
it('throws error if module unknown', function() {
typeRegistry.init(settingsWithStorageAndInitialConfig,null);
/*jshint immed: false */
(function(){
typeRegistry.enableNodeSet("test-module/unknown");
}).should.throw("Unrecognised id: test-module/unknown");
});
it.skip('enables the node',function(){})
});
describe("#disableNodeSet", function() {
it('throws error if settings unavailable', function() {
typeRegistry.init(settings,null);
/*jshint immed: false */
(function(){
typeRegistry.disableNodeSet("test-module/test-name");
}).should.throw("Settings unavailable");
});
it('throws error if module unknown', function() {
typeRegistry.init(settingsWithStorageAndInitialConfig,null);
/*jshint immed: false */
(function(){
typeRegistry.disableNodeSet("test-module/unknown");
}).should.throw("Unrecognised id: test-module/unknown");
});
it.skip('disables the node',function(){})
});
describe('#getNodeConfig', function() {
it('returns nothing for an unregistered type config', function(done) {
typeRegistry.init(settings,null);
var config = typeRegistry.getNodeConfig("imaginary-shark");
(config === null).should.be.true();
done();
});
});
describe('#saveNodeList',function() {
it('rejects when settings unavailable',function(done) {
typeRegistry.init(stubSettings({},false,{}),null);
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {"test-name":{module:"test-module",name:"test-name",types:[]}}});
typeRegistry.saveNodeList().catch(function(err) {
done();
});
});
it('saves the list',function(done) {
var s = stubSettings({},true,{});
typeRegistry.init(s,null);
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":testNodeSet1,
"test-name-2":testNodeSet2WithError
}});
typeRegistry.saveNodeList().then(function() {
s.set.called.should.be.true();
s.set.lastCall.args[0].should.eql('nodes');
var nodes = s.set.lastCall.args[1];
nodes.should.have.property('test-module');
for (var n in nodes['test-module'].nodes) {
if (nodes['test-module'].nodes.hasOwnProperty(n)) {
var nn = nodes['test-module'].nodes[n];
nn.should.not.have.property('err');
nn.should.not.have.property('id');
}
}
done();
}).catch(function(err) {
done(err);
});
});
});
describe('#removeModule',function() {
it('throws error for unknown module', function() {
var s = stubSettings({},true,{});
typeRegistry.init(s,null);
/*jshint immed: false */
(function(){
typeRegistry.removeModule("test-module/unknown");
}).should.throw("Unrecognised module: test-module/unknown");
});
it('throws error for unavaiable settings', function() {
var s = stubSettings({},false,{});
typeRegistry.init(s,null);
/*jshint immed: false */
(function(){
typeRegistry.removeModule("test-module/unknown");
}).should.throw("Settings unavailable");
});
it('removes a known module', function() {
var s = stubSettings({},true,{});
typeRegistry.init(s,null);
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":testNodeSet1
}});
var moduleList = typeRegistry.getModuleList();
moduleList.should.have.a.property("test-module");
typeRegistry.getNodeList().should.have.lengthOf(1);
var info = typeRegistry.removeModule('test-module');
moduleList = typeRegistry.getModuleList();
moduleList.should.not.have.a.property("test-module");
typeRegistry.getNodeList().should.have.lengthOf(0);
});
});
describe('#get[All]NodeConfigs', function() {
it('returns node config', function() {
typeRegistry.init(settings,{
getNodeHelp: function(config) { return "HE"+config.name+"LP" }
});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"]
},
"test-name-2":{
id: "test-module/test-name-2",
module: "test-module",
name: "test-name-2",
enabled: true,
loaded: false,
config: "configB",
types: [ "test-c","test-d"]
}
}});
typeRegistry.getNodeConfig("test-module/test-name").should.eql('<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP');
typeRegistry.getNodeConfig("test-module/test-name-2").should.eql('<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP');
typeRegistry.getAllNodeConfigs().should.eql('\n<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP\n<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP');
});
});
describe('#getModuleInfo', function() {
it('returns module info', function() {
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
}});
var moduleInfo = typeRegistry.getModuleInfo("test-module");
moduleInfo.should.have.a.property('name','test-module');
moduleInfo.should.have.a.property('version','0.0.1');
moduleInfo.should.have.a.property('nodes');
moduleInfo.nodes.should.have.a.lengthOf(1);
moduleInfo.nodes[0].should.have.a.property('id','test-module/test-name');
moduleInfo.nodes[0].should.not.have.a.property('file');
});
});
describe('#getNodeInfo', function() {
it('returns node info', function() {
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
}});
var nodeSetInfo = typeRegistry.getNodeInfo("test-module/test-name");
nodeSetInfo.should.have.a.property('id',"test-module/test-name");
nodeSetInfo.should.not.have.a.property('config');
nodeSetInfo.should.not.have.a.property('file');
});
});
describe('#getFullNodeInfo', function() {
it('returns node info', function() {
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
}});
var nodeSetInfo = typeRegistry.getFullNodeInfo("test-module/test-name");
nodeSetInfo.should.have.a.property('id',"test-module/test-name");
nodeSetInfo.should.have.a.property('config');
nodeSetInfo.should.have.a.property('file');
});
});
describe('#cleanModuleList', function() {
it.skip("cleans the module list");
});
describe('#getNodeList', function() {
it("returns a filtered list", function() {
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
},
"test-name-2":{
id: "test-module/test-name-2",
module: "test-module",
name: "test-name-2",
enabled: true,
loaded: false,
config: "configB",
types: [ "test-c","test-d"],
file: "def"
}
}});
var filterCallCount = 0;
var filteredList = typeRegistry.getNodeList(function(n) { filterCallCount++; return n.name === 'test-name-2';});
filterCallCount.should.eql(2);
filteredList.should.have.a.lengthOf(1);
filteredList[0].should.have.a.property('id',"test-module/test-name-2");
});
});
describe('#registerNodeConstructor', function() {
var TestNodeConstructor;
beforeEach(function() {
TestNodeConstructor = function TestNodeConstructor() {};
sinon.stub(events,'emit');
});
afterEach(function() {
events.emit.restore();
});
it('registers a node constructor', function() {
typeRegistry.registerNodeConstructor('node-set','node-type',TestNodeConstructor);
events.emit.calledOnce.should.be.true();
events.emit.lastCall.args[0].should.eql('type-registered');
events.emit.lastCall.args[1].should.eql('node-type');
})
it('throws error on duplicate node registration', function() {
typeRegistry.registerNodeConstructor('node-set','node-type',TestNodeConstructor);
events.emit.calledOnce.should.be.true();
events.emit.lastCall.args[0].should.eql('type-registered');
events.emit.lastCall.args[1].should.eql('node-type');
/*jshint immed: false */
(function(){
typeRegistry.registerNodeConstructor('node-set','node-type',TestNodeConstructor);
}).should.throw("node-type already registered");
events.emit.calledOnce.should.be.true();
});
});
describe('#getNodeIconPath', function() {
it('returns the null when getting an unknown icon', function() {
var iconPath = typeRegistry.getNodeIconPath('random-module','youwonthaveme.png');
should.not.exist(iconPath);
});
it('returns a registered icon' , function() {
var testIcon = path.resolve(__dirname+'/resources/userDir/lib/icons/');
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
},icons: [{path:testIcon,icons:['test_icon.png']}]});
var iconPath = typeRegistry.getNodeIconPath('test-module','test_icon.png');
iconPath.should.eql(path.resolve(testIcon+"/test_icon.png"));
});
it('returns null when getting an unknown module', function() {
var debugIcon = path.resolve(__dirname+'/../../../public/icons/debug.png');
var iconPath = typeRegistry.getNodeIconPath('unknown-module', 'debug.png');
should.not.exist(iconPath);
});
});
describe('#getNodeIcons', function() {
it('returns empty icon list when no modules are registered', function() {
var iconList = typeRegistry.getNodeIcons();
iconList.should.eql({});
});
it('returns an icon list of registered node module', function() {
var testIcon = path.resolve(__dirname+'/resources/userDir/lib/icons/');
typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
},icons: [{path:testIcon,icons:['test_icon.png']}]});
var iconList = typeRegistry.getNodeIcons();
iconList.should.eql({"test-module":["test_icon.png"]});
});
});
describe('#getModuleResource', function() {
beforeEach(function() {
typeRegistry.init(settings,{});
typeRegistry.addModule({
name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
},
resources: {
path: path.join(__dirname, "resources","examples")
}
});
});
it('Returns valid resource path', function() {
const result = typeRegistry.getModuleResource("test-module","one.json");
should.exist(result);
result.should.eql(path.join(__dirname, "resources","examples","one.json"))
});
it('Returns null for path that tries to break out', function() {
// Note - this path exists, but we don't allow .. in the resolved path to
// avoid breaking out of the resources dir
const result = typeRegistry.getModuleResource("test-module","../../index_spec.js");
should.not.exist(result);
});
it('Returns null for path that does not exist', function() {
const result = typeRegistry.getModuleResource("test-module","two.json");
should.not.exist(result);
});
});
});

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function DuffNode(n) {}
RED.nodes.registerType("duff-node",DuffNode);
}

View File

@@ -1,3 +0,0 @@
<script type="text/x-red" data-template-name="test-node-1"></script>
<script type="text/x-red" data-help-name="test-node-1"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-1',{});</script>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function DuplicateTestNode(n) {}
RED.nodes.registerType("test-node-1",DuplicateTestNode);
}

View File

@@ -1,6 +0,0 @@
<script type="text/x-red" data-template-name="test-node-multiple-1a"></script>
<script type="text/x-red" data-help-name="test-node-multiple-1a"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-multiple-1a',{});</script>
<script type="text/x-red" data-template-name="test-node-multiple-1b"></script>
<script type="text/x-red" data-help-name="test-node-multiple-1b"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-multiple-1b',{});</script>

View File

@@ -1,7 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode1(n) {}
RED.nodes.registerType("test-node-multiple-1a",TestNode1);
function TestNode2(n) {}
RED.nodes.registerType("test-node-multiple-1b",TestNode2);
}

View File

@@ -1,4 +0,0 @@
<script type="text/x-red" data-template-name="nested-node-1"></script>
<script type="text/x-red" data-help-name="nested-node-1"></script>
<script type="text/javascript">RED.nodes.registerType('nested-node-1',{});</script>
<style></style>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("nested-node-1",TestNode);
}

View File

@@ -1,4 +0,0 @@
<script type="text/x-red" data-template-name="should-not-load-1"></script>
<script type="text/x-red" data-help-name="should-not-load-1"></script>
<script type="text/javascript">RED.nodes.registerType('should-not-load-1',{});</script>
<style></style>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("should-not-load-1",TestNode);
}

View File

@@ -1,4 +0,0 @@
<script type="text/x-red" data-template-name="should-not-load-2"></script>
<script type="text/x-red" data-help-name="should-not-load-2"></script>
<script type="text/javascript">RED.nodes.registerType('should-not-load-2',{});</script>
<style></style>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("should-not-load-2",TestNode);
}

View File

@@ -1,4 +0,0 @@
<script type="text/x-red" data-template-name="should-not-load-3"></script>
<script type="text/x-red" data-help-name="should-not-load-3"></script>
<script type="text/javascript">RED.nodes.registerType('should-not-load-3',{});</script>
<style></style>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("should-not-load-3",TestNode);
}

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-1"></script>
<script type="text/x-red" data-help-name="test-node-1"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-1',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("test-node-1",TestNode);
}

View File

@@ -1,4 +0,0 @@
<script type="text/x-red" data-template-name="test-node-2"></script>
<script type="text/x-red" data-help-name="test-node-2"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-2',{});</script>
<style></style>

View File

@@ -1,9 +0,0 @@
// A test node that exports a function which returns a resolving promise
module.exports = function(RED) {
return new Promise(function(resolve,reject) {
function TestNode(n) {}
RED.nodes.registerType("test-node-2",TestNode);
resolve();
});
}

View File

@@ -1,3 +0,0 @@
<script type="text/x-red" data-template-name="test-node-3"></script>
<script type="text/x-red" data-help-name="test-node-3"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-3',{});</script>

View File

@@ -1,7 +0,0 @@
// A test node that exports a function which returns a rejecting promise
module.exports = function(RED) {
return new Promise(function(resolve,reject) {
reject("fail");
});
}

View File

@@ -1,3 +0,0 @@
<script type="text/x-red" data-template-name="test-node-3"></script>
<script type="text/x-red" data-help-name="test-node-3"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-3',{});</script>

View File

@@ -1 +0,0 @@
throw new Error("fail to require");

View File

@@ -1 +0,0 @@
This file exists just to ensure the parent directory is in the repository.

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-mod-1"></script>
<script type="text/x-red" data-help-name="test-node-mod-1"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-mod-1',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("test-node-mod-1",TestNode);
}

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-mod-2"></script>
<script type="text/x-red" data-help-name="test-node-mod-2"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-mod-2',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1,4 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
throw new Error("fail to load");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

View File

@@ -1,3 +0,0 @@
This file exists just to ensure the 'icons' directory is in the repository.
TODO: a future test needs to ensure the right icon files are loaded - this
directory can be used for that

View File

@@ -1,11 +0,0 @@
{
"name" : "TestNodeModule",
"version" : "0.0.1",
"description" : "A test node module",
"node-red" : {
"nodes": {
"TestNodeMod1": "TestNodeModule.js",
"TestNodeMod2": "TestNodeModule2.js"
}
}
}

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-mod-1"></script>
<script type="text/x-red" data-help-name="test-node-mod-1"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-mod-1',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1,5 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
function TestNode(n) {}
RED.nodes.registerType("test-node-mod-1",TestNode);
}

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-mod-2"></script>
<script type="text/x-red" data-help-name="test-node-mod-2"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-mod-2',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1,4 +0,0 @@
// A test node that exports a function
module.exports = function(RED) {
throw new Error("fail to load");
}

View File

@@ -1,3 +0,0 @@
This file exists just to ensure the 'icons' directory is in the repository.
TODO: a future test needs to ensure the right icon files are loaded - this
directory can be used for that

View File

@@ -1,12 +0,0 @@
{
"name" : "VersionMismatchModule",
"version" : "0.0.1",
"description" : "A test node module",
"node-red" : {
"version": "100.0.0",
"nodes": {
"VersionMismatchMod1": "TestNodeModule.js",
"VersionMismatchMod2": "TestNodeModule2.js"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

View File

@@ -1,5 +0,0 @@
<script type="text/x-red" data-template-name="test-node-5"></script>
<script type="text/x-red" data-help-name="test-node-5"></script>
<script type="text/javascript">RED.nodes.registerType('test-node-5',{});</script>
<style></style>
<p>this should be filtered out</p>

View File

@@ -1 +0,0 @@
throw new Error("fail to require");

View File

@@ -1,3 +0,0 @@
describe("red/nodes/registry/subflow",function() {
it.skip("NEEDS TESTS");
});

View File

@@ -1,115 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
const should = require("should");
const sinon = require("sinon");
const NR_TEST_UTILS = require("nr-test-utils");
const registryUtil = NR_TEST_UTILS.require("@node-red/registry/lib/util");
// Get the internal runtime api
const runtime = NR_TEST_UTILS.require("@node-red/runtime")._;
const i18n = NR_TEST_UTILS.require("@node-red/util").i18n;
describe("red/nodes/registry/util",function() {
describe("createNodeApi", function() {
let i18n_;
let registerType;
let registerSubflow;
before(function() {
i18n_ = sinon.stub(i18n,"_").callsFake(function() {
return Array.prototype.slice.call(arguments,0);
})
registerType = sinon.stub(runtime.nodes,"registerType");
registerSubflow = sinon.stub(runtime.nodes,"registerSubflow");
});
after(function() {
i18n_.restore();
registerType.restore();
registerSubflow.restore();
})
it("builds node-specific view of runtime api", function() {
registryUtil.init(runtime);
var result = registryUtil.createNodeApi({id: "my-node", namespace: "my-namespace"})
// Need a better strategy here.
// For now, validate the node-custom functions
var message = result._("message");
// This should prepend the node's namespace to the message
message.should.eql([ 'my-namespace:message' ]);
var nodeConstructor = () => {};
var nodeOpts = {};
result.nodes.registerType("type",nodeConstructor, nodeOpts);
registerType.called.should.be.true();
registerType.lastCall.args[0].should.eql("my-node")
registerType.lastCall.args[1].should.eql("type")
registerType.lastCall.args[2].should.eql(nodeConstructor)
registerType.lastCall.args[3].should.eql(nodeOpts)
var subflowDef = {};
result.nodes.registerSubflow(subflowDef);
registerSubflow.called.should.be.true();
registerSubflow.lastCall.args[0].should.eql("my-node")
registerSubflow.lastCall.args[1].should.eql(subflowDef)
});
});
describe("checkModuleAllowed", function() {
function checkList(module, version, allowList, denyList) {
return registryUtil.checkModuleAllowed(
module,
version,
registryUtil.parseModuleList(allowList),
registryUtil.parseModuleList(denyList)
)
}
it("allows module with no allow/deny list provided", function() {
checkList("abc","1.2.3",[],[]).should.be.true();
})
it("defaults allow to * when only deny list is provided", function() {
checkList("abc","1.2.3",["*"],["def"]).should.be.true();
checkList("def","1.2.3",["*"],["def"]).should.be.false();
})
it("uses most specific matching rule", function() {
checkList("abc","1.2.3",["ab*"],["a*"]).should.be.true();
checkList("def","1.2.3",["d*"],["de*"]).should.be.false();
})
it("checks version string using semver rules", function() {
// Deny
checkList("abc","1.2.3",["abc@1.2.2"],["*"]).should.be.false();
checkList("abc","1.2.3",["abc@1.2.4"],["*"]).should.be.false();
checkList("abc","1.2.3",["abc@>1.2.3"],["*"]).should.be.false();
checkList("abc","1.2.3",["abc@>=1.2.3"],["abc"]).should.be.false();
checkList("node-red-contrib-foo","1.2.3",["*"],["*contrib*"]).should.be.false();
// Allow
checkList("abc","1.2.3",["abc@1.2.3"],["*"]).should.be.true();
checkList("abc","1.2.3",["abc@<1.2.4"],["*"]).should.be.true();
checkList("abc","1.2.3",["abc"],["abc@>1.2.3"]).should.be.true();
checkList("abc","1.2.3",["abc"],["abc@<1.2.3||>1.2.3"]).should.be.true();
checkList("node-red-contrib-foo","1.2.3",["*contrib*"],["*"]).should.be.true();
})
})
});

View File

@@ -1,321 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var comms = NR_TEST_UTILS.require("@node-red/runtime/lib/api/comms");
var events = NR_TEST_UTILS.require("@node-red/util/lib/events");
describe("runtime-api/comms", function() {
describe("listens for events", function() {
var messages = [];
var clientConnection = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var eventHandlers = {};
before(function(done) {
sinon.stub(events,"removeListener").callsFake(function() {})
sinon.stub(events,"on").callsFake(function(evt,handler) { eventHandlers[evt] = handler })
comms.init({
log: {
trace: function(){}
}
})
comms.addConnection({client: clientConnection}).then(done);
})
after(function(done) {
comms.removeConnection({client: clientConnection}).then(done);
events.removeListener.restore();
events.on.restore();
})
afterEach(function() {
messages = [];
})
it('runtime events',function(){
eventHandlers.should.have.property('runtime-event');
eventHandlers['runtime-event']({
id: "my-event",
payload: "my-payload"
})
messages.should.have.length(1);
messages[0].should.have.property("topic","notification/my-event");
messages[0].should.have.property("data","my-payload")
})
it('status events',function(){
eventHandlers.should.have.property('node-status');
eventHandlers['node-status']({
id: "my-event",
status: {text:"my-status",badProperty:"should be filtered"}
})
messages.should.have.length(1);
messages[0].should.have.property("topic","status/my-event");
messages[0].should.have.property("data");
messages[0].data.should.have.property("text","my-status");
messages[0].data.should.not.have.property("badProperty");
})
it('comms events',function(){
eventHandlers.should.have.property('runtime-event');
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(1);
messages[0].should.have.property("topic","my-topic");
messages[0].should.have.property("data","my-payload")
})
});
describe("manages connections", function() {
var eventHandlers = {};
var messages = [];
var clientConnection1 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var clientConnection2 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
before(function() {
sinon.stub(events,"removeListener").callsFake(function() {})
sinon.stub(events,"on").callsFake(function(evt,handler) { eventHandlers[evt] = handler })
comms.init({
log: {
trace: function(){}
}
})
})
after(function() {
events.removeListener.restore();
events.on.restore();
})
afterEach(function(done) {
comms.removeConnection({client: clientConnection1}).then(function() {
comms.removeConnection({client: clientConnection2}).then(done);
});
messages = [];
})
it('adds new connections',function(done){
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection1}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(1);
comms.addConnection({client: clientConnection2}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(3);
done();
}).catch(done);
});
});
it('removes connections',function(done){
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection1}).then(function() {
comms.addConnection({client: clientConnection2}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(2);
comms.removeConnection({client: clientConnection1}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(3);
done();
});
}).catch(done);
});
})
})
describe("subscriptions", function() {
var messages = [];
var clientConnection = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var clientConnection2 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var eventHandlers = {};
before(function() {
sinon.stub(events,"removeListener").callsFake(function() {})
sinon.stub(events,"on").callsFake(function(evt,handler) { eventHandlers[evt] = handler })
comms.init({
log: {
trace: function(){}
}
})
})
after(function() {
events.removeListener.restore();
events.on.restore();
})
afterEach(function(done) {
messages = [];
comms.removeConnection({client: clientConnection}).then(done);
})
it('subscribe triggers retained messages',function(done){
eventHandlers['comms']({
topic: "my-event",
data: "my-payload",
retain: true
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","my-event");
messages[0].should.have.property("data","my-payload");
done();
});
}).catch(done);
})
it('retains non-blank status message',function(done){
eventHandlers['node-status']({
id: "node1234",
status: {text:"hello"}
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "status/#"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","status/node1234");
messages[0].should.have.property("data",{text:"hello", fill: undefined, shape: undefined});
done();
});
}).catch(done);
})
it('does not retain blank status message',function(done){
eventHandlers['node-status']({
id: "node1234",
status: {}
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "status/#"}).then(function() {
messages.should.have.length(0);
done();
});
}).catch(done);
})
it('does not send blank status if first status',function(done){
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "status/#"}).then(function() {
eventHandlers['node-status']({
id: "node5678",
status: {}
})
messages.should.have.length(0);
done()
})
}).catch(done);
});
it('sends blank status if replacing retained',function(done){
eventHandlers['node-status']({
id: "node5678",
status: {text:"hello"}
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "status/#"}).then(function() {
messages.should.have.length(1);
eventHandlers['node-status']({
id: "node5678",
status: {}
})
messages.should.have.length(2);
done()
})
}).catch(done);
});
it('does not retain initial status blank message',function(done){
eventHandlers['node-status']({
id: "my-event",
status: {}
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","my-event");
messages[0].should.have.property("data","my-payload");
done();
});
}).catch(done);
})
it('retained messages get cleared',function(done) {
eventHandlers['comms']({
topic: "my-event",
data: "my-payload",
retain: true
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","my-event");
messages[0].should.have.property("data","my-payload");
// Now we have a retained message, clear it
eventHandlers['comms']({
topic: "my-event",
data: "my-payload-cleared"
});
messages.should.have.length(2);
messages[1].should.have.property("topic","my-event");
messages[1].should.have.property("data","my-payload-cleared");
// Now add a second client and subscribe - no message should arrive
return comms.addConnection({client: clientConnection2}).then(function() {
return comms.subscribe({client: clientConnection2, topic: "my-event"}).then(function() {
messages.should.have.length(2);
done();
});
});
});
}).catch(done);
});
})
});

View File

@@ -1,353 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var context = NR_TEST_UTILS.require("@node-red/runtime/lib/api/context");
var mockLog = () => ({
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc";}
});
var mockContext = function(contents) {
return {
get: function(key,store,callback) {
if (contents.hasOwnProperty(store) && contents[store].hasOwnProperty(key)) {
callback(null,contents[store][key]);
} else {
callback(null,undefined);
}
},
set: function (key, value, store, callback) {
if (contents.hasOwnProperty(store)) {
if (!value) {
delete contents[store][key];
callback(null);
}
} else {
callback("err store");
}
},
keys: function (store, callback) {
if (contents.hasOwnProperty(store)) {
callback(null, Object.keys(contents[store]));
} else {
callback("err store");
}
}
};
};
describe("runtime-api/context", function() {
var globalContext, flowContext, nodeContext, contexts;
beforeEach(function() {
globalContext = { default: { abc: 111 }, file: { abc: 222 } };
flowContext = { default: { abc: 333 }, file: { abc: 444 } };
nodeContext = { default: { abc: 555 }, file: { abc: 666 } };
contexts = {
global: mockContext(globalContext),
flow1: mockContext(flowContext)
};
context.init({
nodes: {
listContextStores: function() {
return { default: 'default', stores: [ 'default', 'file' ] };
},
getContext: function(id) {
return contexts[id];
},
getNode: function(id) {
if (id === 'known') {
return {
context: function() { return mockContext(nodeContext); }
};
} else {
return null;
}
}
},
settings: {
functionGlobalContext: {
fgc:1234
}
},
log: mockLog()
});
});
describe("getValue", function() {
it('gets global value of default store', function() {
return context.getValue({
scope: 'global',
id: undefined,
store: undefined, // use default
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','111');
result.should.have.property('format','number');
});
});
it('gets global value of specified store', function() {
return context.getValue({
scope: 'global',
id: undefined,
store: 'file',
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','222');
result.should.have.property('format','number');
});
});
it('gets flow value of default store', function() {
return context.getValue({
scope: 'flow',
id: 'flow1',
store: undefined, // use default
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','333');
result.should.have.property('format','number');
});
});
it('gets flow value of specified store', function() {
return context.getValue({
scope: 'flow',
id: 'flow1',
store: 'file',
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','444');
result.should.have.property('format','number');
});
});
it('gets node value of default store', function() {
return context.getValue({
scope: 'node',
id: 'known',
store: undefined, // use default
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','555');
result.should.have.property('format','number');
});
});
it('gets node value of specified store', function() {
return context.getValue({
scope: 'node',
id: 'known',
store: 'file',
key: 'abc'
}).then(function(result) {
result.should.have.property('msg','666');
result.should.have.property('format','number');
});
});
it('404s for unknown store', function(done) {
context.getValue({
scope: 'global',
id: undefined,
store: 'unknown',
key: 'abc'
}).then(function(result) {
done("getValue for unknown store should not resolve");
}).catch(function(err) {
err.should.have.property('code','not_found');
err.should.have.property('status',404);
done();
});
});
it('gets all global value properties', function() {
return context.getValue({
scope: 'global',
id: undefined,
store: undefined, // use default
key: undefined, //
}).then(function(result) {
result.should.eql({
default: { abc: { msg: '111', format: 'number' } },
file: { abc: { msg: '222', format: 'number' } }
});
});
});
it('gets all flow value properties', function() {
return context.getValue({
scope: 'flow',
id: 'flow1',
store: undefined, // use default
key: undefined, //
}).then(function(result) {
result.should.eql({
default: { abc: { msg: '333', format: 'number' } },
file: { abc: { msg: '444', format: 'number' } }
});
});
});
it('gets all node value properties', function() {
return context.getValue({
scope: 'node',
id: 'known',
store: undefined, // use default
key: undefined, //
}).then(function(result) {
result.should.eql({
default: { abc: { msg: '555', format: 'number' } },
file: { abc: { msg: '666', format: 'number' } }
});
});
});
it('gets empty object when specified context doesn\'t exist', function() {
return context.getValue({
scope: 'node',
id: 'non-existent',
store: 'file',
key: 'abc'
}).then(function(result) {
result.should.be.an.Object();
result.should.be.empty();
});
});
});
describe("delete", function () {
it('deletes global value of default store', function () {
return context.delete({
scope: 'global',
id: undefined,
store: undefined, // use default
key: 'abc'
}).then(function () {
globalContext.should.eql({
default: {}, file: { abc: 222 }
});
});
});
it('deletes global value of specified store', function () {
return context.delete({
scope: 'global',
id: undefined,
store: 'file',
key: 'abc'
}).then(function () {
globalContext.should.eql({
default: { abc: 111 }, file: {}
});
});
});
it('deletes flow value of default store', function () {
return context.delete({
scope: 'flow',
id: 'flow1',
store: undefined, // use default
key: 'abc'
}).then(function () {
flowContext.should.eql({
default: {}, file: { abc: 444 }
});
});
});
it('deletes flow value of specified store', function () {
return context.delete({
scope: 'flow',
id: 'flow1',
store: 'file',
key: 'abc'
}).then(function () {
flowContext.should.eql({
default: { abc: 333 }, file: {}
});
});
});
it('deletes node value of default store', function () {
return context.delete({
scope: 'node',
id: 'known',
store: undefined, // use default
key: 'abc'
}).then(function () {
nodeContext.should.eql({
default: {}, file: { abc: 666 }
});
});
});
it('deletes node value of specified store', function () {
return context.delete({
scope: 'node',
id: 'known',
store: 'file',
key: 'abc'
}).then(function () {
nodeContext.should.eql({
default: { abc: 555 }, file: {}
});
});
});
it('does nothing when specified context doesn\'t exist', function() {
return context.delete({
scope: 'node',
id: 'non-existent',
store: 'file',
key: 'abc'
}).then(function(result) {
should.not.exist(result);
nodeContext.should.eql({
default: { abc: 555 }, file: { abc: 666 }
});
});
});
it('404s for unknown store', function (done) {
context.delete({
scope: 'global',
id: undefined,
store: 'unknown',
key: 'abc'
}).then(function () {
done("delete for unknown store should not resolve");
}).catch(function (err) {
err.should.have.property('code', 'not_found');
err.should.have.property('status', 404);
done();
});
});
});
});

View File

@@ -1,430 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/api/flows")
var mockLog = () => ({
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
})
describe("runtime-api/flows", function() {
describe("getFlows", function() {
it("returns the current flow configuration", function(done) {
flows.init({
log: mockLog(),
flows: {
getFlows: function() { return [1,2,3] }
}
});
flows.getFlows({}).then(function(result) {
result.should.eql([1,2,3]);
done();
}).catch(done);
});
});
describe("setFlows", function() {
var setFlows;
var loadFlows;
var reloadError = false;
beforeEach(function() {
setFlows = sinon.spy(function(flows,credentials,type) {
if (flows[0] === "error") {
var err = new Error("error");
err.code = "error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
return Promise.resolve("newRev");
});
loadFlows = sinon.spy(function() {
if (!reloadError) {
return Promise.resolve("newLoadRev");
} else {
var err = new Error("error");
err.code = "error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
})
flows.init({
log: mockLog(),
flows: {
getFlows: function() { return {rev:"currentRev",flows:[]} },
setFlows: setFlows,
loadFlows: loadFlows
}
})
})
it("defaults to full deploy", function(done) {
flows.setFlows({
flows: {flows:[4,5,6]}
}).then(function(result) {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
setFlows.lastCall.args[2].should.eql("full");
done();
}).catch(done);
});
it("includes credentials when part of the request", function(done) {
flows.setFlows({
flows: {flows:[4,5,6], credentials: {$:"creds"}},
}).then(function(result) {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
setFlows.lastCall.args[1].should.eql({$:"creds"});
setFlows.lastCall.args[2].should.eql("full");
done();
}).catch(done);
});
it("passes through other deploy types", function(done) {
flows.setFlows({
deploymentType: "nodes",
flows: {flows:[4,5,6]}
}).then(function(result) {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
setFlows.lastCall.args[2].should.eql("nodes");
done();
}).catch(done);
});
it("triggers a flow reload", function(done) {
flows.setFlows({
deploymentType: "reload"
}).then(function(result) {
result.should.eql({rev:"newLoadRev"});
setFlows.called.should.be.false();
loadFlows.called.should.be.true();
done();
}).catch(done);
});
it("allows update when revision matches", function(done) {
flows.setFlows({
deploymentType: "nodes",
flows: {flows:[4,5,6],rev:"currentRev"}
}).then(function(result) {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
setFlows.lastCall.args[2].should.eql("nodes");
done();
}).catch(done);
});
it("rejects update when revision does not match", function(done) {
flows.setFlows({
deploymentType: "nodes",
flows: {flows:[4,5,6],rev:"notTheCurrentRev"}
}).then(function(result) {
done(new Error("Did not reject rev mismatch"));
}).catch(function(err) {
err.should.have.property('code','version_mismatch');
err.should.have.property('status',409);
done();
}).catch(done);
});
it("rejects when reload fails",function(done) {
reloadError = true;
flows.setFlows({
deploymentType: "reload"
}).then(function(result) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','error');
done();
}).catch(done);
});
it("rejects when update fails",function(done) {
flows.setFlows({
deploymentType: "full",
flows: {flows:["error",5,6]}
}).then(function(result) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','error');
done();
}).catch(done);
});
});
describe("addFlow", function() {
var addFlow;
beforeEach(function() {
addFlow = sinon.spy(function(flow) {
if (flow === "error") {
var err = new Error("error");
err.code = "error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
return Promise.resolve("newId");
});
flows.init({
log: mockLog(),
flows: {
addFlow: addFlow
}
});
})
it("adds a flow", function(done) {
flows.addFlow({flow:{a:"123"}}).then(function(id) {
addFlow.called.should.be.true();
addFlow.lastCall.args[0].should.eql({a:"123"});
id.should.eql("newId");
done()
}).catch(done);
});
it("rejects when add fails", function(done) {
flows.addFlow({flow:"error"}).then(function(id) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','error');
done();
}).catch(done);
});
});
describe("getFlow", function() {
var getFlow;
beforeEach(function() {
getFlow = sinon.spy(function(flow) {
if (flow === "unknown") {
return null;
}
return [1,2,3];
});
flows.init({
log: mockLog(),
flows: {
getFlow: getFlow
}
});
})
it("gets a flow", function(done) {
flows.getFlow({id:"123"}).then(function(flow) {
flow.should.eql([1,2,3]);
done()
}).catch(done);
});
it("rejects when flow not found", function(done) {
flows.getFlow({id:"unknown"}).then(function(flow) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','not_found');
err.should.have.property('status',404);
done();
}).catch(done);
});
});
describe("updateFlow", function() {
var updateFlow;
beforeEach(function() {
updateFlow = sinon.spy(function(id,flow) {
if (id === "unknown") {
var err = new Error();
// TODO: quirk of internal api - uses .code for .status
err.code = 404;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (id === "error") {
var err = new Error();
// TODO: quirk of internal api - uses .code for .status
err.code = "error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
return Promise.resolve();
});
flows.init({
log: mockLog(),
flows: {
updateFlow: updateFlow
}
});
})
it("updates a flow", function(done) {
flows.updateFlow({id:"123",flow:[1,2,3]}).then(function(id) {
id.should.eql("123");
updateFlow.called.should.be.true();
updateFlow.lastCall.args[0].should.eql("123");
updateFlow.lastCall.args[1].should.eql([1,2,3]);
done()
}).catch(done);
});
it("rejects when flow not found", function(done) {
flows.updateFlow({id:"unknown"}).then(function(flow) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','not_found');
err.should.have.property('status',404);
done();
}).catch(done);
});
it("rejects when update fails", function(done) {
flows.updateFlow({id:"error"}).then(function(flow) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','error');
err.should.have.property('status',400);
done();
}).catch(done);
});
});
describe("deleteFlow", function() {
var removeFlow;
beforeEach(function() {
removeFlow = sinon.spy(function(flow) {
if (flow === "unknown") {
var err = new Error();
// TODO: quirk of internal api - uses .code for .status
err.code = 404;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (flow === "error") {
var err = new Error();
// TODO: quirk of internal api - uses .code for .status
err.code = "error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
return Promise.resolve();
});
flows.init({
log: mockLog(),
flows: {
removeFlow: removeFlow
}
});
})
it("deletes a flow", function(done) {
flows.deleteFlow({id:"123"}).then(function() {
removeFlow.called.should.be.true();
removeFlow.lastCall.args[0].should.eql("123");
done()
}).catch(done);
});
it("rejects when flow not found", function(done) {
flows.deleteFlow({id:"unknown"}).then(function(flow) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','not_found');
err.should.have.property('status',404);
done();
}).catch(done);
});
it("rejects when delete fails", function(done) {
flows.deleteFlow({id:"error"}).then(function(flow) {
done(new Error("Did not return internal error"));
}).catch(function(err) {
err.should.have.property('code','error');
err.should.have.property('status',400);
done();
}).catch(done);
});
});
describe("getNodeCredentials", function() {
beforeEach(function() {
flows.init({
log: mockLog(),
nodes: {
getCredentials: function(id) {
if (id === "unknown") {
return undefined;
} else if (id === "known") {
return {
username: "abc",
password: "123"
}
} else if (id === "known2") {
return {
username: "abc",
password: ""
}
} else {
return {};
}
},
getCredentialDefinition: function(type) {
if (type === "node") {
return {
username: {type:"text"},
password: {type:"password"}
}
} else {
return null;
}
}
}
});
})
it("returns an empty object for an unknown node", function(done) {
flows.getNodeCredentials({id:"unknown", type:"node"}).then(function(result) {
result.should.eql({});
done();
}).catch(done);
});
it("gets the filtered credentials for a known node with password", function(done) {
flows.getNodeCredentials({id:"known", type:"node"}).then(function(result) {
result.should.eql({
username: "abc",
has_password: true
});
done();
}).catch(done);
});
it("gets the filtered credentials for a known node without password", function(done) {
flows.getNodeCredentials({id:"known2", type:"node"}).then(function(result) {
result.should.eql({
username: "abc",
has_password: false
});
done();
}).catch(done);
});
it("gets the empty credentials for a known node without a registered definition", function(done) {
flows.getNodeCredentials({id:"known2", type:"unknown-type"}).then(function(result) {
result.should.eql({});
done();
}).catch(done);
});
});
});

View File

@@ -1,55 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var index = NR_TEST_UTILS.require("@node-red/runtime/lib/api/index");
describe("runtime-api/index", function() {
before(function() {
["comms","flows","nodes","settings","library","projects"].forEach(n => {
sinon.stub(NR_TEST_UTILS.require(`@node-red/runtime/lib/api/${n}`),"init").callsFake(()=>{});
})
});
after(function() {
["comms","flows","nodes","settings","library","projects"].forEach(n => {
NR_TEST_UTILS.require(`@node-red/runtime/lib/api/${n}`).init.restore()
})
})
it('isStarted', function(done) {
index.init({
isStarted: ()=>true
});
index.isStarted({}).then(function(started) {
started.should.be.true();
done();
}).catch(done);
})
it('isStarted', function(done) {
index.init({
version: ()=>"1.2.3.4"
});
index.version({}).then(function(version) {
version.should.eql("1.2.3.4");
done();
}).catch(done);
})
});

View File

@@ -1,167 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var library = NR_TEST_UTILS.require("@node-red/runtime/lib/api/library")
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
}
describe("runtime-api/library", function() {
describe("getEntry", function() {
before(function() {
library.init({
log: mockLog,
library: {
getEntry: function(library, type,path) {
if (type === "known") {
return Promise.resolve("known");
} else if (type === "forbidden") {
var err = new Error("forbidden");
err.code = "forbidden";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (type === "not_found") {
var err = new Error("forbidden");
err.code = "not_found";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (type === "error") {
var err = new Error("error");
err.code = "unknown_error";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (type === "blank") {
return Promise.reject();
}
}
}
})
})
it("returns a known entry", function(done) {
library.getEntry({library: "local",type: "known", path: "/abc"}).then(function(result) {
result.should.eql("known")
done();
}).catch(done)
})
it("rejects a forbidden entry", function(done) {
library.getEntry({library: "local",type: "forbidden", path: "/abc"}).then(function(result) {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("code","forbidden");
err.should.have.property("status",403);
done();
}).catch(done)
})
it("rejects an unknown entry", function(done) {
library.getEntry({library: "local",type: "not_found", path: "/abc"}).then(function(result) {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("code","not_found");
err.should.have.property("status",404);
done();
}).catch(done)
})
it("rejects a blank (unknown) entry", function(done) {
library.getEntry({library: "local",type: "blank", path: "/abc"}).then(function(result) {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("code","not_found");
err.should.have.property("status",404);
done();
}).catch(done)
})
it("rejects unexpected error", function(done) {
library.getEntry({library: "local",type: "error", path: "/abc"}).then(function(result) {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("status",400);
done();
}).catch(done)
})
})
describe("saveEntry", function() {
var opts;
before(function() {
library.init({
log: mockLog,
library: {
saveEntry: function(library,type,path,meta,body) {
opts = {type,path,meta,body};
if (type === "known") {
return Promise.resolve();
} else if (type === "forbidden") {
var err = new Error("forbidden");
err.code = "forbidden";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
} else if (type === "not_found") {
var err = new Error("forbidden");
err.code = "not_found";
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
})
})
it("saves an entry", function(done) {
library.saveEntry({library: "local",type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
opts.should.have.property("type","known");
opts.should.have.property("path","/abc");
opts.should.have.property("meta",{a:1});
opts.should.have.property("body","123");
done();
}).catch(done)
})
it("rejects a forbidden entry", function(done) {
library.saveEntry({library: "local",type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("code","forbidden");
err.should.have.property("status",403);
done();
}).catch(done)
})
it("rejects an unknown entry", function(done) {
library.saveEntry({library: "local",type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
done(new Error("did not reject"));
}).catch(function(err) {
err.should.have.property("status",400);
done();
}).catch(done)
})
})
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +0,0 @@
const should = require("should");
const sinon = require("sinon");
const NR_TEST_UTILS = require("nr-test-utils");
const plugins = NR_TEST_UTILS.require("@node-red/runtime/lib/api/plugins")
const mockLog = () => ({
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
})
describe("runtime-api/plugins", function() {
const pluginList = [{id:"one",module:'test-module'},{id:"two",module:"node-red"}];
const pluginConfigs = "123";
describe("getPluginList", function() {
it("gets the plugin list", function() {
plugins.init({
log: mockLog(),
plugins: {
getPluginList: function() { return pluginList}
}
});
return plugins.getPluginList({}).then(function(result) {
result.should.eql(pluginList);
})
});
});
describe("getPluginConfigs", function() {
it("gets the plugin configs", function() {
plugins.init({
log: mockLog(),
plugins: {
getPluginConfigs: function() { return pluginConfigs}
}
});
return plugins.getPluginConfigs({}).then(function(result) {
result.should.eql(pluginConfigs);
})
});
});
describe("getPluginCatalogs", function() {
it("gets the plugin catalogs", function() {
plugins.init({
log: mockLog(),
plugins: {
getPluginList: function() { return pluginList}
},
i18n: {
i: {
changeLanguage: function(lang,done) { done && done() },
getResourceBundle: function(lang, id) { return {lang,id}}
}
}
});
return plugins.getPluginCatalogs({lang: "en-US"}).then(function(result) {
JSON.stringify(result).should.eql(JSON.stringify({ one: { lang: "en-US", id: "one" } }))
})
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,988 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var clone = require("clone");
var NR_TEST_UTILS = require("nr-test-utils");
var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/api/settings")
var mockLog = () => ({
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
})
describe("runtime-api/settings", function() {
describe("getRuntimeSettings", function() {
it("gets the runtime settings", function() {
settings.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => ["lib1"] },
storage: {}
})
return settings.getRuntimeSettings({}).then(result => {
result.should.have.property("httpNodeRoot","testHttpNodeRoot");
result.should.have.property("version","testVersion");
result.should.have.property("paletteCategories",["red","blue","green"]);
result.should.have.property("libraries",["lib1"]);
result.should.have.property("testNodeSetting","helloWorld");
result.should.have.property("testPluginSettings","helloPluginWorld");
result.should.not.have.property("foo",123);
result.should.have.property("flowEncryptionType","test-key-type");
result.should.not.have.property("user");
result.should.have.property("externalModules");
result.externalModules.should.eql({palette:{allowInstall:false, allowUpload: false}});
})
});
it("gets the filtered user settings", function() {
settings.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => { ["lib1"]} },
storage: {}
})
return settings.getRuntimeSettings({
user: {
username: "nick",
anonymous: false,
image: "http://example.com",
permissions: "*",
private: "secret"
}
}).then(result => {
result.should.have.property("user");
result.user.should.have.property("username","nick");
result.user.should.have.property("permissions","*");
result.user.should.have.property("image","http://example.com");
result.user.should.have.property("anonymous",false);
result.user.should.not.have.property("private");
})
});
it("gets the filtered settings when editor disabled ", function() {
settings.init({
settings: {
disableEditor: true,
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => { ["lib1"]} },
storage: {
projects: {
getActiveProject: () => 'test-active-project',
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
})
return settings.getRuntimeSettings({
user: {
username: "nick",
anonymous: false,
image: "http://example.com",
permissions: "*",
private: "secret"
}
}).then(result => {
result.should.have.property("user");
result.user.should.have.property("username","nick");
result.user.should.have.property("permissions","*");
result.user.should.have.property("image","http://example.com");
result.user.should.have.property("anonymous",false);
result.user.should.not.have.property("private");
// Filtered out when disableEditor is true
result.should.not.have.property("paletteCategories",["red","blue","green"]);
result.should.not.have.property("testNodeSetting","helloWorld");
result.should.not.have.property("foo",123);
result.should.not.have.property("flowEncryptionType","test-key-type");
result.should.not.have.property("project");
result.should.not.have.property("git");
})
});
it('includes project settings if projects available', function() {
settings.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => { ["lib1"]} },
storage: {
projects: {
getActiveProject: () => 'test-active-project',
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
})
return settings.getRuntimeSettings({
user: {
username: "nick",
anonymous: false,
image: "http://example.com",
permissions: "*",
private: "secret"
}
}).then(result => {
result.should.have.property("project","test-active-project");
result.should.not.have.property("files");
result.should.have.property("git");
result.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
});
});
it('includes existing files details if projects enabled but no active project and files exist', function() {
settings.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => { ["lib1"]} },
storage: {
projects: {
flowFileExists: () => true,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
})
return settings.getRuntimeSettings({
user: {
username: "nick",
anonymous: false,
image: "http://example.com",
permissions: "*",
private: "secret"
}
}).then(result => {
result.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
result.should.not.have.property("project");
result.should.have.property("files");
result.files.should.have.property("flow",'test-flow-file');
result.files.should.have.property("credentials",'test-creds-file');
result.should.have.property("git");
result.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
});
});
it('does not include file details if projects enabled but no active project and files do not exist', function() {
settings.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: (obj) => {
obj.testNodeSetting = "helloWorld";
},
},
plugins: {
exportPluginSettings: (obj) => {
obj.testPluginSettings = "helloPluginWorld";
}
},
nodes: {
listContextStores: () => { return {stores:["file","memory"], default: "file"} },
installerEnabled: () => false,
getCredentialKeyType: () => "test-key-type"
},
library: {getLibraries: () => { ["lib1"]} },
storage: {
projects: {
flowFileExists: () => false,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
})
return settings.getRuntimeSettings({
user: {
username: "nick",
anonymous: false,
image: "http://example.com",
permissions: "*",
private: "secret"
}
}).then(result => {
result.should.not.have.property("project");
result.should.not.have.property("files");
result.should.have.property("git");
result.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
});
});
});
describe("getUserSettings", function() {
before(function() {
settings.init({
settings: {
getUserSettings: username => username
}
});
})
it("returns default user settings", function() {
return settings.getUserSettings({}).then(result => {
result.should.eql("_");
})
})
it("returns default user settings for anonymous", function() {
return settings.getUserSettings({user:{anonymous:true}}).then(result => {
result.should.eql("_");
})
})
it("returns user settings", function() {
return settings.getUserSettings({user:{username:'nick'}}).then(result => {
result.should.eql("nick");
})
})
});
describe("updateUserSettings", function() {
var userSettings;
before(function() {
settings.init({
settings: {
getUserSettings: username => clone(userSettings[username]),
setUserSettings: (username, settings) => {
if (username === 'error') {
var p = Promise.reject(new Error("unknown user"));
p.catch(()=>{});
return p;
} else if (username === 'throw') {
throw new Error("thrown error");
}
userSettings[username] = clone(settings);
return Promise.resolve();
}
},
log: mockLog()
});
})
beforeEach(function() {
userSettings = {
"_": { abc: 123 },
"nick": {abc: 456}
}
})
it('sets default user settings', function() {
return settings.updateUserSettings({settings:{abc:789}}).then(function() {
userSettings._.abc.should.eql(789)
})
})
it('merges user settings', function() {
return settings.updateUserSettings({settings:{def:789}}).then(function() {
userSettings._.abc.should.eql(123)
userSettings._.def.should.eql(789)
})
})
it('sets default user settings for anonymous user', function() {
return settings.updateUserSettings({user:{anonymous:true},settings:{def:789}}).then(function() {
userSettings._.abc.should.eql(123)
userSettings._.def.should.eql(789)
})
})
it('sets named user settings', function() {
return settings.updateUserSettings({user:{username:'nick'},settings:{def:789}}).then(function() {
userSettings.nick.abc.should.eql(456)
userSettings.nick.def.should.eql(789)
})
})
it('rejects with suitable error', function(done) {
settings.updateUserSettings({user:{username:'error'},settings:{def:789}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
it('rejects with suitable error - thrown', function(done) {
settings.updateUserSettings({user:{username:'throw'},settings:{def:789}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
});
describe("getUserKeys", function() {
before(function() {
settings.init({
storage: {
projects: {
ssh: {
listSSHKeys: username => {
if (username === 'error') {
var p = Promise.reject(new Error("unknown user"));
p.catch(()=>{});
return p;
}
return Promise.resolve([username])
}
}
}
}
})
})
it('returns the default users keys', function() {
return settings.getUserKeys({}).then(result => {
result.should.eql(['__default']);
})
})
it('returns the default users keys for anonymous', function() {
return settings.getUserKeys({user:{anonymous:true}}).then(result => {
result.should.eql(['__default']);
})
})
it('returns the users keys', function() {
return settings.getUserKeys({user:{username:'nick'}}).then(result => {
result.should.eql(['nick']);
})
})
it('rejects with suitable error', function(done) {
settings.getUserKeys({user:{username:'error'}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
});
describe("getUserKey", function() {
before(function() {
settings.init({
storage: {
projects: {
ssh: {
getSSHKey: (username, id) => {
if (username === 'error') {
var p = Promise.reject(new Error("unknown user"));
p.catch(()=>{});
return p;
} else if (username === '404') {
return Promise.resolve(null);
}
return Promise.resolve({username,id})
}
}
}
}
})
})
it('returns the default user key', function() {
return settings.getUserKey({id:'keyid'}).then(result => {
result.should.eql({id:'keyid',username:"__default"});
})
})
it('returns the default user key - anonymous', function() {
return settings.getUserKey({user:{anonymous:true},id:'keyid'}).then(result => {
result.should.eql({id:'keyid',username:"__default"});
})
})
it('returns the user key', function() {
return settings.getUserKey({user:{username:'nick'},id:'keyid'}).then(result => {
result.should.eql({id:'keyid',username:"nick"});
})
})
it('404s for unknown key', function(done) {
settings.getUserKey({user:{username:'404'},id:'keyid'}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 404);
err.should.have.property('code', 'not_found');
done();
}).catch(done);
})
it('rejects with suitable error', function(done) {
settings.getUserKey({user:{username:'error'}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
});
describe("generateUserKey", function() {
before(function() {
settings.init({
storage: {
projects: {
ssh: {
generateSSHKey: (username, opts) => {
if (username === 'error') {
var p = Promise.reject(new Error("unknown user"));
p.catch(()=>{});
return p;
}
return Promise.resolve(JSON.stringify({username,opts}))
}
}
}
}
})
})
it('generates for the default user', function() {
return settings.generateUserKey({id:'keyid'}).then(result => {
var data = JSON.parse(result);
data.should.eql({opts:{id:'keyid'},username:"__default"});
})
})
it('generates for the default user - anonymous', function() {
return settings.generateUserKey({user:{anonymous:true},id:'keyid'}).then(result => {
var data = JSON.parse(result);
data.should.eql({opts:{user:{anonymous:true},id:'keyid'},username:"__default"});
})
})
it('generates for the user', function() {
return settings.generateUserKey({user:{username:'nick'},id:'keyid'}).then(result => {
var data = JSON.parse(result);
data.should.eql({opts:{user:{username:'nick'},id:'keyid'},username:"nick"});
})
})
it('rejects with suitable error', function(done) {
settings.generateUserKey({user:{username:'error'}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
});
describe("removeUserKey", function() {
var received = {};
before(function() {
settings.init({
storage: {
projects: {
ssh: {
deleteSSHKey: (username, id) => {
if (username === 'error') {
var p = Promise.reject(new Error("unknown user"));
p.catch(()=>{});
return p;
}
received.username = username;
received.id = id;
return Promise.resolve();
}
}
}
}
})
});
beforeEach(function() {
received.username = "";
received.id = "";
})
it('removes for the default user', function() {
return settings.removeUserKey({id:'keyid'}).then(() => {
received.username.should.eql("__default");
received.id.should.eql("keyid");
})
})
it('removes for the default user key - anonymous', function() {
return settings.removeUserKey({user:{anonymous:true},id:'keyid'}).then(() => {
received.username.should.eql("__default");
received.id.should.eql("keyid");
})
})
it('returns the user key', function() {
return settings.removeUserKey({user:{username:'nick'},id:'keyid'}).then(() => {
received.username.should.eql("nick");
received.id.should.eql("keyid");
})
})
it('rejects with suitable error', function(done) {
settings.removeUserKey({user:{username:'error'}}).then(result => {
done("Unexpected resolve for error case");
}).catch(err => {
err.should.have.property('status', 400);
done();
}).catch(done);
})
});
});
/*
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var editorApi = require("../../../../red/api/editor");
var comms = require("../../../../red/api/editor/comms");
var info = require("../../../../red/api/editor/settings");
var auth = require("../../../../red/api/auth");
var sshkeys = require("../../../../red/api/editor/sshkeys");
var bodyParser = require("body-parser");
var fs = require("fs-extra");
var fspath = require("path");
describe("api/editor/sshkeys", function() {
var app;
var mockList = [
'library','theme','locales','credentials','comms'
]
var isStarted = true;
var errors = [];
var session_data = {};
var mockRuntime = {
settings:{
httpNodeRoot: true,
httpAdminRoot: true,
disableEditor: false,
exportNodeSettings:function(){},
storage: {
getSessions: function(){
return Promise.resolve(session_data);
},
setSessions: function(_session) {
session_data = _session;
return Promise.resolve();
}
}
},
log:{audit:function(){},error:function(msg){errors.push(msg)},trace:function(){}},
storage: {
projects: {
ssh: {
init: function(){},
listSSHKeys: function(){},
getSSHKey: function(){},
generateSSHKey: function(){},
deleteSSHKey: function(){}
}
}
},
events:{on:function(){},removeListener:function(){}},
isStarted: function() { return isStarted; },
nodes: {installerEnabled: function() { return false }}
};
before(function() {
auth.init(mockRuntime);
app = express();
app.use(bodyParser.json());
app.use(editorApi.init({},mockRuntime));
});
after(function() {
})
beforeEach(function() {
sinon.stub(mockRuntime.storage.projects.ssh, "listSSHKeys");
sinon.stub(mockRuntime.storage.projects.ssh, "getSSHKey");
sinon.stub(mockRuntime.storage.projects.ssh, "generateSSHKey");
sinon.stub(mockRuntime.storage.projects.ssh, "deleteSSHKey");
})
afterEach(function() {
mockRuntime.storage.projects.ssh.listSSHKeys.restore();
mockRuntime.storage.projects.ssh.getSSHKey.restore();
mockRuntime.storage.projects.ssh.generateSSHKey.restore();
mockRuntime.storage.projects.ssh.deleteSSHKey.restore();
})
it('GET /settings/user/keys --- return empty list', function(done) {
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve([]));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
res.body.keys.should.be.empty();
done();
});
});
it('GET /settings/user/keys --- return normal list', function(done) {
var fileList = [
'test_key01',
'test_key02'
];
var retList = fileList.map(function(elem) {
return {
name: elem
};
});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
for (var item of retList) {
res.body.keys.should.containEql(item);
}
done();
});
});
it('GET /settings/user/keys --- return Error', function(done) {
var errInstance = new Error("Messages here.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(null));
request(app)
.get("/settings/user/keys/NOT_REAL")
.expect(404)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
var key_file_name = "test_key";
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(fileContent));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
mockRuntime.storage.projects.ssh.getSSHKey.called.should.be.true();
res.body.should.be.deepEqual({ publickey: fileContent });
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('POST /settings/user/keys --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('POST /settings/user/keys --- return parameter error', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("You need to have body or body.name");
done();
});
});
it('POST /settings/user/keys --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('POST /settings/user/keys --- return Unexpected error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.be.deepEqual({});
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
});
*/

File diff suppressed because it is too large Load Diff

View File

@@ -1,885 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require('sinon');
var clone = require('clone');
var util = require("util");
var NR_TEST_UTILS = require("nr-test-utils");
var Subflow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Subflow");
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
describe('Subflow', function() {
var getType;
var stoppedNodes = {};
var currentNodes = {};
var rewiredNodes = {};
var createCount = 0;
beforeEach(function() {
currentNodes = {};
stoppedNodes = {};
rewiredNodes = {};
createCount = 0;
var runtime = {
settings:{},
log:{
log: sinon.stub(), // function() { console.log("l",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//
debug: sinon.stub(), // function() { console.log("d",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
trace: sinon.stub(), // function() { console.log("t",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
warn: sinon.stub(), // function() { console.log("w",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
info: sinon.stub(), // function() { console.log("i",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
metric: sinon.stub(),
_: function() { return "abc"}
}
}
Flow.init(runtime);
Subflow.init(runtime);
});
var TestNode = function(n) {
Node.call(this,n);
this._index = createCount++;
this.scope = n.scope;
var node = this;
this.foo = n.foo;
this.handled = 0;
this.stopped = false;
this.received = null;
currentNodes[node.id] = node;
this.on('input',function(msg) {
// console.log(this.id,msg.payload);
node.handled++;
node.received = msg.payload;
node.send(msg);
});
this.on('close',function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
});
this.__updateWires = this.updateWires;
this.updateWires = function(newWires) {
rewiredNodes[node.id] = node;
node.newWires = newWires;
node.__updateWires(newWires);
};
}
util.inherits(TestNode,Node);
var TestErrorNode = function(n) {
Node.call(this,n);
this._index = createCount++;
this.scope = n.scope;
this.name = n.name;
var node = this;
this.foo = n.foo;
this.handled = 0;
this.stopped = false;
currentNodes[node.id] = node;
this.on('input',function(msg) {
node.handled++;
node.error("test error",msg);
});
this.on('close',function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
});
this.__updateWires = this.updateWires;
this.updateWires = function(newWires) {
rewiredNodes[node.id] = node;
node.newWires = newWires;
node.__updateWires(newWires);
};
}
util.inherits(TestErrorNode,Node);
var TestStatusNode = function(n) {
Node.call(this,n);
this._index = createCount++;
this.scope = n.scope;
this.name = n.name;
var node = this;
this.foo = n.foo;
this.handled = 0;
this.stopped = false;
currentNodes[node.id] = node;
this.on('input',function(msg) {
node.handled++;
node.status({text:"test status"});
});
this.on('close',function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
});
this.__updateWires = this.updateWires;
this.updateWires = function(newWires) {
rewiredNodes[node.id] = node;
node.newWires = newWires;
node.__updateWires(newWires);
};
}
util.inherits(TestStatusNode,Node);
var TestAsyncNode = function(n) {
Node.call(this,n);
var node = this;
this.scope = n.scope;
this.foo = n.foo;
this.handled = 0;
this.messages = [];
this.stopped = false;
this.closeDelay = n.closeDelay || 50;
currentNodes[node.id] = node;
this.on('input',function(msg) {
node.handled++;
node.messages.push(msg);
node.send(msg);
});
this.on('close',function(done) {
setTimeout(function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
done();
},node.closeDelay);
});
}
util.inherits(TestAsyncNode,Node);
var TestEnvNode = function(n) {
Node.call(this,n);
this._index = createCount++;
this.scope = n.scope;
this.foo = n.foo;
var node = this;
this.stopped = false;
this.received = null;
currentNodes[node.id] = node;
this.on('input',function(msg) {
var flow = node._flow;
var val = flow.getSetting("__KEY__");
node.received = val;
node.send({payload: val});
});
this.on('close',function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
});
this.__updateWires = this.updateWires;
this.updateWires = function(newWires) {
rewiredNodes[node.id] = node;
node.newWires = newWires;
node.__updateWires(newWires);
};
}
util.inherits(TestEnvNode,Node);
before(function() {
getType = sinon.stub(typeRegistry,"get").callsFake(function(type) {
if (type=="test") {
return TestNode;
} else if (type=="testError"){
return TestErrorNode;
} else if (type=="testStatus"){
return TestStatusNode;
} else if (type=="testEnv"){
return TestEnvNode;
} else {
return TestAsyncNode;
}
});
});
after(function() {
getType.restore();
});
describe('#start',function() {
it("instantiates a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]},{"wires":[{"id":"sf1","port":0}]}]},
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
{id:"sf1-2",type:"test","z":"sf1",foo:"sf1-cn",x:166,y:99,"wires":[[]]},
{id:"sf1-cn",type:"test","z":"sf1"}
]);
var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(4);
// var sfInstanceId = Object.keys(activeNodes)[5];
// var sfInstanceId2 = Object.keys(activeNodes)[6];
var sfConfigId = Object.keys(activeNodes)[4];
flow.getNode('1').should.have.a.property('id','1');
flow.getNode('2').should.have.a.property('id','2');
flow.getNode('3').should.have.a.property('id','3');
flow.getNode('4').should.have.a.property('id','4');
// flow.getNode(sfInstanceId).should.have.a.property('id',sfInstanceId);
// flow.getNode(sfInstanceId2).should.have.a.property('id',sfInstanceId2);
// flow.getNode(sfConfigId).should.have.a.property('id',sfConfigId);
// flow.getNode(sfInstanceId2).should.have.a.property('foo',sfConfigId);
Object.keys(currentNodes).should.have.length(6);
currentNodes.should.have.a.property("1");
currentNodes.should.not.have.a.property("2");
currentNodes.should.have.a.property("3");
currentNodes.should.have.a.property("4");
// currentNodes.should.have.a.property(sfInstanceId);
// currentNodes.should.have.a.property(sfInstanceId2);
// currentNodes.should.have.a.property(sfConfigId);
currentNodes["1"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0);
currentNodes["4"].should.have.a.property("handled",0);
// currentNodes[sfInstanceId].should.have.a.property("handled",0);
// currentNodes[sfInstanceId2].should.have.a.property("handled",0);
currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
flow.stop().then(function() {
Object.keys(currentNodes).should.have.length(0);
Object.keys(stoppedNodes).should.have.length(6);
// currentNodes.should.not.have.a.property("1");
// currentNodes.should.not.have.a.property("3");
// currentNodes.should.not.have.a.property("4");
// // currentNodes.should.not.have.a.property(sfInstanceId);
// // currentNodes.should.not.have.a.property(sfInstanceId2);
// // currentNodes.should.not.have.a.property(sfConfigId);
// stoppedNodes.should.have.a.property("1");
// stoppedNodes.should.have.a.property("3");
// stoppedNodes.should.have.a.property("4");
// // stoppedNodes.should.have.a.property(sfInstanceId);
// // stoppedNodes.should.have.a.property(sfInstanceId2);
// // stoppedNodes.should.have.a.property(sfConfigId);
done();
});
},150);
});
it("instantiates a subflow inside a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 1","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
{id:"sf2",type:"subflow","name":"Subflow 2","info":"",
"in":[{wires:[]}],"out":[{"wires":[{"id":"sf2","port":0}]}]},
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
{id:"sf1-2",type:"subflow:sf2","z":"sf1",x:166,y:99,"wires":[[]]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
currentNodes["1"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0);
currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
flow.stop().then(function() {
Object.keys(currentNodes).should.have.length(0);
done();
});
},150);
});
it("rewires a subflow node on update/start",function(done){
var rawConfig = [
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
{id:"sf1-1",type:"test1","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
{id:"sf1-2",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}
];
var config = flowUtils.parseConfig(clone(rawConfig));
rawConfig[2].wires = [["4"]];
var newConfig = flowUtils.parseConfig(rawConfig);
var diff = flowUtils.diffConfigs(config,newConfig);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(4);
// var sfInstanceId = Object.keys(activeNodes)[4];
// var sfInstanceId2 = Object.keys(activeNodes)[5];
currentNodes["1"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0);
currentNodes["4"].should.have.a.property("handled",0);
currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",0);
flow.update(newConfig,newConfig.flows["t1"]);
flow.start(diff)
currentNodes["1"].receive({payload:"test2"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",2);
// currentNodes[sfInstanceId].should.have.a.property("handled",2);
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
flow.stop().then(function() {
done();
});
},150);
},150);
});
});
describe('#stop', function() {
it("stops subflow instance nodes",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[[]]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(3);
Object.keys(stoppedNodes).should.have.length(0);
flow.stop(["2"]).then(function() {
Object.keys(currentNodes).should.have.length(2);
Object.keys(stoppedNodes).should.have.length(1);
done();
}).catch(done);
});
});
describe("#handleStatus",function() {
it("passes a status event to the subflow's parent tab status node - all scope",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
{id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() {
done();
});
},150);
});
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
{id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",scope:["2"],wires:[]}
]);
var parentFlowStatusCalled = false;
var flow = Flow.create({handleStatus:() => { parentFlowStatusCalled = true} },config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
parentFlowStatusCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() {
done();
});
},150);
});
});
describe("status node", function() {
it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{
id:"sf1",
type:"subflow",
name:"Subflow 2",
info:"",
in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-1",port:0}]}],
status:{wires:[{id:"sf1-1", port:0}]}
},
{id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test-payload"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test-payload");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() {
done();
});
},150);
});
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{
id:"sf1",
type:"subflow",
name:"Subflow 2",
info:"",
in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-1",port:0}]}],
status:{wires:[{id:"sf1-1", port:0}]}
},
{id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:{text:"payload-obj"}});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","payload-obj");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() {
done();
});
},150);
});
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{
id:"sf1",
type:"subflow",
name:"Subflow 2",
info:"",
in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-1",port:0}]}],
status:{wires:[{id:"sf1-1", port:0}]}
},
{id:"sf1-1",type:"test",name:"test","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({status:{text:"status-obj"}});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","status-obj");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() {
done();
});
},150);
});
it("does not emit a regular status event if it contains a subflow-status node", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{
id:"sf1",
type:"subflow",
name:"Subflow 2",
info:"",
in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-1",port:0}]}],
status:{wires:[]}
},
{id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test-payload"});
currentNodes["sn"].should.have.a.property("handled",0);
flow.stop().then(function() {
done();
});
});
})
describe("#handleError",function() {
it("passes an error event to the subflow's parent tab catch node - all scope",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
{id:"sf1-1",name:"test-error-node",type:"testError","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","test error");
statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() {
done();
});
},150);
});
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
{id:"sf1-1",name:"test-error-node",type:"testError","z":"sf1",x:166,y:99,"wires":[[]]},
{id:"sn",x:10,y:10,z:"t1",type:"catch",scope:["2"],wires:[]}
]);
var parentFlowErrorCalled = false;
var flow = Flow.create({handleError:() => { parentFlowErrorCalled = true} },config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
parentFlowErrorCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","test error");
statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() {
done();
});
},150);
});
});
describe("#env var", function() {
// should be changed according to internal env var representation
function setEnv(node, key, val) {
var flow = node._flow;
if (flow) {
var env = flow.env;
if (!env) {
env = flow.env = {};
}
env[key] = {
name: key,
type: "str",
value: val
};
}
}
it("can access process env var", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",
"in":[ {wires:[{id:"sf1-1"}]} ],
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
{id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1-cn",x:166,y:99,wires:[[]]}
]);
var flow = Flow.create({
getSetting: k=> process.env[k],
handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]);
flow.start();
process.env["__KEY__"] = "__VAL__";
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL__");
flow.stop().then(function() {
done();
});
},150);
});
it("can access subflow env var", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",
"in":[ {wires:[{id:"sf1-1"}]} ],
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
{id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]}
]);
var flow = Flow.create({
getSetting: k=> process.env[k],
handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]);
flow.start();
var testenv_node = null;
for (var n in currentNodes) {
var node = currentNodes[n];
if (node.type === "testEnv") {
testenv_node = node;
break;
}
}
process.env["__KEY__"] = "__VAL0__";
setEnv(testenv_node, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__");
flow.stop().then(function() {
done();
});
},150);
});
it("can access nested subflow env var", function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
{id:"sf1",type:"subflow",name:"Subflow 1",info:"",
in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-2",port:0}]}]},
{id:"sf2",type:"subflow",name:"Subflow 2",info:"",
in:[{wires:[{id:"sf2-1"}]}],
out:[{wires:[{id:"sf2-2",port:0}]}]},
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
{id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]]},
{id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]},
{id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]},
]);
var flow = Flow.create({
getSetting: k=> process.env[k],
handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]);
flow.start();
var node_sf1_1 = null;
var node_sf2_1 = null;
var testenv_node = null;
for (var n in currentNodes) {
var node = currentNodes[n];
if (node.foo === "sf1.1") {
node_sf1_1 = node;
}
if (node.foo === "sf2.1") {
node_sf2_1 = node;
}
}
process.env["__KEY__"] = "__VAL0__";
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL0__");
setEnv(node_sf1_1, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__");
setEnv(node_sf2_1, "__KEY__", "__VAL2__");
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL2__");
flow.stop().then(function() {
done();
});
},150);
},150);
},150);
});
});
});

View File

@@ -1,669 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var clone = require("clone");
var NR_TEST_UTILS = require("nr-test-utils");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
var RedNode = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
var RED = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes");
var events = NR_TEST_UTILS.require("@node-red/util/lib/events");
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry")
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
describe('flows/index', function() {
var storage;
var eventsOn;
var credentialsClean;
var credentialsLoad;
var credentialsAdd;
var flowCreate;
var getType;
var checkFlowDependencies;
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
_: function() { return "abc"}
}
before(function() {
getType = sinon.stub(typeRegistry,"get").callsFake(function(type) {
return type.indexOf('missing') === -1;
});
checkFlowDependencies = sinon.stub(typeRegistry, "checkFlowDependencies").callsFake(async function(flow) {
if (flow[0].id === "node-with-missing-modules") {
throw new Error("Missing module");
}
});
});
after(function() {
getType.restore();
checkFlowDependencies.restore();
});
beforeEach(function() {
eventsOn = sinon.spy(events,"on");
credentialsClean = sinon.stub(credentials,"clean").callsFake(function(conf) {
conf.forEach(function(n) {
delete n.credentials;
});
return Promise.resolve();
});
credentialsLoad = sinon.stub(credentials,"load").callsFake(function(creds) {
if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") {
return Promise.reject("creds error");
}
return Promise.resolve();
});
credentialsAdd = sinon.stub(credentials,"add").callsFake(async function(id, conf){})
flowCreate = sinon.stub(Flow,"create").callsFake(function(parent, global, flow) {
var id;
if (typeof flow === 'undefined') {
flow = global;
id = '_GLOBAL_';
} else {
id = flow.id;
}
flowCreate.flows[id] = {
flow: flow,
global: global,
start: sinon.spy(),
update: sinon.spy(),
stop: sinon.spy(),
getActiveNodes: function() {
return flow.nodes||{};
},
handleError: sinon.spy(),
handleStatus: sinon.spy()
}
return flowCreate.flows[id];
});
flowCreate.flows = {};
storage = {
saveFlows: function(conf) {
storage.conf = conf;
return Promise.resolve();
}
}
});
afterEach(function(done) {
eventsOn.restore();
credentialsClean.restore();
credentialsLoad.restore();
credentialsAdd.restore();
flowCreate.restore();
flows.stopFlows().then(done);
});
// describe('#init',function() {
// it('registers the type-registered handler', function() {
// flows.init({},{});
// eventsOn.calledOnce.should.be.true();
// });
// });
describe('#setFlows', function() {
it('sets the full flow', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
credentialsClean.called.should.be.true();
storage.hasOwnProperty('conf').should.be.true();
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
it('loads the full flow for type load', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var loadStorage = {
saveFlows: function(conf) {
loadStorage.conf = conf;
return Promise.resolve(456);
},
getFlows: function() {
return Promise.resolve({flows:originalConfig,rev:123})
}
}
flows.init({log:mockLog, settings:{},storage:loadStorage});
flows.setFlows(originalConfig,"load").then(function() {
credentialsClean.called.should.be.false();
// 'load' type does not trigger a save
loadStorage.hasOwnProperty('conf').should.be.false();
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
it('extracts credentials from the full flow', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[],credentials:{"a":1}},
{id:"t1",type:"tab"}
];
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
credentialsClean.called.should.be.true();
storage.hasOwnProperty('conf').should.be.true();
var cleanedFlows = flows.getFlows();
storage.conf.flows.should.eql(cleanedFlows.flows);
cleanedFlows.flows.should.not.eql(originalConfig);
cleanedFlows.flows[0].credentials = {"a":1};
cleanedFlows.flows.should.eql(originalConfig);
done();
});
});
it('sets the full flow including credentials', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var credentials = {"t1-1":{"a":1}};
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig,credentials).then(function() {
credentialsClean.called.should.be.true();
credentialsAdd.called.should.be.true();
credentialsAdd.lastCall.args[0].should.eql("t1-1");
credentialsAdd.lastCall.args[1].should.eql({"a":1});
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
it('updates existing flows with partial deployment - nodes', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var newConfig = clone(originalConfig);
newConfig.push({id:"t1-2",x:10,y:10,z:"t1",type:"test",wires:[]});
newConfig.push({id:"t2",type:"tab"});
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
events.once('flows:started',function() {
flows.setFlows(newConfig,"nodes").then(function() {
flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t1'].update.called.should.be.true();
flowCreate.flows['t2'].start.called.should.be.true();
flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
done();
})
});
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
});
it('updates existing flows with partial deployment - flows', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var newConfig = clone(originalConfig);
newConfig.push({id:"t1-2",x:10,y:10,z:"t1",type:"test",wires:[]});
newConfig.push({id:"t2",type:"tab"});
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
events.once('flows:started',function() {
flows.setFlows(newConfig,"nodes").then(function() {
flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t1'].update.called.should.be.true();
flowCreate.flows['t2'].start.called.should.be.true();
flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
flows.stopFlows().then(done);
})
});
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
});
it('returns error if it cannot decrypt credentials', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var credentials = {"$":"fail"};
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig,credentials).then(function() {
done("Unexpected success when credentials couldn't be decrypted")
}).catch(function(err) {
done();
});
});
});
describe('#load', function() {
it('loads the flow config', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
credentialsLoad.called.should.be.true();
// 'load' type does not trigger a save
storage.hasOwnProperty('conf').should.be.false();
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
});
describe('#startFlows', function() {
it('starts the loaded config', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
events.once('flows:started',function() {
Object.keys(flowCreate.flows).should.eql(['_GLOBAL_','t1']);
done();
});
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
return flows.startFlows();
});
});
it('does not start if nodes missing', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
return flows.startFlows();
}).then(() => {
try {
flowCreate.called.should.be.false();
done();
} catch(err) {
done(err);
}
});
});
it('starts when missing nodes registered', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
{id:"t1-3",x:10,y:10,z:"t1",type:"missing2",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
return flows.startFlows();
}).then(() => {
flowCreate.called.should.be.false();
events.emit("type-registered","missing");
setTimeout(function() {
flowCreate.called.should.be.false();
events.emit("type-registered","missing2");
setTimeout(function() {
flowCreate.called.should.be.true();
done();
},10);
},10);
});
});
it('does not start if external modules missing', function(done) {
var originalConfig = [
{id:"node-with-missing-modules",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
var receivedEvent = null;
var handleEvent = function(payload) {
receivedEvent = payload;
}
events.on("runtime-event",handleEvent);
//{id:"runtime-state",payload:{error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true});"
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(flows.startFlows).then(() => {
events.removeListener("runtime-event",handleEvent);
try {
flowCreate.called.should.be.false();
receivedEvent.should.have.property('id','runtime-state');
receivedEvent.should.have.property('payload',
{ error: 'missing-modules',
type: 'warning',
text: 'notification.warnings.missing-modules',
modules: [] }
);
done();
}catch(err) {
done(err)
}
});
});
});
describe.skip('#get',function() {
});
describe('#eachNode', function() {
it('iterates the flow nodes', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
var c = 0;
flows.eachNode(function(node) {
c++
})
c.should.equal(2);
done();
});
});
});
describe('#stopFlows', function() {
});
// describe('#handleError', function() {
// it('passes error to correct flow', function(done) {
// var originalConfig = [
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
// {id:"t1",type:"tab"}
// ];
// storage.getFlows = function() {
// return Promise.resolve({flows:originalConfig});
// }
//
// events.once('flows:started',function() {
// flows.handleError(originalConfig[0],"message",{});
// flowCreate.flows['t1'].handleError.called.should.be.true();
// done();
// });
//
// flows.init({log:mockLog, settings:{},storage:storage});
// flows.load().then(function() {
// flows.startFlows();
// });
// });
// it('passes error to flows that use the originating global config', function(done) {
// var originalConfig = [
// {id:"configNode",type:"test"},
// {id:"t1",type:"tab"},
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
// {id:"t2",type:"tab"},
// {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
// {id:"t3",type:"tab"},
// {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
// ];
// storage.getFlows = function() {
// return Promise.resolve({flows:originalConfig});
// }
//
// events.once('flows:started',function() {
// flows.handleError(originalConfig[0],"message",{});
// try {
// flowCreate.flows['t1'].handleError.called.should.be.true();
// flowCreate.flows['t2'].handleError.called.should.be.false();
// flowCreate.flows['t3'].handleError.called.should.be.true();
// done();
// } catch(err) {
// done(err);
// }
// });
//
// flows.init({log:mockLog, settings:{},storage:storage});
// flows.load().then(function() {
// flows.startFlows();
// });
// });
// });
// describe('#handleStatus', function() {
// it('passes status to correct flow', function(done) {
// var originalConfig = [
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
// {id:"t1",type:"tab"}
// ];
// storage.getFlows = function() {
// return Promise.resolve({flows:originalConfig});
// }
//
// events.once('flows:started',function() {
// flows.handleStatus(originalConfig[0],"message");
// flowCreate.flows['t1'].handleStatus.called.should.be.true();
// done();
// });
//
// flows.init({log:mockLog, settings:{},storage:storage});
// flows.load().then(function() {
// flows.startFlows();
// });
// });
//
// it('passes status to flows that use the originating global config', function(done) {
// var originalConfig = [
// {id:"configNode",type:"test"},
// {id:"t1",type:"tab"},
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
// {id:"t2",type:"tab"},
// {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
// {id:"t3",type:"tab"},
// {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
// ];
// storage.getFlows = function() {
// return Promise.resolve({flows:originalConfig});
// }
//
// events.once('flows:started',function() {
// flows.handleStatus(originalConfig[0],"message");
// try {
// flowCreate.flows['t1'].handleStatus.called.should.be.true();
// flowCreate.flows['t2'].handleStatus.called.should.be.false();
// flowCreate.flows['t3'].handleStatus.called.should.be.true();
// done();
// } catch(err) {
// done(err);
// }
// });
//
// flows.init({log:mockLog, settings:{},storage:storage});
// flows.load().then(function() {
// flows.startFlows();
// });
// });
// });
describe('#checkTypeInUse', function() {
before(function() {
sinon.stub(typeRegistry,"getNodeInfo").callsFake(function(id) {
if (id === 'unused-module') {
return {types:['one','two','three']}
} else {
return {types:['one','test','three']}
}
});
});
after(function() {
typeRegistry.getNodeInfo.restore();
});
it('returns cleanly if type not is use', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
flows.checkTypeInUse("unused-module");
done();
});
});
it('throws error if type is in use', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({log:mockLog, settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
/*jshint immed: false */
try {
flows.checkTypeInUse("used-module");
done("type_in_use error not thrown");
} catch(err) {
err.code.should.eql("type_in_use");
done();
}
});
});
});
describe('#addFlow', function() {
it("rejects duplicate node id",function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
flows.addFlow({
label:'new flow',
nodes:[
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}
]
}).then(function() {
done(new Error('failed to reject duplicate node id'));
}).catch(function(err) {
done();
})
});
});
it("addFlow",function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return Promise.resolve({flows:originalConfig});
}
storage.setFlows = function() {
return Promise.resolve();
}
flows.init({log:mockLog, settings:{},storage:storage});
flows.load().then(function() {
return flows.startFlows();
}).then(function() {
flows.addFlow({
label:'new flow',
nodes:[
{id:"t2-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t2-2",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t2-3",z:"t1",type:"test"}
]
}).then(function(id) {
flows.getFlows().flows.should.have.lengthOf(6);
var createdFlows = Object.keys(flowCreate.flows);
createdFlows.should.have.lengthOf(3);
createdFlows[2].should.eql(id);
done();
}).catch(function(err) {
done(err);
})
});
});
})
describe('#updateFlow', function() {
it.skip("updateFlow");
})
describe('#removeFlow', function() {
it.skip("removeFlow");
})
describe('#disableFlow', function() {
it.skip("disableFlow");
})
describe('#enableFlow', function() {
it.skip("enableFlow");
})
});

View File

@@ -1,801 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var clone = require("clone");
var NR_TEST_UTILS = require("nr-test-utils");
var flowUtil = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
var redUtil = NR_TEST_UTILS.require("@node-red/util").util;
describe('flows/util', function() {
var getType;
before(function() {
getType = sinon.stub(typeRegistry,"get").callsFake(function(type) {
return type!=='missing';
});
});
after(function() {
getType.restore();
});
describe('#mapEnvVarProperties',function() {
before(function() {
process.env.foo1 = "bar1";
process.env.foo2 = "bar2";
process.env.foo3 = "bar3";
})
after(function() {
delete process.env.foo1;
delete process.env.foo2;
delete process.env.foo3;
})
it('handles ENV substitutions in an object - $()', function() {
var foo = {a:"$(foo1)",b:"$(foo2)",c:{d:"$(foo3)"}};
for (var p in foo) {
if (foo.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(foo,p,{getSetting: p => process.env[p]});
}
}
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
});
it('handles ENV substitutions in an object - ${}', function() {
var foo = {a:"${foo1}",b:"${foo2}",c:{d:"${foo3}"}};
for (var p in foo) {
if (foo.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(foo,p,{getSetting: p => process.env[p]});
}
}
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
});
it('gets ENV from parent flow', function() {
var foo = {a:"$(unknown)",b:"$(foo2)",c:{d:"$(foo3)"}};
for (var p in foo) {
if (foo.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(foo,p,{
getSetting: name => name[0]==='f'?name.toUpperCase():undefined
});
}
}
foo.should.eql({ a: '$(unknown)', b: 'FOO2', c: { d: 'FOO3' } } );
});
});
describe('#getEnvVar',function() {
before(function() {
process.env.foo1 = "bar1";
})
after(function() {
delete process.env.foo1;
})
it('returns a known env var', function() {
flowUtil.init({settings:{}});
flowUtil.getEnvVar("foo1").should.equal("bar1")
})
it('returns undefined for an unknown env var', function() {
flowUtil.init({settings:{}});
(flowUtil.getEnvVar("foo2") === undefined).should.be.true()
})
it('returns undefined for an excluded env var', function() {
flowUtil.init({settings:{envVarExcludes:['foo1']}});
(flowUtil.getEnvVar("foo1") === undefined).should.be.true()
})
});
describe('#diffNodes',function() {
it('handles a null old node', function() {
flowUtil.diffNodes(null,{}).should.be.true();
});
it('ignores x/y changes', function() {
flowUtil.diffNodes({x:10,y:10},{x:20,y:10}).should.be.false();
flowUtil.diffNodes({x:10,y:10},{x:10,y:20}).should.be.false();
});
it('ignores wiring changes', function() {
flowUtil.diffNodes({wires:[]},{wires:[1,2,3]}).should.be.false();
});
it('spots existing property change - string', function() {
flowUtil.diffNodes({a:"foo"},{a:"bar"}).should.be.true();
});
it('spots existing property change - number', function() {
flowUtil.diffNodes({a:0},{a:1}).should.be.true();
});
it('spots existing property change - boolean', function() {
flowUtil.diffNodes({a:true},{a:false}).should.be.true();
});
it('spots existing property change - truthy', function() {
flowUtil.diffNodes({a:true},{a:1}).should.be.true();
});
it('spots existing property change - falsey', function() {
flowUtil.diffNodes({a:false},{a:0}).should.be.true();
});
it('spots existing property change - array', function() {
flowUtil.diffNodes({a:[0,1,2]},{a:[0,2,3]}).should.be.true();
});
it('spots existing property change - object', function() {
flowUtil.diffNodes({a:{a:[0,1,2]}},{a:{a:[0,2,3]}}).should.be.true();
flowUtil.diffNodes({a:{a:[0,1,2]}},{a:{b:[0,1,2]}}).should.be.true();
});
it('spots added property', function() {
flowUtil.diffNodes({a:"foo"},{a:"foo",b:"bar"}).should.be.true();
});
it('spots removed property', function() {
flowUtil.diffNodes({a:"foo",b:"bar"},{a:"foo"}).should.be.true();
});
});
describe('#parseConfig',function() {
it('parses a single-tab flow', function() {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
it('parses a single-tab flow with global config node', function() {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",foo:"cn", wires:[]},
{id:"cn",type:"test"},
{id:"t1",type:"tab"}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
it('parses a multi-tab flow', function() {
var originalConfig = [
{id:"t1",type:"tab"},
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t2",type:"tab"},
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
it('parses a subflow flow', function() {
var originalConfig = [
{id:"t1",type:"tab"},
{id:"t1-1",x:10,y:10,z:"t1",type:"subflow:sf1",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
it('parses a flow with a missing type', function() {
var originalConfig = [
{id:"t1",type:"tab"},
{id:"t1-1",x:10,y:10,z:"t1",type:"sf1",wires:[]},
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
parsedConfig.missingTypes.should.eql(['missing']);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]};
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true();
});
it('parses a flow with a missing flow', function() {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",foo:"cn", wires:[]},
{id:"cn",type:"test"},
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
it('parses a flow including a group', function() {
var originalConfig = [
{id:"t1",type:"tab"},
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"g1",type:"group",z:"t1"}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]}
parsedConfig.should.eql(expectedConfig);
});
});
describe('#diffConfigs', function() {
it('handles an identical configuration', function() {
var config = [{id:"123",type:"test",foo:"a",wires:[]}];
var originalConfig = flowUtil.parseConfig(clone(config));
var changedConfig = flowUtil.parseConfig(clone(config));
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.should.have.length(0);
});
it('identifies nodes with changed properties, including downstream linked', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[0].foo = "b";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.eql(["1"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.should.eql(["2"]);
});
it('identifies nodes with changed properties, including upstream linked', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].bar = "c";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.eql(["2"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.should.eql(["1"]);
});
it('identifies nodes with changed credentials, including downstream linked', function() {
var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[0].credentials = {};
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.eql(["1"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.should.eql(["2"]);
});
it('identifies nodes with changed wiring', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires[0][0] = "3";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.eql(["2"]);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('identifies nodes with changed wiring - second connection added', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires[0].push("1");
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.eql(["2"]);
diffResult.linked.sort().should.eql(["1"]);
});
it('identifies nodes with changed wiring - node connected', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires.push("3");
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.eql(["2"]);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('identifies new nodes', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig.push({id:"2",type:"test",bar:"b",wires:[["1"]]});
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.eql(["2"]);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1"]);
});
it('identifies deleted nodes', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig.splice(1,1);
newConfig[0].wires = [];
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.eql(["2"]);
diffResult.rewired.should.eql(["1"]);
diffResult.linked.sort().should.eql(["3"]);
});
it('identifies config nodes changes, node->config', function() {
var config = [
{id:"1",type:"test",foo:"configNode",wires:[["2"]]},
{id:"2",type:"test",bar:"b",wires:[["3"]]},
{id:"3",type:"test",foo:"a",wires:[]},
{id:"configNode",type:"testConfig"}
];
var newConfig = clone(config);
newConfig[3].foo = "bar";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(["1","configNode"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["2","3"]);
});
it('identifies config nodes changes, node->config->config', function() {
var config = [
{id:"1",type:"test",foo:"configNode1",wires:[["2"]]},
{id:"2",type:"test",bar:"b",wires:[["3"]]},
{id:"3",type:"test",foo:"a",wires:[]},
{id:"configNode1",foo:"configNode2",type:"testConfig"},
{id:"configNode2",type:"testConfig"}
];
var newConfig = clone(config);
newConfig[4].foo = "bar";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(["1","configNode1","configNode2"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["2","3"]);
});
it('marks a parent subflow as changed for an internal property change', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",z:"sf1",type:"test",foo:"a",wires:[]},
{id:"4",type:"subflow:sf1",wires:[]}
];
var newConfig = clone(config);
newConfig[4].foo = "b";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', '4', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal wiring change', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[4].wires = [["sf1-2"]];
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal node add', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig.push({id:"sf1-3",z:"sf1",type:"test",wires:[]});
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal node delete', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig.splice(5,1);
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(1);
diffResult.removed.sort().should.eql(['sf1-2']);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal subflow wiring change - input removed', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[3].in[0].wires = [];
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal subflow wiring change - input added', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[3].in[0].wires.push({"id":"sf1-2"});
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal subflow wiring change - output added', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[3].out[0].wires.push({"id":"sf1-2","port":0});
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal subflow wiring change - output removed', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[3].out[0].wires = [];
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for a global config node change', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf1-1",z:"sf1",prop:"configNode",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"test",wires:[]},
{id:"configNode",a:"foo",type:"test",wires:[]}
];
var newConfig = clone(config);
newConfig[6].a = "bar";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', "configNode", 'sf1']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('marks a parent subflow as changed for an internal subflow instance change', function() {
var config = [
{id:"1",type:"test",wires:[["2"]]},
{id:"2",type:"subflow:sf1",wires:[["3"]]},
{id:"3",type:"test",wires:[]},
{id:"sf1",type:"subflow"},
{id:"sf2",type:"subflow"},
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
{id:"sf1-2",z:"sf1",type:"subflow:sf2",wires:[]},
{id:"sf2-1",z:"sf2",type:"test",wires:[]},
{id:"sf2-2",z:"sf2",type:"test",wires:[]},
];
var newConfig = clone(config);
newConfig[8].a = "bar";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(['2', 'sf1', 'sf2']);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["1","3"]);
});
it('ignores tab changes that are immaterial', function() {
var config = [{id:"1",type:"tab",label:"fred"},{id:"2",type:"test",bar:"b",wires:[["1"]],z:"1"}];
var newConfig = clone(config);
newConfig[0].label = "barney";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
});
it('marks a deleted tab as removed', function() {
var config = [{id:"f1",type:"tab",label:"fred"},{id:"n1",type:"test",bar:"b",wires:[["1"]],z:"f1"},
{id:"f2",type:"tab",label:"fred"},{id:"n2",type:"test",bar:"b",wires:[["1"]],z:"f2"}];
var newConfig = clone(config);
newConfig = newConfig.slice(0,2);
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.sort().should.eql(['f2', 'n2']);
diffResult.rewired.should.have.length(0);
});
it('marks all nodes as added when tab state changes disabled to enabled', function() {
var config = [{id:"1",type:"tab",disabled:true,label:"fred"},{id:"2",type:"test",bar:"b",wires:[["1"]],z:"1"},{id:"3",type:"test"}];
var newConfig = clone(config);
newConfig[0].disabled = false;
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(2);
diffResult.added.sort().should.eql(["1","2"]);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
});
it('marks all nodes as removed when tab state changes enabled to disabled', function() {
var config = [{id:"1",type:"tab",disabled:false,label:"fred"},{id:"2",type:"test",bar:"b",wires:[["1"]],z:"1"},{id:"3",type:"test"}];
var newConfig = clone(config);
newConfig[0].disabled = true;
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(0);
diffResult.removed.should.have.length(2);
diffResult.removed.sort().should.eql(["1","2"]);
diffResult.rewired.should.have.length(0);
});
it('marks a node as removed when its state changes enabled to disabled', function() {
var config = [{id:"1",type:"tab",disabled:false,label:"fred"},{id:"2",type:"test",bar:"b",wires:[["1"]],z:"1"},{id:"3",type:"test"}];
var newConfig = clone(config);
newConfig[1].d = true;
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.should.have.length(2);
diffResult.changed.sort().should.eql(["1","2"]);
diffResult.removed.should.have.length(1);
diffResult.removed.sort().should.eql(["2"]);
diffResult.rewired.should.have.length(0);
});
});
});

View File

@@ -1,250 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var api = NR_TEST_UTILS.require("@node-red/runtime/lib/api");
var runtime = NR_TEST_UTILS.require("@node-red/runtime");
var redNodes = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes");
var storage = NR_TEST_UTILS.require("@node-red/runtime/lib/storage");
var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/settings");
var util = NR_TEST_UTILS.require("@node-red/util");
var log = NR_TEST_UTILS.require("@node-red/util").log;
var i18n = NR_TEST_UTILS.require("@node-red/util").i18n;
describe("runtime", function() {
afterEach(function() {
if (console.log.restore) {
console.log.restore();
}
})
before(function() {
process.env.NODE_RED_HOME = NR_TEST_UTILS.resolve("node-red");
});
after(function() {
delete process.env.NODE_RED_HOME;
});
function mockUtil(metrics) {
sinon.stub(log,"log").callsFake(function(){})
sinon.stub(log,"warn").callsFake(function(){})
sinon.stub(log,"info").callsFake(function(){})
sinon.stub(log,"trace").callsFake(function(){})
sinon.stub(log,"metric").callsFake(function(){ return !!metrics })
sinon.stub(log,"_").callsFake(function(){ return "abc"})
sinon.stub(i18n,"registerMessageCatalog").callsFake(function(){ return Promise.resolve()})
}
function unmockUtil() {
log.log.restore && log.log.restore();
log.warn.restore && log.warn.restore();
log.info.restore && log.info.restore();
log.trace.restore && log.trace.restore();
log.metric.restore && log.metric.restore();
log._.restore && log._.restore();
i18n.registerMessageCatalog.restore && i18n.registerMessageCatalog.restore();
}
describe("init", function() {
beforeEach(function() {
sinon.stub(log,"init").callsFake(function() {});
sinon.stub(settings,"init").callsFake(function() {});
sinon.stub(redNodes,"init").callsFake(function() {})
mockUtil();
});
afterEach(function() {
log.init.restore();
settings.init.restore();
redNodes.init.restore();
unmockUtil();
})
it("initialises components", function() {
runtime.init({testSettings: true, httpAdminRoot:"/"});
settings.init.called.should.be.true();
redNodes.init.called.should.be.true();
});
it("returns version", function() {
runtime.init({testSettings: true, httpAdminRoot:"/"});
return runtime.version().then(version => {
/^\d+\.\d+\.\d+(-.*)?$/.test(version).should.be.true();
});
})
});
describe("start",function() {
var storageInit;
var settingsLoad;
var redNodesInit;
var redNodesLoad;
var redNodesCleanModuleList;
var redNodesGetNodeList;
var redNodesLoadFlows;
var redNodesStartFlows;
var redNodesLoadContextsPlugin;
beforeEach(function() {
storageInit = sinon.stub(storage,"init").callsFake(function(settings) {return Promise.resolve();});
redNodesInit = sinon.stub(redNodes,"init").callsFake(function() {});
redNodesLoad = sinon.stub(redNodes,"load").callsFake(function() {return Promise.resolve()});
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList").callsFake(function(){});
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows").callsFake(function() {return Promise.resolve()});
redNodesStartFlows = sinon.stub(redNodes,"startFlows").callsFake(function() {});
redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin").callsFake(function() {return Promise.resolve()});
mockUtil();
});
afterEach(function() {
storageInit.restore();
redNodesInit.restore();
redNodesLoad.restore();
redNodesGetNodeList.restore();
redNodesCleanModuleList.restore();
redNodesLoadFlows.restore();
redNodesStartFlows.restore();
redNodesLoadContextsPlugin.restore();
unmockUtil();
});
it("reports errored/missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList").callsFake(function(cb) {
return [
{ err:"errored",name:"errName" }, // error
{ module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing
].filter(cb);
});
runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return Promise.resolve();}});
// sinon.stub(console,"log");
runtime.start().then(function() {
// console.log.restore();
try {
storageInit.calledOnce.should.be.true();
redNodesInit.calledOnce.should.be.true();
redNodesLoad.calledOnce.should.be.true();
redNodesLoadFlows.calledOnce.should.be.true();
log.warn.calledWithMatch("Failed to register 1 node type");
log.warn.calledWithMatch("Missing node modules");
log.warn.calledWithMatch(" - module: typeA, typeB");
redNodesCleanModuleList.calledOnce.should.be.true();
done();
} catch(err) {
done(err);
}
}).catch(err=>{done(err)});
});
it("initiates load of missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList").callsFake(function(cb) {
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
].filter(cb);
});
var serverInstallModule = sinon.stub(redNodes,"installModule").callsFake(function(name) { return Promise.resolve({nodes:[]});});
runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}});
sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
try {
log.warn.calledWithMatch("Failed to register 2 node types");
log.warn.calledWithMatch("Missing node modules");
log.warn.calledWithMatch(" - module: typeA, typeB");
log.warn.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();
}
}).catch(err=>{done(err)});
});
it("reports errored modules when verbose is enabled",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList").callsFake(function(cb) {
return [
{ err:"errored",name:"errName" } // error
].filter(cb);
});
runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}});
sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
try {
log.warn.neverCalledWithMatch("Failed to register 1 node type");
log.warn.calledWithMatch("[errName] errored");
done();
} catch(err) {
done(err);
}
}).catch(err=>{done(err)});
});
it("reports runtime metrics",function(done) {
var stopFlows = sinon.stub(redNodes,"stopFlows").callsFake(function() { return Promise.resolve();} );
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList").callsFake(function() {return []});
unmockUtil();
mockUtil(true);
runtime.init(
{testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}},
{},
undefined);
// sinon.stub(console,"log");
runtime.start().then(function() {
// console.log.restore();
setTimeout(function() {
try {
log.log.args.should.have.lengthOf(3);
log.log.args[0][0].should.have.property("event","runtime.memory.rss");
log.log.args[1][0].should.have.property("event","runtime.memory.heapTotal");
log.log.args[2][0].should.have.property("event","runtime.memory.heapUsed");
done();
} catch(err) {
done(err);
} finally {
runtime.stop();
stopFlows.restore();
}
},300);
}).catch(err=>{done(err)});
});
});
it("stops components", function(done) {
var stopFlows = sinon.stub(redNodes,"stopFlows").callsFake(function() { return Promise.resolve();} );
var closeContextsPlugin = sinon.stub(redNodes,"closeContextsPlugin").callsFake(function() { return Promise.resolve();} );
runtime.stop().then(function(){
stopFlows.called.should.be.true();
closeContextsPlugin.called.should.be.true();
stopFlows.restore();
closeContextsPlugin.restore();
done();
}).catch(function(err){
stopFlows.restore();
closeContextsPlugin.restore();
return done(err)
});
});
});

View File

@@ -1,138 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var NR_TEST_UTILS = require("nr-test-utils");
var examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples")
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
}
describe("runtime/library/examples", function() {
describe("getEntry", function() {
before(function() {
examplesLibrary.init({
log: mockLog,
storage: {
getLibraryEntry: function(type,path) {
return Promise.resolve({type,path});
},
getFlow: function(path) {
return Promise.resolve({path});
}
},
nodes: {
getNodeExampleFlows: function() {
return {
"test-module": {
f: ["abc"]
},
"@scope/test-module": {
f: ["abc","throw"]
}
}
},
getNodeExampleFlowPath: function(module,entryPath) {
if (module === "unknown") {
return null;
}
return "/tmp/"+module+"/"+entryPath;
}
}
});
sinon.stub(fs,"readFile").callsFake(function(path,opts,callback) {
if (path === "/tmp/test-module/abc") {
callback(null,"Example flow result");
} else if (path === "/tmp/@scope/test-module/abc") {
callback(null,"Example scope flow result");
} else if (path === "/tmp/test-module/throw") {
throw new Error("Instant error")
} else {
callback(new Error("Unexpected path:"+path))
}
})
});
after(function() {
fs.readFile.restore();
})
it ('returns a flow example entry', function(done) {
examplesLibrary.getEntry("flows","test-module/abc").then(function(result) {
result.should.eql("Example flow result");
done();
}).catch(done);
});
it ('returns a flow example listing - top level', function(done) {
examplesLibrary.getEntry("flows","").then(function(result) {
result.should.eql([ 'test-module', '@scope/test-module' ])
done();
}).catch(done);
});
it ('returns a flow example listing - in module', function(done) {
examplesLibrary.getEntry("flows","test-module").then(function(result) {
result.should.eql([{ fn: 'abc' }])
done();
}).catch(done);
});
it ('returns a flow example listing - in scoped module', function(done) {
examplesLibrary.getEntry("flows","@scope/test-module").then(function(result) {
result.should.eql([{ fn: 'abc' }, {fn: 'throw'}])
done();
}).catch(done);
});
it ('returns a flow example entry from scoped module', function(done) {
examplesLibrary.getEntry("flows","@scope/test-module/abc").then(function(result) {
result.should.eql("Example scope flow result");
done();
}).catch(done);
});
it ('returns an error for unknown flow example entry', function(done) {
examplesLibrary.getEntry("flows","unknown/abc").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
err.should.have.property("code","not_found");
done();
});
});
it ('returns an error for file load error - async', function(done) {
examplesLibrary.getEntry("flows","test-module/unknown").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
done();
});
});
it ('returns an error for file load error - sync', function(done) {
examplesLibrary.getEntry("flows","test-module/throw").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
done();
});
});
});
});

View File

@@ -1,199 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
const should = require("should");
const sinon = require("sinon");
const NR_TEST_UTILS = require("nr-test-utils");
const library = NR_TEST_UTILS.require("@node-red/runtime/lib/library/index")
const localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local")
const examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples")
const events = NR_TEST_UTILS.require("@node-red/util/lib/events")
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
}
describe("runtime/library", function() {
before(function() {
sinon.stub(localLibrary,"getEntry").callsFake(function(type,path) {
return Promise.resolve({
library: "local",
type:type,
path:path
})
});
sinon.stub(localLibrary,"saveEntry").callsFake(function(type, path, meta, body) {
return Promise.resolve({
library: "local",
type:type,
path:path,
meta:meta,
body:body
})
});
sinon.stub(examplesLibrary,"getEntry").callsFake(function(type,path) {
return Promise.resolve({
library: "_examples_",
type:type,
path:path
})
});
});
after(function() {
localLibrary.getEntry.restore();
localLibrary.saveEntry.restore();
examplesLibrary.getEntry.restore();
})
describe("register", function() {
// it("throws error for duplicate type", function() {
// library.init({});
// library.register("unknown","/abc");
// should(()=>{library.register("unknown","/abc")} ).throw();
// })
})
describe("getLibraries", function() {
before(function() {
library.init({});
});
it('returns the default and examples libraries', function() {
const libs = library.getLibraries();
libs.should.have.length(2);
libs[0].should.have.property('id', 'local');
libs[0].should.have.property('label','editor:library.types.local');
libs[0].should.have.property("user", false);
libs[0].should.have.property('icon', 'font-awesome/fa-hdd-o');
libs[1].should.have.property('id', 'examples');
libs[1].should.have.property('label','editor:library.types.examples');
libs[1].should.have.property("user", false);
libs[1].should.have.property('icon', 'font-awesome/fa-life-ring');
libs[1].should.have.property('readOnly', true);
libs[1].should.have.property('types', ['flows']);
});
it('returns the libraries from settings', function() {
library.init({
plugins: {
getPlugin: id => { return {
id: "test-library-plugin",
type: "node-red-library-source",
class: function() {}
}
}
},
settings: {
editorTheme: {
library: {
sources: [
{id: "test-plugin-id", type:"test-library-plugin"}
]
}
}
}
});
let libs = library.getLibraries();
libs.should.have.length(2);
events.emit("registry:plugin-added","test-library-plugin" )
libs = library.getLibraries();
libs.should.have.length(3);
libs[2].should.have.property('id', 'test-plugin-id');
libs[2].should.have.property("user", false);
});
})
describe("getEntry", function() {
before(function() {
library.init({});
});
it('throws error for unregistered type', function() {
should(()=>{library.getEntry("local","unknown","/abc")} ).throw();
});
it('throws error for unknown library', function() {
should(()=>{library.getEntry("unknown","unknown","/abc")} ).throw();
});
it('returns a registered non-flow entry', function(done) {
library.register("test-module","test-type");
library.getEntry("local","test-type","/abc").then(function(result) {
result.should.have.property("library","local")
result.should.have.property("type","test-type")
result.should.have.property("path","/abc")
done();
}).catch(done);
});
it ('returns a flow entry', function(done) {
library.getEntry("local","flows","/abc").then(function(result) {
result.should.have.property("library","local")
result.should.have.property("path","/abc")
done();
}).catch(done);
});
it ('returns a flow example entry', function(done) {
library.getEntry("examples","flows","/test-module/abc").then(function(result) {
result.should.have.property("library","_examples_")
result.should.have.property("path","/test-module/abc")
done();
}).catch(done);
});
});
describe("saveEntry", function() {
before(function() {
library.init({});
});
it('throws error for unknown library', function() {
should(()=>{library.saveEntry("unknown","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
});
it('throws error for unregistered type', function() {
should(()=>{library.saveEntry("local","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
});
it('throws error for save to readonly library', function() {
should(()=>{library.saveEntry("_examples_","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
});
it('saves a flow entry', function(done) {
library.saveEntry('local','flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("path","/abc");
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
it('saves a non-flow entry', function(done) {
library.register("test-module","test-type");
library.saveEntry('local','test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("type","test-type");
result.should.have.property("path","/abc");
result.should.have.property("meta",{id:"meta"});
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
});
});

View File

@@ -1,93 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var NR_TEST_UTILS = require("nr-test-utils");
var localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local")
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
}
describe("runtime/library/local", function() {
describe("getEntry", function() {
before(function() {
localLibrary.init({
log: mockLog,
storage: {
getLibraryEntry: function(type,path) {
return Promise.resolve({type,path});
}
}
});
});
it('returns a registered non-flow entry', function(done) {
localLibrary.getEntry("test-type","/abc").then(function(result) {
result.should.have.property("type","test-type")
result.should.have.property("path","/abc")
done();
}).catch(done);
});
it ('returns a flow entry', function(done) {
localLibrary.getEntry("flows","/abc").then(function(result) {
result.should.have.property("path","/abc")
done();
}).catch(done);
});
});
describe("saveEntry", function() {
before(function() {
localLibrary.init({
log: mockLog,
storage: {
saveLibraryEntry: function(type, path, meta, body) {
return Promise.resolve({type,path,meta,body})
}
}
});
});
it('saves a flow entry', function(done) {
localLibrary.saveEntry('flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("path","/abc");
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
it('saves a non-flow entry', function(done) {
localLibrary.saveEntry('test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("type","test-type");
result.should.have.property("path","/abc");
result.should.have.property("meta",{id:"meta"});
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
});
});

View File

@@ -1,809 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils");
var RedNode = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
var Log = NR_TEST_UTILS.require("@node-red/util").log;
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
describe('Node', function() {
describe('#constructor',function() {
it('is called with an id and a type',function() {
var n = new RedNode({id:'123',type:'abc'});
n.should.have.property('id','123');
n.should.have.property('type','abc');
n.should.not.have.property('name');
n.wires.should.be.empty();
});
it('is called with an id, a type and a name',function() {
var n = new RedNode({id:'123',type:'abc',name:'barney'});
n.should.have.property('id','123');
n.should.have.property('type','abc');
n.should.have.property('name','barney');
n.wires.should.be.empty();
});
it('is called with an id, a type and some wires',function() {
var n = new RedNode({id:'123',type:'abc',wires:['123','456']});
n.should.have.property('id','123');
n.should.have.property('type','abc');
n.should.not.have.property('name');
n.wires.should.have.length(2);
});
});
describe('#close', function() {
it('emits close event when closed',function(done) {
var n = new RedNode({id:'123',type:'abc'});
n.on('close',function() {
done();
});
n.close();
});
it('returns a promise when provided a callback with a done parameter',function(testdone) {
var n = new RedNode({id:'123',type:'abc'});
n.on('close',function(done) {
setTimeout(function() {
done();
},50);
});
var p = n.close();
should.exist(p);
p.then(function() {
testdone();
});
});
it('accepts a callback with "removed" and "done" parameters', function(testdone) {
var n = new RedNode({id:'123',type:'abc'});
var receivedRemoved;
n.on('close',function(removed,done) {
receivedRemoved = removed;
setTimeout(function() {
done();
},50);
});
var p = n.close(true);
should.exist(p);
(receivedRemoved).should.be.true();
p.then(function() {
testdone();
});
})
it('allows multiple close handlers to be registered',function(testdone) {
var n = new RedNode({id:'123',type:'abc'});
var callbacksClosed = 0;
n.on('close',function(done) {
setTimeout(function() {
callbacksClosed++;
done();
},50);
});
n.on('close',function(done) {
setTimeout(function() {
callbacksClosed++;
done();
},75);
});
n.on('close',function() {
callbacksClosed++;
});
var p = n.close();
should.exist(p);
p.then(function() {
callbacksClosed.should.eql(3);
testdone();
}).catch(function(e) {
testdone(e);
});
});
});
describe('#receive', function() {
it('emits input event when called', function(done) {
var n = new RedNode({id:'123',type:'abc'});
var message = {payload:"hello world"};
n.on('input',function(msg) {
should.deepEqual(msg,message);
done();
});
n.receive(message);
});
it('writes metric info with undefined msg', function(done){
var n = new RedNode({id:'123',type:'abc'});
n.on('input',function(msg) {
(typeof msg).should.not.be.equal("undefined");
(typeof msg._msgid).should.not.be.equal("undefined");
done();
});
n.receive();
});
it('writes metric info with null msg', function(done){
var n = new RedNode({id:'123',type:'abc'});
n.on('input',function(msg) {
(typeof msg).should.not.be.equal("undefined");
(typeof msg._msgid).should.not.be.equal("undefined");
done();
});
n.receive(null);
});
it('handles thrown errors', function(done) {
var n = new RedNode({id:'123',type:'abc'});
sinon.stub(n,"error").callsFake(function(err,msg) {});
var message = {payload:"hello world"};
n.on('input',function(msg) {
throw new Error("test error");
});
n.receive(message);
setTimeout(function() {
n.error.called.should.be.true();
n.error.firstCall.args[1].should.equal(message);
done();
},50);
});
it('calls parent flow handleComplete when callback provided', function(done) {
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
try {
msg.should.deepEqual(message);
done();
} catch(err) {
done(err);
}
}
}});
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone();
});
n.receive(message);
});
it('triggers onComplete hook when done callback provided', function(done) {
var handleCompleteCalled = false;
var hookCalled = false;
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
handleCompleteCalled = true;
}
}});
var hookError;
hooks.add("onComplete",function(completeEvent) {
hookCalled = true;
try {
handleCompleteCalled.should.be.false("onComplete should be called before handleComplete")
should.not.exist(completeEvent.error);
completeEvent.msg.should.deepEqual(message);
completeEvent.node.id.should.eql("123");
completeEvent.node.node.should.equal(n);
} catch(err) {
hookError = err;
}
})
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone();
});
n.receive(message);
setTimeout(function() {
if (hookError) {
done(hookError);
return
}
try {
hookCalled.should.be.true("onComplete hook should be called");
handleCompleteCalled.should.be.true("handleComplete should be called");
done();
} catch(err) {
done(err);
}
})
});
it('triggers onComplete hook when done callback provided - with error', function(done) {
var handleCompleteCalled = false;
var hookCalled = false;
var errorReported = false;
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
handleCompleteCalled = true;
}
}});
var hookError;
hooks.add("onComplete",function(completeEvent) {
hookCalled = true;
try {
handleCompleteCalled.should.be.false("onComplete should be called before handleComplete")
should.exist(completeEvent.error);
completeEvent.error.toString().should.equal("Error: test error")
completeEvent.msg.should.deepEqual(message);
completeEvent.node.id.should.eql("123");
completeEvent.node.node.should.equal(n);
} catch(err) {
hookError = err;
}
})
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone(new Error("test error"));
});
n.error = function(err,msg) {
errorReported = true;
}
n.receive(message);
setTimeout(function() {
if (hookError) {
done(hookError);
return
}
try {
hookCalled.should.be.true("onComplete hook should be called");
handleCompleteCalled.should.be.false("handleComplete should not be called");
done();
} catch(err) {
done(err);
}
})
});
it('logs error if callback provides error', function(done) {
var n = new RedNode({id:'123',type:'abc'});
sinon.stub(n,"error").callsFake(function(err,msg) {});
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone(new Error("test error"));
setTimeout(function() {
try {
n.error.called.should.be.true();
n.error.firstCall.args[0].toString().should.equal("Error: test error");
n.error.firstCall.args[1].should.equal(message);
done();
} catch(err) {
done(err);
}
},50);
});
n.receive(message);
});
it("triggers hooks when receiving a message", function(done) {
var hookErrors = [];
var messageReceived = false;
var hooksCalled = [];
hooks.add("onReceive", function(receiveEvent) {
hooksCalled.push("onReceive")
try {
messageReceived.should.be.false("Message should not have been received before onReceive")
receiveEvent.msg.should.be.exactly(message);
receiveEvent.destination.id.should.equal("123")
receiveEvent.destination.node.should.equal(n)
} catch(err) {
hookErrors.push(err);
}
})
hooks.add("postReceive", function(receiveEvent) {
hooksCalled.push("postReceive")
try {
messageReceived.should.be.true("Message should have been received before postReceive")
receiveEvent.msg.should.be.exactly(message);
receiveEvent.destination.id.should.equal("123")
receiveEvent.destination.node.should.equal(n)
} catch(err) {
hookErrors.push(err);
}
})
var n = new RedNode({id:'123',type:'abc'});
var message = {payload:"hello world"};
n.on('input',function(msg) {
messageReceived = true;
try {
should.strictEqual(this,n);
hooksCalled.should.eql(["onReceive"])
should.deepEqual(msg,message);
} catch(err) {
hookErrors.push(err)
}
});
n.receive(message);
setTimeout(function() {
hooks.clear();
if (hookErrors.length > 0) {
done(hookErrors[0])
} else {
done();
}
},10);
});
describe("errors thrown by hooks are reported", function() {
before(function() {
hooks.add("onReceive",function(recEvent) {
if (recEvent.msg.payload === "trigger-onReceive") {
throw new Error("onReceive Error")
}
})
hooks.add("postReceive",function(recEvent) {
if (recEvent.msg.payload === "trigger-postReceive") {
throw new Error("postReceive Error")
}
})
})
after(function() {
hooks.clear();
})
function testHook(hook, msgExpected, done) {
var messageReceived = false;
var errorReceived;
var n = new RedNode({id:'123',type:'abc'});
var message = {payload:"trigger-"+hook};
n.on('input',function(msg) {
messageReceived = true;
});
n.error = function (err) {
errorReceived = err;
}
n.receive(message);
setTimeout(function() {
try {
messageReceived.should.equal(msgExpected,`Hook ${hook} messageReceived expected ${msgExpected} actual ${messageReceived}`);
should.exist(errorReceived);
errorReceived.toString().should.containEql(hook)
done()
} catch(err) {
done(err);
}
},10);
}
it("onReceive", function(done) { testHook("onReceive", false, done)})
it("postReceive", function(done) { testHook("postReceive", true, done)})
})
});
describe("hooks can halt receive", function() {
before(function() {
hooks.add("onReceive",function(recEvent) {
if (recEvent.msg.payload === "trigger-onReceive") {
return false;
}
})
})
after(function() {
hooks.clear();
})
function testHook(hook, msgExpected, done) {
var messageReceived = false;
var errorReceived;
var n = new RedNode({id:'123',type:'abc'});
var message = {payload:"trigger-"+hook};
n.on('input',function(msg) {
messageReceived = true;
});
n.error = function (err) {
errorReceived = err;
}
n.receive(message);
setTimeout(function() {
try {
messageReceived.should.equal(msgExpected,`Hook ${hook} messageReceived expected ${msgExpected} actual ${messageReceived}`);
should.not.exist(errorReceived);
done()
} catch(err) {
done(err);
}
},10);
}
it("onReceive", function(done) { testHook("onReceive", false, done)})
})
describe('#send', function() {
it('emits a single message', function(done) {
var flow = {
send: (sendEvents) => {
try {
sendEvents.should.have.length(1);
sendEvents[0].msg.should.equal(message);
sendEvents[0].destination.should.eql({id:"n2", node: undefined});
sendEvents[0].source.should.eql({id:"n1", node: n1, port: 0})
done();
} catch(err) {
done(err);
}
},
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var message = {payload:"hello world"};
n1.send(message);
});
it('emits a message with callback provided send', function(done) {
var flow = {
handleError: (node,logMessage,msg,reportingNode) => {done(logMessage)},
handleComplete: (node,msg) => {},
send: (sendEvents) => {
try {
sendEvents.should.have.length(1);
sendEvents[0].msg.should.equal(message);
sendEvents[0].destination.should.eql({id:"n2", node: undefined});
sendEvents[0].source.should.eql({id:"n1", node: n1, port: 0});
sendEvents[0].cloneMessage.should.be.false();
done();
} catch(err) {
done(err);
}
},
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var message = {payload:"hello world"};
n1.on('input',function(msg,nodeSend,nodeDone) {
nodeSend(msg);
nodeDone();
});
n1.receive(message);
});
it('emits multiple messages on a single output', function(done) {
var flow = {
handleError: (node,logMessage,msg,reportingNode) => {done(logMessage)},
send: (sendEvents) => {
try {
sendEvents.should.have.length(2);
sendEvents[0].msg.should.equal(messages[0]);
sendEvents[0].destination.should.eql({id:"n2", node: undefined});
sendEvents[0].source.should.eql({id:"n1", node: n1, port: 0});
sendEvents[0].cloneMessage.should.be.false();
sendEvents[1].msg.should.equal(messages[1]);
sendEvents[1].destination.should.eql({id:"n2", node: undefined});
sendEvents[1].source.should.eql({id:"n1", node: n1, port: 0});
sendEvents[1].cloneMessage.should.be.true();
done();
} catch(err) {
done(err);
}
},
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var messages = [
{payload:"hello world"},
{payload:"hello world again"}
];
n1.send([messages]);
});
it('emits messages to multiple outputs', function(done) {
var flow = {
handleError: (node,logMessage,msg,reportingNode) => {done(logMessage)},
send: (sendEvents) => {
try {
sendEvents.should.have.length(3);
sendEvents[0].msg.should.equal(messages[0]);
sendEvents[0].destination.should.eql({id:"n2", node: undefined});
sendEvents[0].source.should.eql({id:"n1", node: n1, port: 0});
sendEvents[0].cloneMessage.should.be.false();
should.exist(sendEvents[0].msg._msgid);
sendEvents[1].msg.should.equal(messages[2]);
sendEvents[1].destination.should.eql({id:"n4", node: undefined});
sendEvents[1].source.should.eql({id:"n1", node: n1, port: 2})
sendEvents[1].cloneMessage.should.be.true();
should.exist(sendEvents[1].msg._msgid);
sendEvents[2].msg.should.equal(messages[2]);
sendEvents[2].destination.should.eql({id:"n5", node: undefined});
sendEvents[2].source.should.eql({id:"n1", node: n1, port: 2})
sendEvents[2].cloneMessage.should.be.true();
should.exist(sendEvents[2].msg._msgid);
sendEvents[0].msg._msgid.should.eql(sendEvents[1].msg._msgid)
sendEvents[1].msg._msgid.should.eql(sendEvents[2].msg._msgid)
done();
} catch(err) {
done(err);
}
}
};
var n1 = new RedNode({_flow:flow, id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]});
var n2 = new RedNode({_flow:flow, id:'n2',type:'abc'});
var n3 = new RedNode({_flow:flow, id:'n3',type:'abc'});
var n4 = new RedNode({_flow:flow, id:'n4',type:'abc'});
var n5 = new RedNode({_flow:flow, id:'n5',type:'abc'});
var messages = [
{payload:"hello world"},
null,
{payload:"hello world again"}
];
var rcvdCount = 0;
n1.send(messages);
});
it('emits no messages', function(done) {
var flow = {
handleError: (node,logMessage,msg,reportingNode) => {done(logMessage)},
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
n2.on('input',function(msg) {
should.fail(null,null,"unexpected message");
});
setTimeout(function() {
done();
}, 200);
n1.send();
});
// it('emits messages without cloning req or res', function(done) {
// var flow = {
// getNode: (id) => { return {'n1':n1,'n2':n2,'n3':n3}[id]},
// send: (node,dst,msg) => { setImmediate(function() { flow.getNode(dst).receive(msg) })}
// };
// var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[[['n2'],['n3']]]});
// var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
// var n3 = new RedNode({_flow:flow,id:'n3',type:'abc'});
//
// var req = {};
// var res = {};
// var cloned = {};
// var message = {payload: "foo", cloned: cloned, req: req, res: res};
//
// var rcvdCount = 0;
//
// // first message to be sent, so should not be cloned
// n2.on('input',function(msg) {
// should.deepEqual(msg, message);
// msg.cloned.should.be.exactly(message.cloned);
// msg.req.should.be.exactly(message.req);
// msg.res.should.be.exactly(message.res);
// rcvdCount += 1;
// if (rcvdCount == 2) {
// done();
// }
// });
//
// // second message to be sent, so should be cloned
// // message uuids wont match since we've cloned
// n3.on('input',function(msg) {
// msg.payload.should.equal(message.payload);
// msg.cloned.should.not.be.exactly(message.cloned);
// msg.req.should.be.exactly(message.req);
// msg.res.should.be.exactly(message.res);
// rcvdCount += 1;
// if (rcvdCount == 2) {
// done();
// }
// });
//
// n1.send(message);
// });
// it("logs the uuid for all messages sent", function(done) {
// var logHandler = {
// msgIds:[],
// messagesSent: 0,
// emit: function(event, msg) {
// if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
// this.messagesSent++;
// this.msgIds.push(msg.msgid);
// (typeof msg.msgid).should.not.be.equal("undefined");
// }
// }
// };
//
// Log.addHandler(logHandler);
// var flow = {
// getNode: (id) => { return {'n1':sender,'n2':receiver1,'n3':receiver2}[id]},
// send: (node,dst,msg) => { setImmediate(function() { flow.getNode(dst).receive(msg) })}
// };
//
// var sender = new RedNode({_flow:flow,id:'n1',type:'abc', wires:[['n2', 'n3']]});
// var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'});
// var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'});
// sender.send({"some": "message"});
// setTimeout(function() {
// try {
// logHandler.messagesSent.should.equal(1);
// should.exist(logHandler.msgIds[0])
// Log.removeHandler(logHandler);
// done();
// } catch(err) {
// Log.removeHandler(logHandler);
// done(err);
// }
// },50)
// })
});
describe('#log', function() {
it('produces a log message', function(done) {
var n = new RedNode({id:'123',type:'abc',z:'789', _flow: {log:function(msg) { loginfo = msg;}}});
var loginfo = {};
n.log("a log message");
should.deepEqual({level:Log.INFO, id:n.id,
type:n.type, msg:"a log message",z:'789'}, loginfo);
done();
});
it('produces a log message with a name', function(done) {
var n = new RedNode({id:'123', type:'abc', name:"barney", z:'789', _flow: {log:function(msg) { loginfo = msg;}}});
var loginfo = {};
n.log("a log message");
should.deepEqual({level:Log.INFO, id:n.id, name: "barney",
type:n.type, msg:"a log message",z:'789'}, loginfo);
done();
});
});
describe('#warn', function() {
it('produces a warning message', function(done) {
var n = new RedNode({id:'123',type:'abc',z:'789', _flow: {log:function(msg) { loginfo = msg;}}});
var loginfo = {};
n.warn("a warning");
should.deepEqual({level:Log.WARN, id:n.id,
type:n.type, msg:"a warning",z:'789'}, loginfo);
done();
});
});
describe('#error', function() {
it('handles a null error message', function(done) {
var flow = {
handleError: sinon.stub(),
log:sinon.stub()
}
var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'});
var message = {a:1};
n.error(null,message);
flow.handleError.called.should.be.true();
flow.handleError.args[0][0].should.eql(n);
flow.handleError.args[0][1].should.eql("");
flow.handleError.args[0][2].should.eql(message);
done();
});
it('produces an error message', function(done) {
var flow = {
handleError: sinon.stub(),
log:sinon.stub()
}
var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'});
var message = {a:2};
n.error("This is an error",message);
flow.handleError.called.should.be.true();
flow.handleError.args[0][0].should.eql(n);
flow.handleError.args[0][1].should.eql("This is an error");
flow.handleError.args[0][2].should.eql(message);
done();
});
});
describe('#metric', function() {
it('produces a metric message', function(done) {
var n = new RedNode({id:'123',type:'abc'});
var loginfo = {};
sinon.stub(Log, 'log').callsFake(function(msg) {
loginfo = msg;
});
var msg = {payload:"foo", _msgid:"987654321"};
n.metric("test.metric",msg,"15mb");
should.deepEqual({value:"15mb", level:Log.METRIC, nodeid:n.id,
event:"node.abc.test.metric",msgid:"987654321"}, loginfo);
Log.log.restore();
done();
});
});
describe('#metric', function() {
it('returns metric value if eventname undefined', function(done) {
var n = new RedNode({id:'123',type:'abc'});
var loginfo = {};
sinon.stub(Log, 'log').callsFake(function(msg) {
loginfo = msg;
});
var msg = {payload:"foo", _msgid:"987654321"};
var m = n.metric(undefined,msg,"15mb");
m.should.be.a.Boolean();
Log.log.restore();
done();
});
it('returns not defined if eventname defined', function(done) {
var n = new RedNode({id:'123',type:'abc'});
var loginfo = {};
sinon.stub(Log, 'log').callsFake(function(msg) {
loginfo = msg;
});
var msg = {payload:"foo", _msgid:"987654321"};
var m = n.metric("info",msg,"15mb");
should(m).be.undefined;
Log.log.restore();
done();
});
});
describe('#status', function() {
it('publishes status', function(done) {
var flow = {
handleStatus: sinon.stub()
}
var n = new RedNode({_flow:flow,id:'123',type:'abc'});
var status = {fill:"green",shape:"dot",text:"connected"};
n.status(status);
flow.handleStatus.called.should.be.true();
flow.handleStatus.args[0][0].should.eql(n);
flow.handleStatus.args[0][1].should.eql(status);
done();
});
it('publishes status for plain string', function(done) {
var flow = { handleStatus: sinon.stub() }
var n = new RedNode({_flow:flow,id:'123',type:'abc'});
n.status("text status");
flow.handleStatus.called.should.be.true();
flow.handleStatus.args[0][0].should.eql(n);
flow.handleStatus.args[0][1].should.eql({text:"text status"});
done();
});
it('publishes status for plain boolean', function(done) {
var flow = { handleStatus: sinon.stub() }
var n = new RedNode({_flow:flow,id:'123',type:'abc'});
n.status(false);
flow.handleStatus.called.should.be.true();
flow.handleStatus.args[0][0].should.eql(n);
flow.handleStatus.args[0][1].should.eql({text:"false"});
done();
});
it('publishes status for plain number', function(done) {
var flow = { handleStatus: sinon.stub() }
var n = new RedNode({_flow:flow,id:'123',type:'abc'});
n.status(123);
flow.handleStatus.called.should.be.true();
flow.handleStatus.args[0][0].should.eql(n);
flow.handleStatus.args[0][1].should.eql({text:"123"});
done();
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,883 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require('should');
var fs = require('fs-extra');
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var LocalFileSystem = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/context/localfilesystem");
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context"));
var defaultContextBase = "context";
describe('localfilesystem',function() {
before(function() {
return fs.remove(resourcesDir);
});
describe('#get/set',function() {
var context;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
}).then(function(){
return fs.remove(resourcesDir);
});
});
it('should store property',function(done) {
context.get("nodeX","foo",function(err, value){
if (err) { return done(err); }
should.not.exist(value);
context.set("nodeX","foo","test",function(err){
if (err) { return done(err); }
context.get("nodeX","foo",function(err, value){
if (err) { return done(err); }
value.should.be.equal("test");
done();
});
});
});
});
it('should store property - creates parent properties',function(done) {
context.set("nodeX","foo.bar","test",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.eql({bar:"test"});
done();
});
});
});
it('should store local scope property', function (done) {
context.set("abc:def", "foo.bar", "test", function (err) {
context.get("abc:def", "foo", function (err, value) {
value.should.be.eql({ bar: "test" });
done();
});
});
});
it('should delete property',function(done) {
context.set("nodeX","foo.abc.bar1","test1",function(err){
context.set("nodeX","foo.abc.bar2","test2",function(err){
context.get("nodeX","foo.abc",function(err, value){
value.should.be.eql({bar1:"test1",bar2:"test2"});
context.set("nodeX","foo.abc.bar1",undefined,function(err){
context.get("nodeX","foo.abc",function(err, value){
value.should.be.eql({bar2:"test2"});
context.set("nodeX","foo.abc",undefined,function(err){
context.get("nodeX","foo.abc",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",undefined,function(err){
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
done();
});
});
});
});
});
});
});
});
});
});
it('should not shared context with other scope', function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
done();
});
});
});
});
});
});
});
it('should store string',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","bar",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.String();
value.should.be.equal("bar");
context.set("nodeX","foo","1",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.String();
value.should.be.equal("1");
done();
});
});
});
});
});
});
it('should store number',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",1,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Number();
value.should.be.equal(1);
done();
});
});
});
});
it('should store null',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",null,function(err){
context.get("nodeX","foo",function(err, value){
should(value).be.null();
done();
});
});
});
});
it('should store boolean',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",true,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Boolean().and.true();
context.set("nodeX","foo",false,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Boolean().and.false();
done();
});
});
});
});
});
});
it('should store object',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",{obj:"bar"},function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Object();
value.should.eql({obj:"bar"});
done();
});
});
});
});
it('should store array',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",["a","b","c"],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.eql(["a","b","c"]);
context.get("nodeX","foo[1]",function(err, value){
value.should.be.String();
value.should.equal("b");
done();
});
});
});
});
});
it('should store array of arrays',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.have.length(3);
value[0].should.have.length(3);
value[1].should.have.length(4);
value[2].should.have.length(2);
context.get("nodeX","foo[1]",function(err, value){
value.should.be.Array();
value.should.have.length(4);
value.should.be.eql([1,2,3,4]);
done();
});
});
});
});
});
it('should store array of objects',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.have.length(3);
value[0].should.be.Object();
value[1].should.be.Object();
value[2].should.be.Object();
context.get("nodeX","foo[1]",function(err, value){
value.should.be.Object();
value.should.be.eql({obj:"bar2"});
done();
});
});
});
});
});
it('should set/get multiple values', function(done) {
context.set("nodeX",["one","two","three"],["test1","test2","test3"], function(err) {
context.get("nodeX",["one","two"], function() {
Array.prototype.slice.apply(arguments).should.eql([undefined,"test1","test2"])
done();
});
});
})
it('should set/get multiple values - get unknown', function(done) {
context.set("nodeX",["one","two","three"],["test1","test2","test3"], function(err) {
context.get("nodeX",["one","two","unknown"], function() {
Array.prototype.slice.apply(arguments).should.eql([undefined,"test1","test2",undefined])
done();
});
});
})
it('should set/get multiple values - single value providd', function(done) {
context.set("nodeX",["one","two","three"],"test1", function(err) {
context.get("nodeX",["one","two"], function() {
Array.prototype.slice.apply(arguments).should.eql([undefined,"test1",null])
done();
});
});
})
it('should throw error if bad key included in multiple keys - get', function(done) {
context.set("nodeX",["one","two","three"],["test1","test2","test3"], function(err) {
context.get("nodeX",["one",".foo","three"], function(err) {
should.exist(err);
done();
});
});
})
it('should throw error if bad key included in multiple keys - set', function(done) {
context.set("nodeX",["one",".foo","three"],["test1","test2","test3"], function(err) {
should.exist(err);
// Check 'one' didn't get set as a result
context.get("nodeX","one",function(err,one) {
should.not.exist(one);
done();
})
});
})
it('should throw an error when getting a value with invalid key', function (done) {
context.set("nodeX","foo","bar",function(err) {
context.get("nodeX"," ",function(err,value) {
should.exist(err);
done();
});
});
});
it('should throw an error when setting a value with invalid key',function (done) {
context.set("nodeX"," ","bar",function (err) {
should.exist(err);
done();
});
});
it('should throw an error when callback of get() is not a function',function (done) {
try {
context.get("nodeX","foo","callback");
done("should throw an error.");
} catch (err) {
done();
}
});
it('should throw an error when callback of get() is not specified',function (done) {
try {
context.get("nodeX","foo");
done("should throw an error.");
} catch (err) {
done();
}
});
it('should throw an error when callback of set() is not a function',function (done) {
try {
context.set("nodeX","foo","bar","callback");
done("should throw an error.");
} catch (err) {
done();
}
});
it('should not throw an error when callback of set() is not specified', function (done) {
try {
context.set("nodeX"," ","bar");
done();
} catch (err) {
done("should not throw an error.");
}
});
it('should handle empty context file', function (done) {
fs.outputFile(path.join(resourcesDir,defaultContextBase,"nodeX","flow.json"),"",function(){
context.get("nodeX", "foo", function (err, value) {
should.not.exist(value);
context.set("nodeX", "foo", "test", function (err) {
context.get("nodeX", "foo", function (err, value) {
value.should.be.equal("test");
done();
});
});
});
});
});
it('should throw an error when reading corrupt context file', function (done) {
fs.outputFile(path.join(resourcesDir, defaultContextBase, "nodeX", "flow.json"),"{abc",function(){
context.get("nodeX", "foo", function (err, value) {
should.exist(err);
done();
});
});
});
});
describe('#keys',function() {
var context;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
}).then(function(){
return fs.remove(resourcesDir);
});
});
it('should enumerate context keys', function(done) {
context.keys("nodeX",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.set("nodeX","foo","bar",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(1);
value[0].should.equal("foo");
context.set("nodeX","abc.def","bar",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(2);
value[1].should.equal("abc");
done();
});
});
});
});
});
});
it('should enumerate context keys in each scopes', function(done) {
context.keys("nodeX",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.keys("nodeY",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.set("nodeX","foo","bar",function(err){
context.set("nodeY","hoge","piyo",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(1);
value[0].should.equal("foo");
context.keys("nodeY",function(err, value){
value.should.have.length(1);
value[0].should.equal("hoge");
done();
});
});
});
});
});
});
});
it('should throw an error when callback of keys() is not a function', function (done) {
try {
context.keys("nodeX", "callback");
done("should throw an error.");
} catch (err) {
done();
}
});
it('should throw an error when callback of keys() is not specified', function (done) {
try {
context.keys("nodeX");
done("should throw an error.");
} catch (err) {
done();
}
});
});
describe('#delete',function() {
var context;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
}).then(function(){
return fs.remove(resourcesDir);
});
});
it('should delete context',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
context.delete("nodeX").then(function(){
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
done();
});
});
}).catch(done);
});
});
});
});
});
});
});
});
describe('#clean',function() {
var context;
var contextGet;
var contextSet;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
contextGet = function(scope,key) {
return new Promise((res,rej) => {
context.get(scope,key, function(err,value) {
if (err) {
rej(err);
} else {
res(value);
}
})
});
}
contextSet = function(scope,key,value) {
return new Promise((res,rej) => {
context.set(scope,key,value, function(err) {
if (err) {
rej(err);
} else {
res();
}
})
});
}
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close().then(function(){
return fs.remove(resourcesDir);
});
});
});
it('should clean unnecessary context',function(done) {
contextSet("global","foo","testGlobal").then(function() {
return contextSet("nodeX:flow1","foo","testX");
}).then(function() {
return contextSet("nodeY:flow2","foo","testY");
}).then(function() {
return contextGet("nodeX:flow1","foo");
}).then(function(value) {
value.should.be.equal("testX");
}).then(function() {
return contextGet("nodeY:flow2","foo");
}).then(function(value) {
value.should.be.equal("testY");
}).then(function() {
return context.clean([])
}).then(function() {
return contextGet("nodeX:flow1","foo");
}).then(function(value) {
should.not.exist(value);
}).then(function() {
return contextGet("nodeY:flow2","foo");
}).then(function(value) {
should.not.exist(value);
}).then(function() {
return contextGet("global","foo");
}).then(function(value) {
value.should.eql("testGlobal");
}).then(done).catch(done);
});
it('should not clean active context',function(done) {
contextSet("global","foo","testGlobal").then(function() {
return contextSet("nodeX:flow1","foo","testX");
}).then(function() {
return contextSet("nodeY:flow2","foo","testY");
}).then(function() {
return contextGet("nodeX:flow1","foo");
}).then(function(value) {
value.should.be.equal("testX");
}).then(function() {
return contextGet("nodeY:flow2","foo");
}).then(function(value) {
value.should.be.equal("testY");
}).then(function() {
return context.clean(["flow1","nodeX"])
}).then(function() {
return contextGet("nodeX:flow1","foo");
}).then(function(value) {
value.should.be.equal("testX");
}).then(function() {
return contextGet("nodeY:flow2","foo");
}).then(function(value) {
should.not.exist(value);
}).then(function() {
return contextGet("global","foo");
}).then(function(value) {
value.should.eql("testGlobal");
}).then(done).catch(done);
});
});
describe('#if cache is enabled',function() {
var context;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
}).then(function(){
return fs.remove(resourcesDir);
});
});
it('should load contexts into the cache',function() {
var globalData = {key:"global"};
var flowData = {key:"flow"};
var nodeData = {key:"node"};
return Promise.all([
fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8"),
fs.outputFile(path.join(resourcesDir,defaultContextBase,"flow","flow.json"), JSON.stringify(flowData,null,4), "utf8"),
fs.outputFile(path.join(resourcesDir,defaultContextBase,"flow","node.json"), JSON.stringify(nodeData,null,4), "utf8")
]).then(function(){
context = LocalFileSystem({dir: resourcesDir, cache: true});
return context.open();
}).then(function(){
return Promise.all([
fs.remove(path.join(resourcesDir,defaultContextBase,"global","global.json")),
fs.remove(path.join(resourcesDir,defaultContextBase,"flow","flow.json")),
fs.remove(path.join(resourcesDir,defaultContextBase,"flow","node.json"))
]);
}).then(function(){
context.get("global","key").should.be.equal("global");
context.get("flow","key").should.be.equal("flow");
context.get("node:flow","key").should.be.equal("node");
});
});
it('should store property to the cache',function() {
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 1});
return context.open().then(function(){
return new Promise(function(resolve, reject){
context.set("global","foo","bar",function(err){
if(err){
reject(err);
} else {
fs.readJson(path.join(resourcesDir,defaultContextBase,"global","global.json")).then(function(data) {
// File should not exist as flush hasn't happened
reject("File global/global.json should not exist");
}).catch(function(err) {
setTimeout(function() {
fs.readJson(path.join(resourcesDir,defaultContextBase,"global","global.json")).then(function(data) {
data.should.eql({foo:'bar'});
resolve();
}).catch(function(err) {
reject(err);
});
},1100)
})
}
});
});
}).then(function(){
return fs.remove(path.join(resourcesDir,defaultContextBase,"global","global.json"));
}).then(function(){
context.get("global","foo").should.be.equal("bar");
})
});
it('should enumerate context keys in the cache',function() {
var globalData = {foo:"bar"};
return fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8").then(function(){
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2});
return context.open()
}).then(function(){
return fs.remove(path.join(resourcesDir,defaultContextBase,"global","global.json"));
}).then(function(){
var keys = context.keys("global");
keys.should.have.length(1);
keys[0].should.equal("foo");
return new Promise(function(resolve, reject){
context.set("global","foo2","bar2",function(err){
if(err){
reject(err);
} else {
resolve();
}
});
});
}).then(function(){
return fs.remove(path.join(resourcesDir,defaultContextBase,"global","global.json"));
}).then(function(){
var keys = context.keys("global");
keys.should.have.length(2);
keys[1].should.equal("foo2");
})
});
it('should delete context in the cache',function() {
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2});
return context.open().then(function(){
return new Promise(function(resolve, reject){
context.set("global","foo","bar",function(err){
if(err){
reject(err);
} else {
resolve();
}
});
});
}).then(function(){
context.get("global","foo").should.be.equal("bar");
return context.delete("global");
}).then(function(){
should.not.exist(context.get("global","foo"))
})
});
it('should clean unnecessary context in the cache',function() {
var flowAData = {key:"flowA"};
var flowBData = {key:"flowB"};
return Promise.all([
fs.outputFile(path.join(resourcesDir,defaultContextBase,"flowA","flow.json"), JSON.stringify(flowAData,null,4), "utf8"),
fs.outputFile(path.join(resourcesDir,defaultContextBase,"flowB","flow.json"), JSON.stringify(flowBData,null,4), "utf8")
]).then(function(){
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2});
return context.open();
}).then(function(){
context.get("flowA","key").should.be.equal("flowA");
context.get("flowB","key").should.be.equal("flowB");
return context.clean(["flowA"]);
}).then(function(){
context.get("flowA","key").should.be.equal("flowA");
should.not.exist(context.get("flowB","key"));
});
});
});
describe('Configuration', function () {
var context;
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
}).then(function(){
return fs.remove(resourcesDir);
});
});
it('should change a base directory', function (done) {
var differentBaseContext = LocalFileSystem({
base: "contexts2",
dir: resourcesDir,
cache: false
});
differentBaseContext.open().then(function () {
differentBaseContext.set("node2", "foo2", "bar2", function (err) {
differentBaseContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function(err, value) {
should.not.exist(value);
done();
});
});
});
});
});
it('should use userDir', function (done) {
var userDirContext = LocalFileSystem({
base: "contexts2",
cache: false,
settings: {
userDir: resourcesDir
}
});
userDirContext.open().then(function () {
userDirContext.set("node2", "foo2", "bar2", function (err) {
userDirContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
});
it('should use NODE_RED_HOME', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
process.env.NODE_RED_HOME = resourcesDir;
fs.ensureDirSync(resourcesDir);
fs.writeFileSync(path.join(resourcesDir,".config.json"),"");
var nrHomeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
nrHomeContext.open().then(function () {
nrHomeContext.set("node2", "foo2", "bar2", function (err) {
nrHomeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
}
});
it('should use HOME_PATH', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
var oldHOMEPATH = process.env.HOMEPATH;
process.env.NODE_RED_HOME = resourcesDir;
process.env.HOMEPATH = resourcesDir;
var homePath = path.join(resourcesDir, ".node-red");
fs.outputFile(path.join(homePath, ".config.json"),"",function(){
var homeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
homeContext.open().then(function () {
homeContext.set("node2", "foo2", "bar2", function (err) {
homeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
process.env.HOMEPATH = oldHOMEPATH;
}
});
});
it('should use HOME_PATH', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
var oldHOMEPATH = process.env.HOMEPATH;
var oldHOME = process.env.HOME;
process.env.NODE_RED_HOME = resourcesDir;
process.env.HOMEPATH = resourcesDir;
process.env.HOME = resourcesDir;
var homeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
homeContext.open().then(function () {
homeContext.set("node2", "foo2", "bar2", function (err) {
homeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
process.env.HOMEPATH = oldHOMEPATH;
process.env.HOME = oldHOME;
}
});
});
});

View File

@@ -1,321 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require('should');
var NR_TEST_UTILS = require("nr-test-utils");
var Memory = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/context/memory");
describe('memory',function() {
var context;
beforeEach(function() {
context = Memory({});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
});
});
describe('#get/set',function() {
describe('sync',function() {
it('should store property',function() {
should.not.exist(context.get("nodeX","foo"));
context.set("nodeX","foo","test");
context.get("nodeX","foo").should.equal("test");
});
it('should store property - creates parent properties',function() {
context.set("nodeX","foo.bar","test");
context.get("nodeX","foo").should.eql({bar:"test"});
});
it('should delete property',function() {
context.set("nodeX","foo.abc.bar1","test1");
context.set("nodeX","foo.abc.bar2","test2");
context.get("nodeX","foo.abc").should.eql({bar1:"test1",bar2:"test2"});
context.set("nodeX","foo.abc.bar1",undefined);
context.get("nodeX","foo.abc").should.eql({bar2:"test2"});
context.set("nodeX","foo.abc",undefined);
should.not.exist(context.get("nodeX","foo.abc"));
context.set("nodeX","foo",undefined);
should.not.exist(context.get("nodeX","foo"));
});
it('should not shared context with other scope', function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","testX");
context.set("nodeY","foo","testY");
context.get("nodeX","foo").should.equal("testX");
context.get("nodeY","foo").should.equal("testY");
});
it('should throw the error if the error occurs', function() {
try{
context.set("nodeX",".foo","test");
should.fail("Error was not thrown");
}catch(err){
should.exist(err);
try{
context.get("nodeX",".foo");
should.fail("Error was not thrown");
}catch(err){
should.exist(err);
}
}
});
it('should get multiple values - all known', function() {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
var values = context.get("nodeX",["one","two","four"]);
values.should.eql(["test1","test2","test4"])
})
it('should get multiple values - include unknown', function() {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
var values = context.get("nodeX",["one","unknown.with.multiple.levels"]);
values.should.eql(["test1",undefined])
})
it('should throw error if bad key included in multiple keys', function() {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
try{
var values = context.get("nodeX",["one",".foo","three"]);
should.fail("Error was not thrown");
}catch(err){
should.exist(err);
}
})
});
describe('async',function() {
it('should store property',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","test",function(err){
context.get("nodeX","foo",function(err, value){
value.should.equal("test");
done();
});
});
});
});
it('should pass the error to callback if the error occurs',function(done) {
context.set("nodeX",".foo","test",function(err, value){
should.exist(err);
context.get("nodeX",".foo",function(err){
should.exist(err);
done();
});
});
});
it('should get multiple values - all known', function(done) {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
context.get("nodeX",["one","two","four"],function() {
Array.prototype.slice.apply(arguments).should.eql([undefined,"test1","test2","test4"])
done();
});
})
it('should get multiple values - include unknown', function(done) {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
context.get("nodeX",["one","unknown"],function() {
Array.prototype.slice.apply(arguments).should.eql([undefined,"test1",undefined])
done();
});
})
it('should throw error if bad key included in multiple keys', function(done) {
context.set("nodeX","one","test1");
context.set("nodeX","two","test2");
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
context.get("nodeX",["one",".foo","three"], function(err) {
should.exist(err);
done();
});
})
});
});
describe('#keys',function() {
describe('sync',function() {
it('should enumerate context keys', function() {
var keys = context.keys("nodeX");
keys.should.be.an.Array();
keys.should.be.empty();
context.set("nodeX","foo","bar");
keys = context.keys("nodeX");
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("nodeX","abc.def","bar");
keys = context.keys("nodeX");
keys.should.have.length(2);
keys[1].should.equal("abc");
});
it('should enumerate context keys in each scopes', function() {
var keysX = context.keys("nodeX");
keysX.should.be.an.Array();
keysX.should.be.empty();
var keysY = context.keys("nodeY");
keysY.should.be.an.Array();
keysY.should.be.empty();
context.set("nodeX","foo","bar");
context.set("nodeY","hoge","piyo");
keysX = context.keys("nodeX");
keysX.should.have.length(1);
keysX[0].should.equal("foo");
keysY = context.keys("nodeY");
keysY.should.have.length(1);
keysY[0].should.equal("hoge");
});
it('should enumerate global context keys', function () {
var keys = context.keys("global");
keys.should.be.an.Array();
keys.should.be.empty();
context.set("global", "foo", "bar");
keys = context.keys("global");
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("global", "abc.def", "bar");
keys = context.keys("global");
keys.should.have.length(2);
keys[1].should.equal("abc");
});
it('should not return specific keys as global context keys', function () {
var keys = context.keys("global");
context.set("global", "set", "bar");
context.set("global", "get", "bar");
context.set("global", "keys", "bar");
keys = context.keys("global");
keys.should.have.length(0);
});
});
describe('async',function() {
it('should enumerate context keys', function(done) {
context.keys("nodeX", function(err, keys) {
keys.should.be.an.Array();
keys.should.be.empty();
context.set("nodeX", "foo", "bar", function(err) {
context.keys("nodeX", function(err, keys) {
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("nodeX","abc.def","bar",function(err){
context.keys("nodeX",function(err, keys){
keys.should.have.length(2);
keys[1].should.equal("abc");
done();
});
});
});
});
});
});
});
});
describe('#delete',function() {
it('should delete context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.delete("nodeX").then(function(){
should.not.exist(context.get("nodeX","foo"));
should.exist(context.get("nodeY","foo"));
});
});
});
describe('#clean',function() {
it('should clean unnecessary context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.clean([]).then(function(){
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
});
});
it('should not clean active context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.clean(["nodeX"]).then(function(){
should.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
});
});
it('should not clean global context', function () {
context.set("global", "foo", "abc");
context.get("global", "foo").should.equal("abc");
return context.clean(["global"]).then(function () {
should.exist(context.get("global", "foo"));
});
});
});
});

View File

@@ -1,474 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var util = require("util");
var NR_TEST_UTILS = require("nr-test-utils");
var index = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/index");
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
var log = NR_TEST_UTILS.require("@node-red/util").log;
describe('red/runtime/nodes/credentials', function() {
var encryptionDisabledSettings = {
get: function(key) {
return false;
}
}
afterEach(function() {
index.clearRegistry();
});
it('loads provided credentials',function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
return credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.get("a").should.have.property('b',1);
credentials.get("a").should.have.property('c',2);
});
});
it('adds a new credential',function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
return credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false();
should.not.exist(credentials.get("b"));
return credentials.add("b",{"foo":"bar"}).then(function() {
credentials.get("b").should.have.property("foo","bar");
credentials.dirty().should.be.true();
});
});
});
it('deletes an existing credential',function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
return credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false();
credentials.delete("a");
should.not.exist(credentials.get("a"));
credentials.dirty().should.be.true();
});
});
it('exports the credentials, clearing dirty flag', function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
var creds = {"a":{"b":1,"c":2}};
return credentials.load(creds).then(function() {
return credentials.add("b",{"foo":"bar"})
}).then(function() {
credentials.dirty().should.be.true();
return credentials.export().then(function(exported) {
exported.should.eql(creds);
credentials.dirty().should.be.false();
})
});
})
describe("#clean",function() {
it("removes credentials of unknown nodes",function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings,
nodes: { getType: () => function(){} }
});
var creds = {"a":{"b":1,"c":2},"b":{"d":3}};
return credentials.load(creds).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("a"));
should.exist(credentials.get("b"));
return credentials.clean([{id:"b"}]).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("a"));
should.exist(credentials.get("b"));
});
});
});
it("extracts credentials of known nodes",function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings,
nodes: { getType: () => function(){} }
});
credentials.register("testNode",{"b":"text","c":"password"})
var creds = {"a":{"b":1,"c":2}};
var newConfig = [{id:"a",type:"testNode",credentials:{"b":"newBValue","c":"newCValue"}}];
return credentials.load(creds).then(function() {
credentials.dirty().should.be.false();
return credentials.clean(newConfig).then(function() {
credentials.dirty().should.be.true();
credentials.get("a").should.have.property('b',"newBValue");
credentials.get("a").should.have.property('c',"newCValue");
should.not.exist(newConfig[0].credentials);
});
});
});
});
it('warns if a node has no credential definition', function() {
credentials.init({
log: log,
settings: encryptionDisabledSettings,
nodes: { getType: () => function(){} }
});
return credentials.load({}).then(function() {
var node = {id:"node",type:"test",credentials:{
user1:"newUser",
password1:"newPassword"
}};
sinon.spy(log,"warn");
credentials.extract(node);
log.warn.called.should.be.true();
should.not.exist(node.credentials);
log.warn.restore();
});
})
it('extract credential updates in the provided node', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings,
nodes: { getType: () => function(){} }
});
var defintion = {
user1:{type:"text"},
password1:{type:"password"},
user2:{type:"text"},
password2:{type:"password"},
user3:{type:"text"},
password3:{type:"password"}
};
credentials.register("test",defintion);
var def = credentials.getDefinition("test");
defintion.should.eql(def);
credentials.load({"node":{user1:"abc",password1:"123",user2:"def",password2:"456",user3:"ghi",password3:"789"}}).then(function() {
var node = {id:"node",type:"test",credentials:{
// user1 unchanged
password1:"__PWRD__",
user2: "",
password2:" ",
user3:"newUser",
password3:"newPassword"
}};
credentials.dirty().should.be.false();
credentials.extract(node);
node.should.not.have.a.property("credentials");
credentials.dirty().should.be.true();
var newCreds = credentials.get("node");
newCreds.should.have.a.property("user1","abc");
newCreds.should.have.a.property("password1","123");
newCreds.should.not.have.a.property("user2");
newCreds.should.not.have.a.property("password2");
newCreds.should.have.a.property("user3","newUser");
newCreds.should.have.a.property("password3","newPassword");
done();
});
});
it('extract ignores node without credentials', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings,
nodes: { getType: () => function(){} }
});
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
var node = {id:"node",type:"test"};
credentials.dirty().should.be.false();
credentials.extract(node);
credentials.dirty().should.be.false();
done();
});
});
describe("encryption",function() {
var settings = {};
var runtime = {
log: log,
settings: {
get: function(key) {
return settings[key];
},
set: function(key,value) {
settings[key] = value;
return Promise.resolve();
},
delete: function(key) {
delete settings[key];
return Promise.resolve();
}
},
nodes: { getType: () => function(){} }
}
it('migrates to encrypted and generates default key', function(done) {
settings = {};
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
settings.should.have.a.property("_credentialSecret");
settings._credentialSecret.should.have.a.length(64);
credentials.dirty().should.be.true();
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
done();
})
});
});
});
it('uses default key', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.dirty().should.be.false();
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key', function(done) {
settings = {
credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key - when settings are otherwise unavailable', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
if (key === 'credentialSecret') {
return "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a";
}
throw new Error();
},
set: function(key,value) {
throw new Error();
}
}
}
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('migrates from default key to user key', function() {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
return credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
return credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
return credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
})
});
});
});
it('migrates from default key to user key - unencrypted original', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var unencryptedFlows = {"node":{user1:"abc",password1:"123"}};
credentials.init(runtime);
credentials.load(unencryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
done();
})
});
});
});
it('migrates from default key to unencrypted', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: false
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
result.should.eql({"node":{user1:"abc",password1:"123"}});
done();
});
});
});
it('handles bad default key - resets credentials', function(done) {
settings = {
_credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
// credentials.dirty().should.be.true();
// should.not.exist(credentials.get("node"));
done();
}).catch(function(err) {
err.should.have.property('code','credentials_load_failed');
done();
});
});
it('handles bad user key - resets credentials', function(done) {
settings = {
credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
// credentials.dirty().should.be.true();
// should.not.exist(credentials.get("node"));
done();
}).catch(function(err) {
err.should.have.property('code','credentials_load_failed');
done();
});
});
it('handles unavailable settings - leaves creds unencrypted', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
throw new Error();
},
set: function(key,value) {
throw new Error();
}
},
nodes: { getType: () => function(){} }
}
// {"node":{user1:"abc",password1:"123"}}
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
result.should.have.a.property("node");
done();
});
});
});
})
})

View File

@@ -1,404 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var fs = require('fs-extra');
var path = require('path');
var sinon = require('sinon');
var inherits = require("util").inherits;
var NR_TEST_UTILS = require("nr-test-utils");
var index = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/index");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
var registry = NR_TEST_UTILS.require("@node-red/registry")
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
describe("red/nodes/index", function() {
before(function() {
sinon.stub(index,"startFlows");
process.env.NODE_RED_HOME = NR_TEST_UTILS.resolve("node-red");
process.env.foo="bar";
});
after(function() {
index.startFlows.restore();
delete process.env.NODE_RED_HOME;
delete process.env.foo;
});
afterEach(function() {
index.clearRegistry();
});
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
var testCredentials = {"tab1":{"b":1, "c":"2", "d":"$(foo)"}};
var storage = {
getFlows: function() {
return Promise.resolve({red:123,flows:testFlows,credentials:testCredentials});
},
saveFlows: function(conf) {
should.deepEqual(testFlows, conf.flows);
return Promise.resolve(123);
}
};
var settings = {
available: function() { return false },
get: function() { return false }
};
var EventEmitter = require('events').EventEmitter;
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:function() {}},
events: new EventEmitter()
};
function TestNode(n) {
this._flow = {getSetting: p => process.env[p]};
index.createNode(this, n);
this.on("log", function() {
// do nothing
});
}
it('nodes are initialised with credentials',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
testnode.credentials.should.have.property('b',1);
testnode.credentials.should.have.property('c',"2");
testnode.credentials.should.have.property('d',"bar");
done();
}).catch(function(err) {
done(err);
});
});
it('flows should be initialised',function(done) {
index.init(runtime);
index.loadFlows().then(function() {
// console.log(testFlows);
// console.log(index.getFlows());
should.deepEqual(testFlows, index.getFlows().flows);
done();
}).catch(function(err) {
done(err);
});
});
describe("registerType", function() {
describe("logs deprecated usage", function() {
before(function() {
sinon.stub(registry,"registerType");
});
after(function() {
registry.registerType.restore();
});
it("called without node-set name", function() {
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:sinon.spy()},
events: new EventEmitter()
}
index.init(runtime);
index.registerType(/*'test-node-set',*/'test', TestNode, {});
runtime.log.warn.called.should.be.true();
registry.registerType.called.should.be.true();
registry.registerType.firstCall.args[0].should.eql('');
registry.registerType.firstCall.args[1].should.eql('test');
registry.registerType.firstCall.args[2].should.eql(TestNode);
});
});
describe("extends constructor with Node constructor", function() {
var TestNodeConstructor;
before(function() {
sinon.stub(registry,"registerType");
});
after(function() {
registry.registerType.restore();
});
beforeEach(function() {
TestNodeConstructor = function TestNodeConstructor() {};
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:sinon.spy()},
events: new EventEmitter()
}
index.init(runtime);
})
it('extends a constructor with the Node constructor', function() {
TestNodeConstructor.prototype.should.not.be.an.instanceOf(Node);
index.registerType('node-set','node-type',TestNodeConstructor);
TestNodeConstructor.prototype.should.be.an.instanceOf(Node);
});
it('does not override a constructor prototype', function() {
function Foo(){};
inherits(TestNodeConstructor,Foo);
TestNodeConstructor.prototype.should.be.an.instanceOf(Foo);
TestNodeConstructor.prototype.should.not.be.an.instanceOf(Node);
index.registerType('node-set','node-type',TestNodeConstructor);
TestNodeConstructor.prototype.should.be.an.instanceOf(Node);
TestNodeConstructor.prototype.should.be.an.instanceOf(Foo);
index.registerType('node-set','node-type2',TestNodeConstructor);
TestNodeConstructor.prototype.should.be.an.instanceOf(Node);
TestNodeConstructor.prototype.should.be.an.instanceOf(Foo);
});
});
describe("register credentials definition", function() {
var http = require('http');
var express = require('express');
var app = express();
var runtime = NR_TEST_UTILS.require("@node-red/runtime");
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
var localfilesystem = NR_TEST_UTILS.require("@node-red/runtime/lib/storage/localfilesystem");
var log = NR_TEST_UTILS.require("@node-red/util").log;
var RED = NR_TEST_UTILS.require("node-red/lib/red.js");
var userDir = path.join(__dirname,".testUserHome");
before(function(done) {
sinon.stub(log,"log").callsFake(function(){});
fs.remove(userDir,function(err) {
fs.mkdir(userDir,function() {
sinon.stub(index, 'load').callsFake(function() {
return new Promise(function(resolve,reject){
resolve([]);
});
});
sinon.stub(localfilesystem, 'getCredentials').callsFake(function() {
return new Promise(function(resolve,reject) {
resolve({"tab1":{"b":1,"c":2}});
});
}) ;
RED.init(http.createServer(function(req,res){app(req,res)}),
{userDir: userDir});
runtime.start().then(function () {
done();
});
});
});
});
after(function(done) {
fs.remove(userDir,function() {
runtime.stop().then(function() {
index.load.restore();
localfilesystem.getCredentials.restore();
log.log.restore();
done();
});
});
});
it('definition defined',function() {
index.registerType('test-node-set','test', TestNode, {
credentials: {
foo: {type:"test"}
}
});
var testnode = new TestNode({id:'tab1',type:'test',name:'barney', '_alias':'tab1'});
index.getCredentialDefinition("test").should.have.property('foo');
});
});
describe("register settings definition", function() {
beforeEach(function() {
sinon.stub(registry,"registerType");
})
afterEach(function() {
registry.registerType.restore();
})
it('registers valid settings',function() {
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:function() {}},
events: new EventEmitter()
}
runtime.settings.registerNodeSettings = sinon.spy();
index.init(runtime);
index.registerType('test-node-set','test', TestNode, {
settings: {
testOne: {}
}
});
runtime.settings.registerNodeSettings.called.should.be.true();
runtime.settings.registerNodeSettings.firstCall.args[0].should.eql('test');
runtime.settings.registerNodeSettings.firstCall.args[1].should.eql({testOne: {}});
});
it('logs invalid settings',function() {
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:sinon.spy()},
events: new EventEmitter()
}
runtime.settings.registerNodeSettings = function() { throw new Error("pass");}
index.init(runtime);
index.registerType('test-node-set','test', TestNode, {
settings: {
testOne: {}
}
});
runtime.log.warn.called.should.be.true();
});
});
});
describe('allows nodes to be added/removed/enabled/disabled from the registry', function() {
var randomNodeInfo = {id:"5678",types:["random"]};
beforeEach(function() {
sinon.stub(registry,"getNodeInfo").callsFake(function(id) {
if (id == "test") {
return {id:"1234",types:["test"]};
} else if (id == "doesnotexist") {
return null;
} else {
return randomNodeInfo;
}
});
sinon.stub(registry,"disableNode").callsFake(function(id) {
return Promise.resolve(randomNodeInfo);
});
});
afterEach(function() {
registry.getNodeInfo.restore();
registry.disableNode.restore();
});
it('allows an unused node type to be disabled',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
return index.disableNode("5678").then(function(info) {
registry.disableNode.calledOnce.should.be.true();
registry.disableNode.calledWith("5678").should.be.true();
info.should.eql(randomNodeInfo);
done();
});
}).catch(function(err) {
done(err);
});
});
it('prevents disabling a node type that is in use',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.disabledNode("test");
}).should.throw();
done();
}).catch(function(err) {
done(err);
});
});
it('prevents disabling a node type that is unknown',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.disableNode("doesnotexist");
}).should.throw();
done();
}).catch(function(err) {
done(err);
});
});
});
describe('allows modules to be removed from the registry', function() {
var randomNodeInfo = {id:"5678",types:["random"]};
var randomModuleInfo = {
name:"random",
nodes: [randomNodeInfo]
};
before(function() {
sinon.stub(registry,"getNodeInfo").callsFake(function(id) {
if (id == "node-red/foo") {
return {id:"1234",types:["test"]};
} else if (id == "doesnotexist") {
return null;
} else {
return randomNodeInfo;
}
});
sinon.stub(registry,"getModuleInfo").callsFake(function(module) {
if (module == "node-red") {
return {nodes:[{name:"foo"}]};
} else if (module == "doesnotexist") {
return null;
} else {
return randomModuleInfo;
}
});
sinon.stub(registry,"removeModule").callsFake(function(id) {
return randomModuleInfo;
});
});
after(function() {
registry.getNodeInfo.restore();
registry.getModuleInfo.restore();
registry.removeModule.restore();
});
it('prevents removing a module that is in use',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.removeModule("node-red");
}).should.throw();
done();
}).catch(function(err) {
done(err);
});
});
it('prevents removing a module that is unknown',function(done) {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.removeModule("doesnotexist");
}).should.throw();
done();
}).catch(function(err) {
done(err);
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More